版本声明
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.34.1</version>
</dependency>
关于Redis工具类
我整合在了基于Springboot3和java17的简单框架|HSXAZJ的博客 (xiake-half.cn)中,有兴趣可以看看
正文
在进行服务端接口编写时,我们时常要考虑如何让接口访问速度更快,如何让数据库的访问压力减小。
最开始,我们引入Redis缓存作为一种手段
最初的模样
public List<User> getUserList(){
List<User> userList = redisUtil.getList("userList", User.class);
if(CollectionUtils.isEmpty(userList)){
List<User> userList = userMapper.selectList(null);
redisUtil.setList("userList", userList);
}
return userList;
}
第一眼看起来毫无问题,先从Redis中获取,当Redis中没有数据时,从数据库中加载,并且缓存到Redis中
这样我们下一次访问这些数据的时候就可以不经过数据库,减轻了数据库的压力
但是
如果此时Redis中没有缓存或者第一个进来的请求还没来得及向Redis写入缓存,又有很多请求同时进来了呢
如图
来自客户端1的请求显然还未完成呢,客户端2,3,4就来访问接口了,而他们发现此时Redis中没有缓存,也齐刷刷地去查询数据库了
在这种情况下,Redis暂时失效了。这在高流量的项目中是非常危险的。
那么如何去解决呢?
加锁
那这个锁要加在哪个位置呢?
把他加在接口的最上面吧,等客户端1把整个流程都走一遍,Redis中就有缓存了,此时客户端2,3,4就可以从Redis中获取数据,好耶!!!
看下代码
public List<User> getUserList() {
RLock lock = redissonClient.getLock("getUserList");
List<User> userList;
try {
lock.lock();
userList = redisUtil.getList("userList", User.class);
if (CollectionUtils.isEmpty(userList)) {
List<User> userList = userMapper.selectList(null);
redisUtil.setList("userList", userList);
}
} finally {
lock.unlock();
}
return userList;
}
这样Mysql只会被请求一次,好像没什么问题
但是
加锁是会影响性能的呀,每次过来都加锁,数据库是舒服了,但是可能会导致接口请求速度变慢
那把访问Redis的操作移动到锁外面不就好了?
public List<User> getUserList() {
List<User> userList = redisUtil.getList("userList", User.class);
if (CollectionUtils.isEmpty(userList)) {
RLock lock = redissonClient.getLock("getUserList");
try {
lock.lock();
List<User> userList = userMapper.selectList(null);
redisUtil.setList("userList", userList);
} finally {
lock.unlock();
}
}
return userList;
}
这么做的话,你会发现,加了锁之后,数据库不会被并行访问,但访问次数又变回了4次。貌似还不如最初的加锁方案。
这可怎么办呢?
诶,客户端1不是已经把缓存加载到Redis中了嘛,那我拿到锁之后再从Redis中获取一遍不就行了?
双检加锁
public List<User> getUserList() {
List<User> userList = redisUtil.getList("userList", User.class);
if (CollectionUtils.isEmpty(userList)) {
RLock lock = redissonClient.getLock("getUserList");
try {
lock.lock();
userList = redisUtil.getList("userList", User.class);
if (CollectionUtils.isEmpty(userList)) {
List<User> userList = userMapper.selectList(null);
redisUtil.setList("userList", userList);
}
} finally {
lock.unlock();
}
}
return userList;
}
拿到锁后,查询数据库前,再从Redis中获取一次数据,说不定前人已经把数据缓存了,这次Redis里要是再没有数据,那就是真没数据了(拿到了锁,其他请求还在后面排队),如果Redis没有,那我再从数据库中去拿。
这个方案的好处是,Redis中有数据时,客户端可以正常并发访问接口,Redis中没有数据时,同一时间进来的多个请求(客户端2,3,4)会在第一个请求(客户端1)结束后从Redis中获取数据,既减轻了数据库压力,也不会对接口的访问速度有大的影响