昨天在掘金看到了1篇文章Redis——由分布式锁造成的重大事故,文中主要描述了一起因为Redis
分布式锁错误自动过期导致的问题。
今天我就结合这篇文章来谈谈关于Redis除了缓存
之外的分布式锁的用法和思考。
Java中有关键字sync
和Lock
相关的类库都可以实现锁的功能,但是作用范围也就仅仅局限在同一个JVM
中。**“分布式锁”**就是在分布式环境下实现类似的功能。
利用Redis实现分布式锁的技术细节主要有如下几点:
SET key value EX seconds NX
比如:
SET lock_key "xhufx" EX 10086 NX
==============
if redis.call('get',KEYS[1])==AVGV[1]) then
return redis.call('del',KEYS[1])
else
return 0
end
对于第一个部分的Redis
指令
key
和value
对应的就是分布式锁的标志键和对应的值EX second
:表示设置键的过期时间为 second
秒NX
:只在键不存在时,才对键进行设置操作第二个部分的lua
脚本,也是关键。因为Redis
没有get-del
的原子命令,所以只能这么做。
同时需要注意的是,value
和EX seconds
如果设置不当,都会踩坑!
Lock
就会一直存在,导致其他服务无法竞争到该资源,进行后续任务。那么设置多长时间了?太长的话,可能犯同不加锁一样的问题;太短的话实际业务还没有完成,就被Redis自动删掉了,也不正确,开篇中提到的问题就是因为高并发下服务执行时间大于了设置的过期时间导致的超卖问题。关于这一点个人建议过期时间至少是平时正常服务的3-5倍的时间,同时如果是秒杀,还需要其他方案进行兜底。value
建议由上锁
的服务方设置为一个不重复的值,比如UUID
或者SnowFake
值,来实现谁添加谁删除,虽然无法解决上述问题,但是至少当服务删除的时候,发现Value
不一样的时候,可以打一个日志出来,方便后续定位和排查。上文简单分析了和总结了下Redis
实现分布式锁最简单的做法和面临的问题,除了上述这个做法外还有其他做法吗?
有的,我们先说下如何 解决 优化自动过期导致其他服务获取到分布式锁的解决方案
Redis
的自增incry
来获取一个值(这个过程MySQL或其他也是可以的)SELECT value From DB Where Id=key FOR UPDATE
,将这个值同DB中已经存在的值(之前保存的)进行大小比对:当前的newValue
是否大于已保存到DB中的oldValue
newValue
替换oldValue
,然后进行后续业务FOR UPDATE 是为了阻塞下一次的查询和后续流程...
当业务执行后,需要进行数据入库的时候,再次进行一次判定:先开起FOR UPDATE
锁,curValue
同dbValue
是否相等
3.1
,此时直接退出。这样将分布式锁搞复杂了:FOR UPDATE
本质依赖了数据库的锁,既然数据库提供了这个机制,初步想了下,分布式锁依靠Redis也是可以完成的吧?
太晚了,留待第二篇写吧。
分布式锁应该没有100%正确的版本,我个人觉的最好的做法还是通过另外的技术进行兜底。
没有完美的架构,只有平衡的架构,就是这个道理!