Redis分布式锁
目录
为什么需要分布式锁
单机锁
多线程并发读写操作时对共享变量的操作使用锁来互斥
- 作用范围:同一个进程中
分布式锁
微服务架构下,一个应用会部署多个服务,即存在多个进程,当多个进程想要对同一共享数据操作时(如Mysql中的一行数据),而Java的锁只能保证单个进程内的互斥性,此时就需要分布式锁来解决。
此时需要一个对所有进程共享的,可以统一管理加锁的、保证互斥的平台,如Redis,Zookeeper
Redis分布式锁
如何实现分布式锁——SETNX
SETNX:SET if Not eXist
-
加锁——SETNX——互斥能力
应用程序发起加锁命令
-
key不存在,可以设置值,返回1
-
key存在,无法设置值,返回0
-
127.0.0.1:6379> setnx lock 1024
(integer) 1
127.0.0.1:6379> setnx lock 1024
(integer) 0
127.0.0.1:6379> setnx lock 1
(integer) 0
- 释放锁——DEL
127.0.0.1:6379> del lock
(integer) 1
如何避免锁不被释放——设置过期时间
产生死锁的情况
- 应用程序挂了,没机会释放锁
- 程序业务逻辑处理异常,没有释放锁
如何解决死锁——设置租期
-
实现:给redis的key设置过期时间
-
命令:SETNX + EXPIRE(注意:SETNX命令无法设置锁的过期时间)
127.0.0.1:6379> setnx lock 1024
(integer) 1
127.0.0.1:6379> expire lock 10
(integer) 1
-
问题:SETNX(加锁)和EXPIRE(设置过期时间)是两条命令,无法保证同时成功或失败,即不是原子操作,如下情况:
- SETNX 执行成功,执行 EXPIRE 时由于网络问题,执行失败
- SETNX 执行成功,Redis 异常宕机,EXPIRE 没有机会执行
- SETNX 执行成功,客户端异常崩溃,EXPIRE 也没有机会执行
-
解决:保证两个操作的原子操作,使用SET + 参数(EX NX)
- 注意:EX 和 NX 要是大写
127.0.0.1:6379> set lock 1024 EX 10 NX
(nil)
127.0.0.1:6379> set lock 1024 EX 10 NX
OK
如何保证锁不被他人释放——添加标识+Lua
场景
- 客户端 1 加锁(假设为lock)成功,开始操作共享资源
- 客户端 1 操作共享资源的时间,「超过」了锁的过期时间,锁(lock)被「自动释放」
- 客户端 2 加锁(lock)成功,开始操作共享资源
- 客户端 1 操作共享资源完成,释放锁(lock)(但释放的是客户端 2 的锁)
产生原因
- 客户端释放锁时不需要验证(看锁是不是自己持有,即判断持有者),可以直接删除
解决
- 设置客户端的唯一标识,如UUID
127.0.0.1:6379> SET lock $uuid EX 20 NX
OK
- 释放锁时,要先进行判断,再释放
// 锁是自己的,才释放
if redis.get("lock") == $uuid:
redis.del("lock")
使用Lua脚本
-
问题:产生前面问题,GET + DEL 无法保证原子操作
-
解决:使用Lua脚本
redis 单线程处理,对于每个请求串行处理,现在不用GET 和 DEL 发起两个请求处理,而是放在一个Lua脚本中,Redis把脚本看成一个请求处理,从而GET + DEL 之间就不会插入其他命令,保证了原子性
local lock_key = KEYS[1]
local unique_identifier = ARGV[1]
if redis.call('GET', lock_key) == unique_identifier then
return redis.call('DEL', lock_key)
else
return 0 -- Lock release failed
end
锁的过期时间怎么评估——守护线程,自动续期
场景:操作共享资源的时间 > 过期时间
- eg:加锁时设置过期时间10s,而共享资源的操作处理时间25s,导致业务逻辑还没操作完成,锁就被释放了,此时其他线程就可以访问共享资源类
原因:无法准确评估锁共享资源的操作时间
解决方案:
- 延长过期时间——无法彻底解决
- 定期检查过期时间
加锁时,先设置一个过期时间,然后我们开启一个「守护线程」,定时去检测这个锁的失效时间,如果锁快要过期了,操作共享资源还未完成,那么就自动对锁进行「续期」,重新设置过期时间。
使用Redisson
- redisson实现分布式锁时,采用了自动续期来避免锁过期,这个守护线程我们一般也把它叫做看门狗线程。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通