这样看来,redis的确是可以用来实现分布式锁,原理就是使用set nx命令进行设值,若返回成功则代表拿锁成功,返回失败则代表拿锁失败。
三、实现redis分布式锁需要注意哪些问题?首先来看这样一段代码。
@Override
public List
//尝试获取分布式锁,步骤一
Boolean lock = redisTemplate.opsForValue().setIfAbsent(“lock”, “齐天小圣”);
if (lock) {
//加锁成功,执行业务,步骤二
List
//删除锁,步骤三
redisTemplate.delete(“lock”);
return resultList;
} else {
//加锁失败,自旋重试,即重新调用本方法。
try {
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
return findList();
}
}
乍一看用redis完成一个分布式锁的整体逻辑好像也不是很复杂,无非就是先去获取分布式锁,如果拿到锁了就去执行相应的业务,没有拿到锁就自旋重试。但其实上面这段代码漏洞百出,实际想要完成一个完美的分布式锁是很复杂的,我们需要注意以下问题:
**1、死锁问题:**如果某服务器拿锁成功,执行到步骤二时突然宕机没能正常的执行步骤三释放锁,那么其他正在等待锁的服务器则永远也拿不到锁了,这就是死锁问题。
解决这个问题很简单,只需要在拿锁之后给锁上一个超时时间即可,即使业务过程中出现问题导致不能释放锁,到了过期时间redis也会自动帮我们去把这个锁删除。需要注意的是,千万不能把“获取锁”和“设置超时时间”在代码中分成两步执行,如下:
原因在于这两个步骤分开执行没有保证原子性,拿锁到设置过期时间之间是存在时间差的,如果在这之间机器宕机了还是会存在上述问题,解决办法就是在占锁的同时设置过期时间。
**2、业务时间 > 超时时间:**假设这样一个场景,业务1拿锁成功并设置过期时间30s,但业务1比较复杂需要花费40s,那么到了30s后业务1的锁就已经失效了,此时业务2抢到了锁也进来执行相应的逻辑,那么此时业务1和业务2都在执行各自的业务逻辑,可能会操作相同的数据资源造成违反资源互斥的现象。问题还没完,又过去了10s,业务1执行完了,理所当然的就去删锁,那么自然就会把业务2手里的锁删掉,业务2一脸懵逼。。。这就造成了锁误删的现象。
先解决锁误删的问题,其实很简单,只需要保证谁拿的锁谁就有资格删就可以了,我们在获取锁的时候设置了一个value,此时就派上了用场,把每个业务的value都设置uuid,最后删锁的时候先去redis获取锁对应的值,如果这个值等于uuid才有资格执行删锁命令。需要注意的是这里也需要保证原子性,因为去远程redis获取锁对应的值再返回来也是有时间差的,如果业务1去远程获取到锁的value为“1111”,回来的过程中锁过期了,此时业务2拿到了锁开心的去执行它的业务去了,好景不长,业务1回来判断出锁的value是等于自己的uuid,于是又理所当然的把锁删掉了,业务2又一脸懵逼。。。。这里就需要使用lua脚本解锁,lua脚本就是为了保证这两段操作的原子性,解锁脚本内容如下:
if redis.call(“get”,KEYS[1]) == ARGV[1]
then
return redis.call(“del”,KEYS[1])
else
return 0
end
最后优化后的代码如下:
@Override
public List
//尝试获取分布式锁,并设置过期时间
String uuid = UUID.randomUUID().toString 《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 ();
Boolean lock = redisTemplate.opsForValue().setIfAbsent(“lock”, uuid, 30, TimeUnit.SECONDS);
if (lock) {
//加锁成功,执行业务
List
try{
resultList = findListByDB();
} finally {
//获取值进行对比,若对比成功则有资格进行删锁,需使用lua脚本保证原子性
String script = “if redis.call(“get”,KEYS[1]) == ARGV[1]n” +
“thenn” +
" return redis.call(“del”,KEYS[1])n" +
“elsen” +
" return 0n" +
“end”;
Long result = redisTemplate.execute(new DefaultRedisScript(script, Long.class)
, Arrays.asList(“lock”), uuid);



