高并发情况下加锁
前言
在高并发情况下,要保证服务端的性能,那么会采用缓存来提高服务端的性能,如百万请求访问一个查询的接口,这个接口做了缓存,但是不能保存并发同时到达接口时缓存中也没有数据,恰巧这百万的并发又进入到数据库,那么这时数据库压力过大,导致数据库崩溃,导致服务的不可用,乃至整个系统的崩溃,那么这是由于并发同时绕过了缓存判断直接进入到数据库导致的,这时就可以针对这个并发问题进行加锁
本地锁
单体项目时可以这么做–伪代码
public R getData {
/**
* 将数据库的多次查询变为一次查询
* SpringBoot 所有的组件在容器中默认都是单例的,使用 synchronized (this) 可以实现加锁
*/
synchronized (this) {
/**
* 得到锁之后 应该再去缓存中确定一次,如果没有的话才需要继续查询
* 假如有100W个并发请求,首先得到锁的请求开始查询,此时其他的请求将会排队等待锁
* 等到获得锁的时候再去执行查询,但是此时有可能前一个加锁的请求已经查询成功并且将结果添加到了缓存中
*/
//TODO 数据库查询数据操作
}
}
实操
导入Redis相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
不加同步代码块
@RequestMapping("/nativeLock")
public R list(@RequestParam Map<String, Object> params) throws InterruptedException {
String data = stringRedisTemplate.opsForValue().get("data");
if (!StringUtils.isEmpty(data)) {
log.info("缓存中拿数据");
return R.ok().setData(data);
}
log.error("进入数据库查数据");
//模拟数据库操作
Thread.sleep(200);//模拟数据库查询耗时
List<String> dbData=testService.getDataList();
String cache = JSON.toJSONString(dbData);
stringRedisTemplate.opsForValue().set("data", cache, 1, TimeUnit.DAYS);
return R.ok().setData(dbData);
}
使用JMeter模拟并发测试
这里我使用JMeter模拟了1000个并发请求,从控制台打印结果来看,并没有像理论上那样只查询一次数据库,然后将第一个请求查出的数据缓存到redis中,后面的请求都从redis中拿,这是由于有每个并发都对应一个线程,每个线程每走一行代码都要竞争CPU的当部分线程都卡在String data = stringRedisTemplate.opsForValue().get(“data”);这是Redis中还没有缓存数据,那么这些卡在String data = stringRedisTemplate.opsForValue().get(“data”);这一行代码的线程都得到的是空数据,所以这些得到空数据的都会进入Mysql中查询!!!
优化-加同步代码
@RequestMapping("/nativeLock")
public R list(@RequestParam Map<String, Object> params) throws InterruptedException {
synchronized (this) {
String data = stringRedisTemplate.opsForValue().get("data");
if (!StringUtils.isEmpty(data)) {
log.info("缓存中拿数据");
return R.ok().setData(data);
}
log.error("进入数据库查数据");
//模拟数据库操作
Thread.sleep(200);//模拟数据库查询耗时
List<String> dbData=testService.getDataList();
String cache = JSON.toJSONString(dbData);
stringRedisTemplate.opsForValue().set("data", cache, 1, TimeUnit.DAYS);
return R.ok().setData(dbData);
}
}
使用JMeter模拟并发测试
这里加了synchronized后确实能锁住每个线程,当并发进入后,第一个请求抢到锁后,其他的线程请求就会等待,直到第一个并发请求查询缓存–>判断缓存–>数据库存入缓存–>释放锁;后面的并发请求才能进来,那么后面的并发请求进来的时候去检查Redis缓存的时候就都有值了,那么就只会从Redis中取值,并不会进入Mysql中查询
注意:
这里实际测试下来,没有加同步代码的吞吐量为900多,二加了同步代码的吞吐量只有300多!!!
分布式环境下的情况
这里我使用gateway来做代理,当然咯也可以使用nginx来做代理
将服务都启动,开始并发测试
13001
13002
13003
13004
13005
缺点
分析上面五个服务情况,本地锁只能锁住当前进程,在分布式架构环境下锁不住所有的服务请求,难免每个服务还是会对数据库进行一次IO
分布式锁
缺点
分布式锁性能相比本地锁要差一点
本文地址:https://blog.csdn.net/CSDN877425287/article/details/107370533