背景
社区里关于分布式锁的内容已经很多,但几乎清一色是「锁怎么加才安全」:Redisson 看门狗续期在主从切换下丢锁、SETNX + EXPIRE 误删、时钟回拨打穿 TTL、Fencing Token 兜底——这些我都认同,也确实踩过。但我想把争点上移一层,问一个更基础、评审会上吵得最凶的问题:在写并发逻辑时,分布式锁到底该不该是默认选择?
我带团队 review 过一个「扣库存 + 写流水」的链路,第一版方案就是「Redis 分布式锁包住整个方法」,看着最省心。但我们的结论是:能不上就不上。原因很具体——锁把「并发控制」这个本该由数据模型表达的问题,外包给了一个会失效的中间件(主从异步复制、GC 停顿、时钟漂移都能让它失灵,且失灵时往往没有告警,是静默的)。换句话说,锁给的是「大概率互斥」,而资金/库存要的是「确定的线性化」。
常见分歧 / 几种做法
- 默认上分布式锁:实现快、语义直观("同一时间只有一个在处理"),新人好理解。风险:上面说的静默失效,且锁粒度难定——锁太粗吞吐崩,锁太细等于没锁。
- 乐观锁 / 版本号(CAS 自旋重试):
UPDATE ... WHERE status = ? AND version = ?,靠 DB 行的原子性保证正确性,没有"锁持有者"这个会失效的角色。代价:高竞争下重试风暴,且需要业务能表达"期望旧值"。 - 状态机 + 唯一约束:把"互斥"翻译成"状态只能单向流转",
order_status的合法迁移写在 DB 约束里,重复请求天然被挡。适合订单、工单这类有清晰生命周期、副作用可幂等的场景。 - DB 原子操作(自增、
SELECT ... FOR UPDATE、条件 UPDATE):把并发收敛到单行/单表的原子语义里,绕开外部中间件,正确性依赖 DB 而非网络。
我的倾向与理由
我的排序大致是:能用 DB 原子操作 / 状态机 / 乐观锁表达的,就别上分布式锁;分布式锁只留给「操作本身没法用单行原子性表达、又不想引入消息/事务」的窗口——比如一个跨多个写、中间还有外部 RPC 的长临界区,且这段窗口必须串行。理由就一条:锁的正确性依赖于「锁一定不丢」这个假不了也验证不了的前提,而 DB 行的原子性是引擎给的、可测试的。锁是最后手段,不是第一反应。
当然这只是我偏工程实现视角的立场,我承认锁在「锁内逻辑很短、竞争极低、错了也兜得住」的场景里性价比很高,不必过度设计。
想听大家怎么选
想收集几个具体场景的判断,方便对齐认知:
- 你团队里扣库存 / 扣额度这类强一致写,到底走的是锁、
SELECT FOR UPDATE、还是条件 UPDATE?竞争量级大概多少 QPS? - 定时任务防重入(多实例只跑一次)——你们用分布式锁还是别的(比如 DB 抢锁行、lease 表)?锁失效了你们有没有兜底?
- 有没有过「明明上了分布式锁,结果还是双写/超卖」的事故?根因最后定位在哪一层?
- 反过来:你坚持要上分布式锁、且说服不了别人的那种临界区长什么样?
欢迎贴具体的表结构 / 伪代码 / 压测数据,比抽象讨论有用得多。
(顺带引用一篇讲锁失效机制的复盘,机制拆解很清楚,我这边是站在"能不能不上锁"的角度来聊的:见引用。)
0赞
0收藏
0楼层