推荐链接:
总结——》【Java】
总结——》【Mysql】
总结——》【Redis】
总结——》【Kafka】
总结——》【Spring】
总结——》【SpringBoot】
总结——》【MyBatis、MyBatis-Plus】
总结——》【Linux】
总结——》【MongoDB】
总结——》【Elasticsearch】
Redis——》锁被别人释放
一、锁被别人释放的场景
SET lock_key 1 EX 10 NX // 10s后自动过期
执行以上命令,给锁加自动过期时间,但每个客户端在释放锁时,都是无脑操作,并没有检查这把锁是否还归自己持有,所以就会发生释放别人锁的风险。
- 客户端1加锁成功,开始操作共享资源
- 客户端1操作共享资源耗时太久,超过了锁的过期时间,锁失效(锁被自动释放)
- 客户端2加锁成功,开始操作共享资源
- 客户端1操作共享资源完成,在finally块中手动释放锁,但此时它释放的是客户端2的锁。
二、如何避免锁被别人释放
解决方案:客户端在加锁时,设置一个只有自己知道的唯一标识进去。
唯一标识可以采用:
- 自己的线程ID
- UUID(随机且唯一)
1、GET获取锁 + DEL释放锁
(1)实现
// 加锁
SET lock_key unique_value EX 10 NX
//释放锁,先判断这把锁是否归自己持有,比较unique_value是否相等,避免误释放
if redis.get("lock_key") == unique_value then
return redis.del("lock_key")
(2)优点
解决了锁被别人释放的问题
(3)缺点
释放锁使用的是GET + DEL两条命令,这两条命令不能保证是原子操作(一起成功),有可能执行第一条GET成功,但第二条DEL却执行失败。
2、Lua脚本
因为Redis处理每个请求是单线程执行的,在执行一个Lua脚本时其它请求必须等待,直到这个Lua脚本处理完成,这样一来GET+DEL之间就不会有其他命令执行了。
(1)实现
<1>创建Lua脚本unlock.script
参数 | 描述 |
---|---|
KEYS[1] | 表示lock_key |
ARGV[1] | 表示当前客户端的唯一标识 |
//Lua脚本语言
//释放锁,先判断这把锁是否归自己持有,比较unique_value是否相等,避免误释放
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
<2>执行Lua脚本unlock.script
redis-cli --eval unlock.script lock_key , unique_value
(2)优点
解决了锁被别人释放的问题
三、其它
1、增大过期时间是否可以避免锁被别人释放?
如果只是一味增大过期时间,只能缓解问题降低出现问题的概率,依旧无法彻底解决问题。
原因在于客户端在拿到锁之后,在操作共享资源时,遇到的场景是很复杂的,既然是预估的时间,也只能是大致的计算,不可能覆盖所有导致耗时变长的场景。