Redis单用SET无法保证分布式锁安全,因缺乏锁续期、崩溃恢复及原子性释放机制;etcd凭借Lease、Txn和Watch原生支持强一致闭环锁,跨语言更可靠。

Redis 实现分布式锁为什么不能只用 SET 命令
因为 SET key value EX seconds NX 看似原子,但没解决锁续期和客户端崩溃后锁残留问题。真实场景下,一个任务执行时间不确定,硬设 TTL 容易提前释放锁;而单纯靠超时自动释放,又可能在业务还没做完时就被别的进程抢走锁。
- 必须用 Lua 脚本保证「判断锁归属 + 删除」原子性,否则
GET+DEL之间存在竞态 - 锁 value 必须是唯一标识(比如 UUID),不能是固定字符串,否则无法区分谁持锁
- Redis 主从异步复制下,主节点写入后宕机未同步,从节点升主,可能出现同一把锁被两个客户端持有
- 推荐用
redis-go/radix或go-redis/redis的Lock方法(底层已封装 Lua),别手写SETNX+EXPIRE
etcd 的 Lease 和 CompareAndDelete 怎么配合用才安全
etcd 天然支持租约(Lease)和事务(Txn),比 Redis 更适合做强一致分布式锁。关键不是“加锁”,而是“加锁 + 续期 + 安全释放”三者闭环。
- 加锁必须用
Put带LeaseID,且设置PrevKV=true检查 key 是否已存在 - 续期不能只调
KeepAlive,要监听LeaseResponseChan,一旦收到ErrLeaseExpired就得主动放弃锁 - 释放锁必须用
Txn:先Compare当前 value 是否等于自己的 token,再Delete,否则可能删掉别人刚拿到的锁 - 注意 etcd v3 的
Get默认不返回版本号,要用WithRev或WithSerializable避免脏读
Redis 和 etcd 锁在重试逻辑上有什么本质区别
Redis 锁失败后通常立即重试(短间隔轮询),而 etcd 更适合监听 key 变更事件,避免无效轮询。这不是风格偏好,而是底层机制决定的。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- Redis 没有原生 watch 机制,客户端只能
GET+SLEEP,容易打爆连接或错过释放信号 - etcd 的
Watch是长连接 + event-driven,锁释放时能秒级通知等待方,但要注意Watch可能断连,需实现 reconnect + resume logic - 两者都必须限制最大重试次数或总等待时间,否则网络分区时可能无限阻塞
- 如果业务允许“最多一次”语义,etcd 的
Lease自动过期 +Watch更可靠;若要求低延迟响应,Redis + 短 TTL + 快速轮询更常见
跨语言调用时,为什么建议统一用 etcd 而非 Redis 做分布式锁
因为 etcd 的 API 是 gRPC 接口 + 明确的错误码(如 rpc error: code = FailedPrecondition),而 Redis 协议是文本协议,不同语言 client 对 SET 返回值解析不一致,比如空字符串、nil、error 类型混用。
立即学习“go语言免费学习笔记(深入)”;
- Redis 的
OK/(nil)/(integer) 1在 Python、Go、Java client 中映射成不同类型,容易漏判加锁失败 - etcd 的
PutResponse和TxnResponse结构稳定,FailedPrecondition明确表示 key 已存在,无需猜含义 - etcd 的 lease TTL 是服务端维护,不受客户端 GC 或卡顿影响;Redis 的 TTL 依赖客户端主动续期,Go 的 goroutine 卡住就等于锁失效
- 不过 etcd 集群运维成本更高,小规模项目用 Redis 更轻量——选型要看你是否真需要线性一致性

















