sync.mutex 不能用于分布式锁,因其仅限单进程内有效,多实例间互不可见;正确方案需跨进程协调,如 redis(原子set+lua解锁)或etcd(lease续租)。

为什么不能直接用 sync.Mutex 做分布式锁
因为 sync.Mutex 只在单进程内有效,微服务部署在多台机器上时,每个实例都有自己的内存空间,sync.Mutex 完全互不感知。你加的锁,别人根本看不见——这不是锁,是幻觉。
常见错误现象:goroutine 在本地加锁后仍出现并发修改共享资源(比如扣库存超卖、重复发券),日志里看不出异常,但业务数据出错。
真正需要的是跨进程、跨网络、具备租约(lease)和自动释放能力的锁,底层依赖外部协调服务,比如 Redis 或 Etcd。
用 Redis 实现分布式锁要避开 SETNX 的坑
很多人一上来就写 SETNX key value + EXPIRE 两步,这是经典竞态:如果 SETNX 成功但 EXPIRE 失败,锁就永远不释放,服务雪崩。
正确做法是用原子命令:SET key value EX seconds NX。Go 里推荐用 redis.Client.Set 方法(github.com/go-redis/redis/v8),它底层封装了这个原子操作:
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
result, err := rdb.Set(ctx, "lock:order:123", "node-id-abc", &redis.Options{
Expiration: 10 * time.Second,
NX: true,
}).Result()
// result == "OK" 表示抢锁成功
注意三点:
-
NX必须设为true,否则覆盖已有锁 - value 必须全局唯一(建议用 UUID 或
hostname:pid:goroutineID),否则解锁时无法区分所有权 - 过期时间不能拍脑袋定——要大于最长业务执行时间 + 网络抖动余量,但也不能太长(否则故障恢复慢)
解锁必须用 Lua 脚本,不能简单 DEL
如果先 GET 再判断再 DEL,中间可能被别的节点抢先续租或删除,导致误删他人锁。Redis 官方推荐用 Lua 脚本保证原子性:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
Go 中调用方式:
script := redis.NewScript(luaScript)
_, err := script.Run(ctx, rdb, []string{"lock:order:123"}, "node-id-abc").Result()
没返回 1 就说明锁已被别人释放或已过期,当前节点不该再操作;返回 0 不代表错误,只是“没删成”,别重试或 panic。
Etcd 实现更可靠,但要注意 Lease 续约时机
Etcd 的 Lease 天然支持自动过期和 KeepAlive,比 Redis 更适合做强一致性分布式锁,尤其在金融类场景。
关键点:
- 创建 lease 后,必须显式调用
Lease.KeepAlive并持续接收响应,否则 lease 会在 TTL 到期后自动销毁 - 锁 key 要带 revision(用
clientv3.OpPut的WithLease),且抢锁时要用clientv3.OpCompare+clientv3.OpPut的事务操作 - 不要依赖
context.WithTimeout控制抢锁等待——Etcd 的Grant和KeepAlive是长连接,超时应由 lease TTL 和心跳失败共同决定
一个容易被忽略的细节:KeepAlive 的 channel 如果没人读,会阻塞整个 client 连接,最终导致 lease 失效。务必起 goroutine 消费:
keepResp, err := cli.KeepAlive(ctx, leaseID)
go func() {
for range keepResp {
// 心跳成功,什么也不用做
}
}()
锁的可靠性不取决于代码写了多少行,而在于是否堵死了所有竞态窗口——从加锁原子性、解锁原子性,到 lease 续约的实时性,每一步都可能成为单点故障。最危险的不是不会写,而是“看起来能跑通”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










