Fiber 框架本身不提供分布式锁能力,必须自行集成 Redis 客户端并实现原子加锁(SET key value NX PX)与 Lua 校验解锁逻辑;生产推荐用 SET + Lua 方案,避免死锁和误删。

直接说结论:Fiber 框架本身不提供分布式锁能力,必须自行集成 Redis 客户端 + 原子加锁逻辑,或引入 Redisson(需桥接);生产环境推荐用 SET key value NX PX + Lua 释放,避免手写竞态逻辑。
为什么 Fiber 不能直接用 redis.SetNX 就完事?
Fiber 是 Go 的轻量 HTTP 框架,它没有内置锁抽象,所有 Redis 操作都依赖你选的客户端(如 github.com/go-redis/redis/v9)。但问题在于:SetNX 只管“设值”,不自动带过期时间——如果只调 client.SetNX(ctx, key, val, 0),锁永远不会过期,一旦进程 panic 或网络中断,就死锁。
- 必须显式传入
time.Duration作为 TTL,例如time.Second * 30 - 更关键的是:加锁命令必须是原子的,不能拆成
SetNX+Expire两步(中间崩溃会导致无过期时间) - Go-Redis v9 的
SetNX方法支持Expiration参数,等价于 Redis 的SET key value NX PX ms,这是安全起点
解锁时为什么不能直接 DEL?
直接 client.Del(ctx, key) 会误删别人持有的锁。比如 A 加锁后业务耗时超长,锁已过期;B 成功加锁;此时 A 才执行 Del,就把 B 的锁干掉了。
- 正确做法:用 Lua 脚本校验
value是否匹配再删,保证“只有加锁者能删锁” - 脚本内容必须是:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end - Go 中通过
client.Eval(ctx, script, []string{key}, val)执行,返回int64判断是否删除成功 - 注意:
val必须是加锁时生成的唯一标识(如 UUID),不能是固定字符串
如何在 Fiber 中安全封装加锁/解锁逻辑?
别把锁逻辑散落在各个 handler 里。建议封装成可复用的中间件或工具函数,且强制传入 context 超时控制。
- 加锁函数签名应类似:
func TryLock(ctx context.Context, client *redis.Client, key, val string, ttl time.Duration) (bool, error) - 内部用
client.SetNX(ctx, key, val, ttl),并检查返回值是否为true - 解锁函数必须接收同一个
val,并用 Lua 执行校验删除 - 务必在 handler 中用
defer确保解锁,例如:defer unlock(ctx, client, key, val) - 不要依赖
defer在 panic 后执行——panic 时可能无法保证解锁,需配合超时锁自动过期兜底
为什么不用 Redisson?它在 Go 里不适用
Redisson 是 Java 生态的成熟方案,自带看门狗续期、RedLock、可重入等特性。但它没有官方 Go 版本,社区移植版(如 redis-go-cluster 衍生项目)普遍缺乏维护、测试覆盖差、API 不稳定。
- Go 生态中更现实的选择是:自己实现最小可行锁(
SET NX PX+ Lua 解锁),或使用github.com/bsm/redislock这类专注锁的小型库 - 若真需要 RedLock(防止单点 Redis 故障),得手动连多个 Redis 实例,按多数派成功才算加锁——复杂度陡增,多数业务用单实例 + 主从高可用就够了
- 真正容易被忽略的点:锁的 key 设计。不能写死
"order_lock",得带业务维度,例如"order_lock:12345",否则不同订单会互相阻塞











