共计 1490 个字符,预计需要花费 4 分钟才能阅读完成。
背景与痛点
在分布式系统中,多个节点同时访问共享资源时,数据一致性成为核心挑战。传统单机锁(如 synchronized)在跨进程、跨服务器场景下完全失效,而数据库悲观锁又因性能瓶颈难以支撑高并发。我们曾遇到一个典型 case:订单库存超卖,明明系统显示剩余 10 件商品,却产生了 15 笔成功订单。

技术选型对比
- Redis:
- 优点:性能极高(10 万 + QPS),实现简单
-
缺点:异步复制可能丢锁,需自行处理续期
-
Zookeeper:
- 优点:强一致性,watch 机制完善
-
缺点:写性能低(约 1 万 QPS),依赖会话
-
etcd:
- 优点:强一致性,支持 lease API
- 缺点:运维复杂度较高
OpenClaw 选择 Redlock 算法,因其在 Redis 基础上通过多实例部署(通常 5 个)实现容错,满足 CP 需求。数学证明显示当 N /2+ 1 个节点获取成功时,锁才真正生效,显著降低脑裂风险。
核心实现
# Redlock 核心实现(Python 伪代码)class RedLock:
def __init__(self, redis_nodes):
self.quorum = len(redis_nodes) // 2 + 1
self.redis_conns = [Redis(host=n.host, port=n.port) for n in redis_nodes]
def lock(self, resource, ttl_ms):
token = str(uuid4())
start_time = time.time() * 1000
# 尝试获取多数节点锁
success_count = 0
for conn in self.redis_conns:
if conn.set(resource, token, nx=True, px=ttl_ms):
success_count += 1
# 校验耗时是否有效
elapsed_ms = time.time() * 1000 - start_time
if success_count >= self.quorum and elapsed_ms < ttl_ms:
return token
else:
self.unlock(resource, token) # 部分成功需要回滚
return None
def unlock(self, resource, token):
# Lua 脚本保证原子性
unlock_script = """if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end"""
for conn in self.redis_conns:
conn.eval(unlock_script, 1, resource, token)
性能与安全
- 超时设置 :
- TTL 建议设为业务平均耗时 3 倍(如业务需 200ms,设 600ms)
-
过短会导致误释放,过长会增加死锁时间
-
时钟漂移防护 :
- 采用 elapsed_ms 校验获取锁的总耗时
-
当超过 TTL 时立即放弃,避免持有失效锁
-
压测数据 :
- 5 节点 Redis 集群可支撑 8 万 +/ 秒的锁请求
- 平均获取耗时 1.2ms(P99 在 5ms 内)
避坑指南
- GC 停顿 :JVM 应用需确保 GC 时间小于锁 TTL 的 1 /3
- 锁续期 :对于长任务,建议起后台线程每 TTL/ 3 时间执行一次 pexpire
- 网络分区 :出现分区时可能产生双主,关键业务需增加 DB 唯一约束二次校验
思考与实践
如何实现可重入锁? 提示:需要记录持有者线程 ID 和重入次数。欢迎在评论区分享你的实现方案!
通过这次 OpenClaw 实战,我们总结出分布式锁的黄金原则:宁可拒绝,不要错误。当你不确定锁状态时,永远选择让业务失败而非继续执行。这虽然会降低可用性,但能确保数据绝对正确。
正文完
