问题
单机环境中,多个线程用语言层面的锁(如 mutex)就能互斥访问共享资源。
但在分布式环境中,多个进程/机器需要协调对共享资源的访问(如防止定时任务被多个实例同时执行、防止库存超卖)。
这就需要分布式锁。
基本要求
一、互斥性:任一时刻只有一个客户端能持有锁。
二、避免死锁:持有锁的客户端崩溃后,锁必须能被释放(通常需要过期时间)。
三、容错:锁服务本身要高可用。
基于 Redis 的实现
基本思路:用 SET 命令的 NX(只在不存在时设置)和 PX(设置过期时间)选项。
SET lock_key unique_value NX PX 30000- 返回 OK:获取锁成功;
- 返回 nil:锁被别人持有。
释放锁:不能简单地 DEL,因为可能误删别人的锁(如果自己的锁已过期,而别人已经获取了)。
正确做法是用 Lua 脚本保证原子性:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end即"检查值是否是我设置的,是才删除"。
过期时间的问题
设定多久合适?
- 太短:任务还没执行完,锁就过期了,另一个客户端获得锁 → 互斥失效;
- 太长:客户端崩溃后,锁要很久才能释放。
改进:看门狗(Watchdog)
获取锁后,启动一个后台线程,在锁快过期时自动续期(如果任务还在执行)。
客户端正常结束时主动释放。
这是 Redisson 等客户端库的做法。
Redlock 算法
Redis 作者提出的一种多节点方案:
- 向 N 个独立的 Redis 节点(通常 N=5)依次请求加锁(用相同的 key 和随机值);
- 只有在超过半数(N/2 + 1)的节点上成功,且总耗时小于锁的有效时间,才算获取成功;
- 释放时,向所有节点发送释放请求。
目的:避免单点 Redis 故障导致锁失效。
争议:分布式系统专家 Martin Kleppmann 曾撰文质疑 Redlock 在时钟漂移和GC 停顿场景下的安全性。双方有过深入讨论。
实践结论:Redlock 增加了复杂度,但在多数业务场景中,单 Redis 实例(配合合理的主从切换策略)已经够用。要明确的是——分布式锁不能保证 100% 的互斥,对强一致要求的场景,应该考虑基于共识算法(如 etcd、ZooKeeper)的方案,或者从设计上避免对锁的依赖。
基于数据库的实现
另一种方案是用数据库的唯一索引或行锁。
优点:不需要引入额外组件。
缺点:
- 性能较差(每次加锁都是一次数据库操作);
- 数据库的锁与事务耦合,容易出问题;
- 数据库本身可能成为瓶颈。
一个设计建议
很多时候,可以避免用分布式锁:
- 用乐观锁(版本号/CAS):更新时带上版本号,失败则重试。适合冲突不频繁的场景;
- 用消息队列串行化:把并发操作放入队列,由单个消费者处理;
- 用数据库的原子操作(如
UPDATE ... WHERE stock > 0)。
分布式锁应该是最后的选择——它引入了对外部系统的强依赖,且难以做到完全正确。
说明:本文为分布式系统的知识整理。