分布式锁是分布式系统从“松散运行”走向“协作、可靠运行”的关键桥梁。

1.什么是分布式锁?

分布式锁是用于在分布式系统中协调多个进程或线程访问共享资源的一种机制,确保在任何时刻只有一个客户端能够操作特定的资源,从而保证数据的一致性。

2.分布式锁的基本要素

无论是基于 Redis、ZooKeeper 还是数据库实现,一个可靠的分布式锁必须满足三个要素:
*加锁(Lock):在资源上设置一个锁的标记。
*设置过期时间(TTL):防止客户端宕机导致锁无法释放,造成死锁。这是分布式锁与本地锁(如 Java 的 ReentrantLock)最核心的区别。
*释放锁(Unlock):客户端完成操作后,安全地移除锁。

3.分布式锁面临问题和解决

3.1 死锁

如果客户端在获取锁后,由于某种原因(例如,程序崩溃)未能正常释放锁,导致锁一直被占用,其他客户端无法获取锁,造成死锁。解决方案:在加锁的同时设置过期时间,即使客户端未能正常释放锁,锁也会在过期后自动释放。或者使用 Redlock 算法,提高锁的可靠性,防止死锁。

3.2 锁的误删

-如果客户端 A 获取锁后,由于执行时间过长,导致锁过期自动释放。此时,客户端 B 获取了锁。然后,客户端 A 执行完业务逻辑后,尝试释放锁,但实际上释放的是客户端 B 的锁,造成锁的误删除。解决方案:在加锁时,将锁的值设置为一个唯一标识(例如,UUID),在释放锁时,先判断锁的值是否与自己的唯一标识相等,如果相等,则释放锁;否则,不释放锁。此过程要保证原子性,可以使用 Lua 脚本实现。
-在网络分区的情况下,可能会导致多个进程同时认为自己持有锁。解决方案:在获取锁时生成一个唯一的 UUID,并将其存储在锁的 Key 中。在释放锁时,先检查当前存储的 UUID 是否与自己的 UUID 匹配,只有匹配时才释放锁。

3.3 锁的续期

如果客户端在加锁后,执行时间超过了锁的过期时间,导致锁被自动释放。此时,其他客户端可能会获取锁,造成并发问题。解决方案:客户端在获取锁后,启动一个后台线程,定期检查锁的剩余时间,如果剩余时间小于一定阈值,则使用 EXPIRE 命令续期锁的过期时间。此流程可以自己实现,也可以使用开源框架,例如Redisson 框架不仅提供了自动续期的功能,还可以简化分布式锁的实现。

3.4 锁的竞争

在高并发场景下,多个进程可能会同时竞争锁,导致锁的获取失败率较高。解决方案:可以使用随机退避重试策略,在获取锁失败后,随机等待一段时间后再次重试。

3.5 锁的重入性

如果同一个进程多次尝试获取锁,可能会导致锁的获取失败。解决方案:在锁的 Key 中存储一个计数器,表示当前进程获取锁的次数。每次获取锁时增加计数器,释放锁时减少计数器,只有计数器为 0 时才删除锁的 Key。

3.6 锁的公平性

多个进程同时请求锁时,可能会出现“饥饿”现象,某些进程长时间无法获取锁。解决方案:可以使用 Redis 的 List 数据结构实现排队机制,确保请求锁的进程按照顺序获取锁。或者使用成熟的分布式锁实现库,如 Redisson,它提供了公平锁和可重入锁等功能。