分布式锁之Redis 分布式锁
使用 Redis 分布式锁,一般用 Redisson 框架就好了,非常的简便易用。
一、Redisson 实现 Redis 分布式锁的底层原理
1、加锁机制
如果客户端A要加锁,面对的是一个 Redis Cluster 集群,首先会根据 Hash 节点选择一台机器。
紧接着,就会发送一段 Lua 脚本到 Redis 上。Lua 脚本中封装了一大坨复杂的业务逻辑,这些发送给 Redis 保证这段复杂业务逻辑执行的原子性。
加锁的过程是这样的:
根据Key加锁,如要加锁的 Key 不存在的话,则进行加锁,一般设置默认生存时间30 秒,和设置加锁的客户端的 ID。
例如:客户端ID:870621bf-i520-8888-87bf-881013jm2222:1,要给锁 Key 为 myKeyName的加锁,加锁成功以后会看到这样一个Hash 数据结构
myKeyName:
{
"870621bf-i520-8888-87bf-881013jm2222:1" : 1
}
2、锁互斥机制
在客户端 A 加锁成功以后,客户端 B 来尝试加锁,执行了同样的一段 Lua 脚本,
此时锁 Key 已经存在了,且锁 Key对应的客户端 ID并不是客户端 B ,
客户端 B 会获取到这个锁的剩余生存时间,此时客户端 B 会进入一个循环,不停的尝试加锁。
3、watch dog 自动延期机制
客户端 A 加锁的锁 Key 默认生存时间才 30 秒,如果超过了 30 秒,客户端 A 还没有执行完任务,还想继续持有这把锁,怎么办呢?不用担心,只要客户端 A 一旦加锁成功,就会启动一个 watch dog 看门狗,他是一个后台线程,会每隔 10 秒检查一下,如果客户端 A 还持有锁 Key,那么就会不断的延长锁 Key 的生存时间。
4、可重入加锁机制
那如果客户端 A 已经持有了这把锁了,结果可重入的加锁会怎么样呢?
对于同一个锁 Key的客户端 ID,会增加对客户端 A 的加锁次数,累加 1。
此时Hash 数据结构会变成
myKeyName:
{
"870621bf-i520-8888-87bf-881013jm2222:1" : 2
}
5、释放锁机制
每次任务完成后执行释放分布式锁,Hash 数据结构中的那个加锁次数减 1。
如果发现加锁次数是 0 了,说明这个客户端已经不再持有锁了,此时就会从 Redis 里删除这个 Key。
此时轮询等待的客户端 B 就可以尝试完成加锁了。这就是所谓的分布式锁的开源 Redisson 框架的实现机制。
二、Redis 分布式锁的缺点
上面那种方案最大的问题,就是如果你对某个 Redis Master 实例,写入了 myKeyName 这种锁 Key 的 Value,此时会异步复制给对应的 Master Slave 实例。
但是这个过程中一旦发生 Redis Master 宕机,主备切换,Redis Slave 变为了 Redis Master。
接着就会导致,客户端 B 来尝试加锁的时候,在新的 Redis Master 上完成了加锁,而客户端 A 也以为自己成功加了锁。
此时就会导致多个客户端对一个分布式锁完成了加锁。这时系统在业务语义上一定会出现问题,导致各种脏数据的产生。
所以这个就是 Redis Cluster,或者是 redis master-slave 架构的主从异步复制导致的 Redis 分布式锁的最大缺陷:在 Redis Master 实例宕机的时候,可能导致多个客户端同时完成加锁。