生产环境推荐StackExchange.Redis配合SET key value EX seconds NX加锁,释放时用Lua脚本校验value一致性;ZooKeeper推荐Curator的InterProcessMutex,强一致但性能较低。

Redis 实现分布式锁:用 StackExchange.Redis + SETNX + Lua 脚本最稳妥
直接上结论:生产环境推荐用 StackExchange.Redis 客户端,配合原子性 SET 命令(带 EX 和 NX 参数)实现加锁,释放时用 Lua 脚本校验 key 和 value 一致性。别手写 GET + DEL,那是经典误操作。
常见错误现象:Thread A 加锁成功但执行超时,Thread B 在过期后重入,Thread A 恢复后误删 Thread B 的锁 —— 根源就是释放锁没做 value 校验。
- 加锁必须用
SET key value EX seconds NX,不能拆成SETNX+EXPIRE(非原子) - value 必须是唯一标识(如
Guid.NewGuid().ToString()),不能用固定字符串或线程 ID - 释放锁必须用 Lua 脚本:
if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end - 锁自动续期(watchdog)需额外实现,StackExchange.Redis 本身不提供;可用
Timer定期PEXPIRE,但注意只续自己持有的锁
ZooKeeper 实现分布式锁:用 Curator Framework 的 InterProcessMutex 最省心
如果你已在用 ZooKeeper 做服务发现或配置中心,那直接上 CuratorFramework 的 InterProcessMutex 是最稳选择。它底层基于临时顺序节点 + Watcher,天然支持可重入、公平性和会话自动清理。
使用场景:对锁延迟不敏感(ZK 有 ZAB 协议开销)、需要强一致性语义、已有 ZooKeeper 集群运维能力。
- 依赖包用
Curator-recipes(.NET 版本对应CuratorNet或通过IKVM调用 Java 版,但更推荐用ZooKeeperNetCore+ 自封装) - 不要手动创建 EPHEMERAL_SEQUENTIAL 节点来“造轮子”,
InterProcessMutex已处理了节点竞争、Watcher 失效、连接中断重试等边界 - 注意会话超时时间(
sessionTimeoutMs)要明显大于业务最大执行时间,否则锁可能被误释放 - ZooKeeper 锁不支持自动续租,超时即释放;若需长任务,得在业务层主动调用
renewSession()或拆分任务
选 Redis 还是 ZooKeeper?关键看你的基础设施和一致性要求
Redis 锁快、简单、资源消耗低,但依赖单点(或哨兵/集群)的可用性,且过期时间是预设硬限制;ZooKeeper 锁慢一点,但 CP 强一致,节点故障不影响锁语义,适合金融级场景。
性能影响:Redis 加锁耗时通常 ConnectionMultiplexer 必须单例)。
- 已有 Redis 集群且能接受 AP 模型 → 优先 Redis + Lua
- 已部署 ZooKeeper 且业务要求“绝不允许多个客户端同时持有同一把锁” → 选 ZooKeeper
- 想跨语言统一锁协议(比如 C# 服务和 Python 脚本共用一把锁)→ ZooKeeper 更易对齐语义
- 完全不想运维中间件?考虑
RedLock算法(多个 Redis 实例),但微软官方不推荐,实际复杂度和收益不成正比
容易被忽略的细节:锁粒度、异常恢复与监控
很多人只关注“怎么加锁”,却在锁的生命周期管理上翻车。真正难的不是获取锁,而是确保锁被正确释放、业务异常时锁不残留、以及能快速定位锁争用热点。
- 锁 key 命名要有业务上下文,比如
lock:order:10086,别用泛化名global_lock - 所有
try块必须配finally { unlock() },且unlock()要做空指针和已释放判断,避免重复释放报错 - 记录锁等待时间(从请求到拿到锁的耗时),超过阈值就告警——这往往是下游依赖慢或锁设计过粗的信号
- Redis 方案中,别依赖
KEYS *查锁,改用SCAN+ 正则匹配,否则阻塞主线程











