参考博客:https://baijiahao.baidu.com/s?id=1771847463104064936&wfr=spider&for=pc
参考博客:https://blog.csdn.net/qq_36602071/article/details/126002886
分布式锁的特点
- 互斥性:分布式锁必须具备互斥性,即同一时刻只能有一个线程持有锁并执行临界操作,防止数据竞争和冲突。
- 超时释放:分布式锁需要具备超时释放功能,即在一定时间内未能成功获取锁时,自动释放锁,避免不必要的线程等待和资源浪费。
- 可重入性:在分布式环境下,同一个节点上的同一个线程如果已经持有锁,则再次请求锁时应该能够成功获取,实现可重入性。
- 高性能和高可用性:分布式锁应具备高性能和高可用性,加锁和解锁的开销要尽可能小,同时要保证锁的可靠性,防止分布式锁失效。
- 支持阻塞和非阻塞性:分布式锁应该支持阻塞和非阻塞性,以满足不同场景下的需求。例如,可以在获取锁时使用阻塞操作或轮询来等待,或者通过非阻塞操作立即返回获取锁的结果。
分布式锁高可用
在上面分布式锁的实现方案中,是针对单节点 Redis 的,在实际生产环境中,为了保证高可用,避免单点故障,一般会使用 Redis 集群。
在集群环境下,分布式锁会遇到一些问题,特别是在Redis主从复制的情况下可能会出现锁的安全性问题。
主从复制是异步的,在故障转移(Failover)过程中,可能导致数据同步的延迟。这样,在发生节点故障时,如果主节点已经获取到锁但数据还未同步到从节点,那么在故障转移后的从节点可能会认为锁没有被持有,从而另一个客户端可以获取到相同的锁。
这种情况下,就会发生多个客户端同时获取到锁的情况,导致竞争和不一致的结果。
我们模拟下这个场景,按照下面的顺序执行:
- 客户端 A 从 Master 节点获取锁;
- Master 节点宕机,主从复制过程中,对应锁的 key 还没有同步到 Slave 节点上;
- Slave 升级为 Master 节点,于是集群丢失了锁数据;
- 其他客户端请求新的 Master 节点,获取到了对应同一个资源的锁;
- 出现多个客户端同时持有同一个资源的锁,不满足锁的互斥性。
可以看到,在单实例场景中和集群环境中实现分布式锁是不同的,关于集群下如何实现分布式锁,Redis 的作者 Antirez(Salvatore Sanfilippo)提出了 Redlock 算法:
Redlock 算法是在 Redis 单节点基础上引入的高可用模式,Redlock 基于 N 个完全独立的 Redis 节点,一般是大于 3 的奇数(通常情况下 N 为 5),来基本保证集群内部的各个节点不会同时宕机。
假设集群有 5 个节点,运行 Redlock 算法的客户端依次执行一下步骤来完成获取锁的操作:
- 客户端记录当前系统时间,以毫秒为单位;
- 依次尝试从 5 个 Redis 实例中,使用相同的 key 获取锁,当向 Redis 请求获取锁时,客户端应该设置一个网络连接和响应超时时间,超时时间应该小于锁的失效时间,避免因为网络故障出现的问题;
- 客户端使用当前时间减去开始获取锁时间就得到了获取锁使用的时间,当且仅当从半数以上的 Redis 节点获取到锁,并且当使用的时间小于锁失效时间时,锁才算获取成功;
- 如果获取到了锁,key 的真正有效时间等于有效时间减去获取锁所使用的时间,减少超时的几率;
- 如果获取锁失败,客户端应该在所有的 Redis 实例上进行解锁,即使是上一步操作请求失败的节点,防止因为服务端响应消息丢失,但是实际数据添加成功导致的不一致。
分布式系统设计是实现复杂性和收益的平衡,考虑到集群环境下的一致性问题,同时要避免过度的设计。在实际业务场景中,我们一般是使用基于单点的 Redis 实现分布式锁就可以了,当出现数据不一致的时候,再通过人工手段去回补。