C# 分布式锁实现方法 C#如何使用Redis或Zookeeper实现分布式锁

来源:这里教程网 时间:2026-02-21 17:42:02 作者:

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
+ 正则匹配,否则阻塞主线程

相关推荐