一、分布式锁实现方式
1. 数据库乐观锁。(增加字段版本标识version控制实现)
2. Redis的分布式锁。(本文重点介绍)
3. ZooKeeper的分布式锁。(ZooKeeper机制规定:同一个目录下只能有一个唯一的文件名,借助ZooKeeper的临时节点实现)
二、Redis的分布式锁实现
1. 使用jedis的2.7.x及以上版本。
2. 获取锁:
命令:SET key value [NX|XX] [EX|PX] seconds
NX – 只有键key不存在的时候才会设置key的值
XX – 只有键key存在的时候才会设置key的值
EX seconds – 设置键key的过期时间,单位时秒
PX milliseconds – 设置键key的过期时间,单位时毫秒
3.释放锁:Redis+Lua实现:
三、注意点
1. 首先,这个锁必须要设置一个过期时间。否则的话,当一个客户端获取锁成功之后,假如它崩溃了,或者由于发生了网络分割导致它再也无法和Redis节点通信了,那么它就会一直持有这个锁,而其它客户端永远无法获得这个锁了。
2. 第二,第一步获取锁的操作,把它实现成了两个Redis命令,SETNX+EXPIRE。虽然这两个命令和前面描述中的一个SET命令执行效果相同,但却不是原子的。如果客户端在执行完SETNX后崩溃了,那么就没有机会执行EXPIRE了,导致它一直持有这个锁。
3. 第三,设置一个随机字符串randomVal是很有必要的,它保证了一个客户端释放的锁必须是自己持有的那个锁。(最好是唯一性UUID)
4. 第四,释放锁的操作必须使用Lua脚本来实现。释放锁其实包含三步操作:GET、判断和DEL,用Lua脚本来实现能保证这三步的原子性。(获取锁也可通过Redis+Lua实现)
四、思考
还有一个问题是由failover引起的,基于单Redis节点的分布式锁无法解决的。假如Redis节点宕机了,那么所有客户端就都无法获得锁了,服务变得不可用。为了提高可用性,我们可以给这个Redis节点挂一个Slave,当Master节点不可用的时候,系统自动切到Slave上(failover)。但由于Redis的主从复制(replication)是异步的,这可能导致在failover过程中丧失锁的安全性。针对这个问题,可以参考一下Redlock的算法(https://redis.io/topics/distlock)。