StackExchange.Redis 实现分布式锁必须满足四要素:唯一 lockValue、Lua 脚本原子校验释放权、合理 TTL(覆盖业务 P99×2)、watchdog 自动续期,缺一不可,否则易致超卖等生产事故。

StackExchange.Redis 是当前 C# 生产环境中最主流的 Redis 客户端,直接用它实现分布式锁比套一层框架(如 Redisson)更可控、更轻量。但“能跑通”和“能上线”是两回事——很多团队在压测或上线后才发现锁被误删、超时失效、续期失败等问题。核心结论是:不带唯一标识 + 不校验释放权 = 生产事故高发组合。
为什么不能只用 StringSetAsync(key, value, when: When.NotExists)
这个调用看似原子、简洁,但它只解决了“加锁”的第一步,却埋下三个隐患:
-
When.NotExists确保 key 不存在时才写入,但不控制 value 内容 —— 所有客户端都可能写入相同 value(比如硬编码"1"),导致后续无法区分锁归属 - 没设过期时间(
TimeSpan参数为空)?锁会永久存在,服务挂掉就成死锁 - 即使设了过期时间,若业务执行超时(比如锁 TTL=10s,但扣款逻辑跑了 12s),锁自动释放后,其他节点可能介入,而原节点仍会执行
DEL或等效操作 —— 这就删掉了别人的锁
LockAsync 必须返回唯一 lockValue 并参与解锁校验
每个客户端获取锁时,必须生成不可预测、全局唯一的标识(如 Guid.NewGuid().ToString()),并把它作为 value 存入 Redis。这不是为了“好看”,而是解锁时做原子校验的唯一依据。
释放锁不能简单调用 StringDeleteAsync(key),必须用 Lua 脚本保证“判断 value 相等 + 删除”原子执行:
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end对应 C# 调用:
await _redisDb.ScriptEvaluateAsync(script, new RedisKey[] { lockKey }, new RedisValue[] { lockValue });- 脚本中
KEYS[1]是锁 key,ARGV[1]是你当初StringSetAsync写入的lockValue - 返回值为
1表示删除成功,0表示锁已被他人持有或已过期,此时不应报错,而应记录 warn 日志 - 千万别把 Lua 脚本拼接进字符串再执行 —— 会被注入或解析失败;用
RedisScript.Prepare()预编译更安全
锁超时时间不是拍脑袋定的,得覆盖「最长业务耗时 + 网络抖动」
TTL 设置太短(如 3 秒),在 GC 暂停、数据库慢查询、远程 API 延迟时极易提前释放;设太长(如 5 分钟),一旦节点崩溃,其他节点要等很久才能抢到锁。
- 建议初始值按 P99 业务耗时 × 2 估算,例如订单扣款 P99 是 800ms,起步设 2 秒,上线后根据监控调整
- 关键路径必须支持自动续期(watchdog)。不要自己起 Timer —— 多线程竞争风险高;用
Task.Run(() => RenewLockAsync(...))在锁剩余 1/3 时间时尝试延长 TTL - 续期也必须校验 lockValue,且新 TTL 应重置为原始值(不是叠加),否则可能无限续下去
别忽略连接与序列化配置对锁行为的影响
StackExchange.Redis 默认使用 JSON.NET 序列化,但如果你传的是 byte[] 或自定义类型,StringSetAsync 可能静默失败或存入乱码,导致 ScriptEvaluateAsync 中 GET 出来的值跟预期不一致。
- 锁 key 和 value 全部强制用
string类型,避免隐式序列化干扰。value 就用Guid字符串,key 用GetLockKey("order-123")这种确定性拼接 - 确保
ConfigurationOptions.AbortOnConnectFail = false,否则 Redis 短暂不可用时整个锁逻辑抛异常中断 - 连接池大小不够(如
DefaultConnectionMultiplexer未配置ConnectTimeout和SyncTimeout)会导致高并发下LockAsync长时间阻塞,看起来像“锁卡住”
真正难的不是写出来,而是让锁在节点重启、网络分区、GC STW、Redis 主从切换这些真实故障下依然不丢不误不重。唯一 lockValue + Lua 校验 + 合理 TTL + watchdog 续期,四者缺一不可。漏掉任意一环,都可能在凌晨三点收到库存超卖告警。


















