驽马十驾 驽马十驾

驽马十驾,功在不舍

目录
关于Redis实现分布式锁的思考-1
/    

关于Redis实现分布式锁的思考-1

开篇

昨天在掘金看到了1篇文章Redis——由分布式锁造成的重大事故,文中主要描述了一起因为Redis分布式锁错误自动过期导致的问题。

今天我就结合这篇文章来谈谈关于Redis除了缓存之外的分布式锁的用法和思考。

分布式锁

简单版本

Java中有关键字syncLock相关的类库都可以实现锁的功能,但是作用范围也就仅仅局限在同一个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指令

  • keyvalue对应的就是分布式锁的标志键和对应的值
  • EX second :表示设置键的过期时间为 second
  • NX :只在键不存在时,才对键进行设置操作

第二个部分的lua脚本,也是关键。因为Redis没有get-del的原子命令,所以只能这么做。

同时需要注意的是,valueEX seconds如果设置不当,都会踩坑!

  • 先说说过期时间,过期时间必须要设置,要不然服务加锁后,宕机后,利用Redis实现的Lock就会一直存在,导致其他服务无法竞争到该资源,进行后续任务。那么设置多长时间了?太长的话,可能犯同不加锁一样的问题;太短的话实际业务还没有完成,就被Redis自动删掉了,也不正确,开篇中提到的问题就是因为高并发下服务执行时间大于了设置的过期时间导致的超卖问题。关于这一点个人建议过期时间至少是平时正常服务的3-5倍的时间,同时如果是秒杀,还需要其他方案进行兜底。
  • value建议由上锁的服务方设置为一个不重复的值,比如UUID或者SnowFake值,来实现谁添加谁删除,虽然无法解决上述问题,但是至少当服务删除的时候,发现Value不一样的时候,可以打一个日志出来,方便后续定位和排查。

上文简单分析了和总结了下Redis实现分布式锁最简单的做法和面临的问题,除了上述这个做法外还有其他做法吗?

有的,我们先说下如何 解决 优化自动过期导致其他服务获取到分布式锁的解决方案

改进版1

  1. 获取锁的时候,通过Redis的自增incry来获取一个值(这个过程MySQL或其他也是可以的)
  2. 将这个值保存到自己的服务的内存中
  3. 通过SELECT value From DB Where Id=key FOR UPDATE,将这个值同DB中已经存在的值(之前保存的)进行大小比对:当前的newValue是否大于已保存到DB中的oldValue
  • 如果不满足,可能是拿到这个value后,因为STW、IO等原因导致分布式锁已经过期了,所以直接退出,不再进行后续执行。
  • 假如满足那么将这个newValue替换oldValue,然后进行后续业务

FOR UPDATE 是为了阻塞下一次的查询和后续流程...

  1. 当业务执行后,需要进行数据入库的时候,再次进行一次判定:先开起FOR UPDATE锁,curValuedbValue是否相等

    • 如果相等,那么表示还是这个锁的有效范围,那么直接执行。
    • 如果不想等,那么肯的原因同3.1,此时直接退出。

这样将分布式锁搞复杂了:FOR UPDATE本质依赖了数据库的锁,既然数据库提供了这个机制,初步想了下,分布式锁依靠Redis也是可以完成的吧?

改进版2

太晚了,留待第二篇写吧。

结语

分布式锁应该没有100%正确的版本,我个人觉的最好的做法还是通过另外的技术进行兜底。

没有完美的架构,只有平衡的架构,就是这个道理!

积土成山,风雨兴焉。积水成渊,蛟龙生焉。