Redis lock
目的:不同进程处理相同业务需满足在同一时刻只有一个任务在执行。
丐版:
SET key value [EX seconds] [PX milliseconds] [NX|XX]
一般场景下我们用丐版的分布式锁就够了,改版的分布式锁指的是利用redis的NX互斥机制,实现获得锁动作的原子操作,如果获得锁的动作失败,则需要进行等待或自旋等待。
一旦忘记释放锁或出现了某些异常导致释放锁的动作失败,则其他进程将永远获得不到锁,导致业务异常(Q1)。
所以在获得锁的同时需要给锁设置一个过期时间,如果在该段时间内依然没有释放锁的动作,则自动过期。如果将设置过期时间的动作和获取锁的动作分开处理就破坏了获取锁的原子操作,过期时间如果执行失败又会回到Q1的问题中。Redis的SET操作支持在加锁的同时设置过期时间(EX),这样就做到了”真*原子”操作。
中配:
锁续约
在丐版实现中,会产生一个问题:
用预估时间去评判业务执行时间是不准确的,随着时间的推移数据的增加,业务逻辑中涉及到的数据量也会变大,万一业务执行耗时增长到锁的极限,就会导致其他进程认为我可以获得锁了,就会不会达到分布式锁的预期(Q2)。并且当锁过期了之后,第二把锁被另外一个进程获取了,第一个进程执行完毕后去释放锁,其实释放的是第二个进程获取的锁,整个锁的控制就乱套了(Q3)。
解决Q3这个问题需要做到一个点,就是谁拿的锁,谁放回来。每次获得锁的时候引入一个随机数,每个进程加锁的同时要设置一个随机数到锁的value中,释放的时候需要判定一下这个锁是否是自己的,如果是自己获得的的再去释放,不是自己获得的就直接忽略就好了。通过这个随机数达到一个自己的锁自己释放的一个目的。
针对Q2,就好比是厕所有人,最多占用10秒钟,10秒钟之后门口的有人提示灯自动关了,门外的人发现提示灯关了,推门就进去了,结果马桶堵了。
这个时候必须有个看门的,当第一个进程获取到锁的时候,就告诉看门人我要用10秒,看门人每隔5秒就去看一下提示灯,如果灯还亮着并且没有换人就给这个提示灯重置为10秒,给里面的人续约;如果发现换人了或者灯直接灭了,那么表明自己负责的那个人已经完事了,自己的使命已经完成了。
高配:
红锁
丐版和中配版都没有解决的一个问题,如果Redis挂了咋办?
Redis挂了之后,就好比是厕所的灯没电了,任何人都无法在进入之前点亮这个“有人”的灯,“上厕所”这项业务就挂掉了。
红锁主要是解决锁资源本身的HA问题的,这个是由Redis作者提出的。简单的说厕所的门需要三盏灯,每盏灯配置独立电源管理,两个或者两个以上的灯都点亮就表明成功获得锁。反映到架构层面,就是需要准备三(N)台硬件独立的Redis服务,每次获得锁至少在两(>= N/2+1)台设备上获得锁才算获得锁成功,如果获得锁失败则立即尝试在下一台设备上获取。
因为redis版分布式锁还引发了一场唇枪舌战,在当时也挺有名的,可以挖个坟看看。
Martin质疑:https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html
Redis作者回应质疑:http://antirez.com/news/101
整体来说Martin的质疑其实挺对的,但如果存在“锁续约”的话实际上就不会出现他提出的一些问题,主要是续约主体进程不能是出异常的进程本身;总的来说用Redis去“精准解决”分布式锁的问题还是需要做一些工作的,但用丐版锁就已经解决了绝大多数问题了。