连载中 17/18

踩坑实录:七个生产事故清单

2026-06-23 · 4895 阅读 · 0 评论 · 0 赞

事故清单摊开看

十六篇的方案讲的是「正着建」,这一篇讲「怎么塌的」。老王把一年内真实发生过、且前文没写透的七个事故整理成清单——每个都是现象、根因、解法三段式。先说共性:七个坑没有一个出在「方案选错了」,全部出在方案没吃透

坑一:消息乱序,注销比注册先到

现象:用户状态机错乱——「已注销」的用户居然有新建的会员权益。排查发现:先发生了「注册」消息消费失败进重试队列,「注销」消息反而先被消费;注册消息重试成功后到达,把已注销的用户又「激活」了。根因:MQ 重试机制天然会造成后发先至,普通消息只有到达序没有业务序。解法:同一用户的消息路由到同一队列用顺序消息(牺牲吞吐),或消息体带业务版本号/操作时间,消费端按序判断——旧消息到达时发现状态已领先,直接幂等丢弃。

坑二:缓存删失败,旧余额挂一天

现象:用户退款成功,APP 上余额却纹丝不动持续了一整天。根因:先更新数据库再删缓存,删除那次 Redis 调用恰好失败,没人重试——缓存里躺着旧余额,读请求持续命中脏数据。解法:缓存删除也要走可靠化——删除失败进重试队列,或者干脆订阅 binlog(Canal)由独立组件删缓存,与业务代码解耦。记住顺序:先更库后删缓存,删除失败要兜底

坑三:大事务撑爆 undo_log

现象:MySQL 磁盘占用飙涨、全库查询变慢,高峰期大量锁等待。根因:一个跑了 20 分钟的大事务(批量导数据没分批),期间整个库的 undo 版本链不能清理,其他事务的旧版本全部堆着——一个大事务,全库陪葬。解法:批量处理分批提交(每批一千条),监控 information_schema 里的长事务并告警,超时主动 kill。长事务是数据库的公敌,不只是它自己的事。

坑四:异步线程里全局事务失踪

现象:@GlobalTransactional 包住的方法里提交线程池跑异步任务,任务里的数据改动完全游离在全局事务之外——主事务回滚了,异步任务写的数据还在。根因:Seata 的全局事务上下文(XID)靠 ThreadLocal 传递,线程池换线程 = 上下文蒸发解法:要么把异步操作移出全局事务(用消息衔接),要么用支持上下文传递的装饰线程池(Seata 提供的包装器)显式传递 XID。团队规范写死:全局事务内禁止裸用线程池。

坑五:顺序消息的假象

现象:用了 RocketMQ 顺序消息,照样乱序。根因:顺序消息只保证单个队列内有序——生产端没按业务键选队列(随机负载均衡发到不同队列),或消费端上了多线程消费,顺序被自己拆了。解法:生产端按业务键(用户 ID)哈希选队列,消费端单线程顺序消费同一队列,两端缺一不可。

坑六:双击下两单

现象:促销当晚,同一用户同一商品两笔订单间隔 80 毫秒。根因:前端按钮没防抖,后端幂等键用的是「请求时间戳」而非业务键——两个请求时间戳不同,幂等形同虚设(呼应第 10 篇:幂等键必须用业务语义键)。解法:前端防抖 + 后端以「用户 + 商品 + 活动」生成幂等键 + 数据库唯一索引三层组合。

坑七:扫描任务扫爆业务库

现象:本地消息表的扫描任务高峰期把业务库 CPU 打满,正常交易跟着遭殃。根因:扫描 SQL 没建 status + create_time 复合索引(全表扫描),且扫描频率未与业务峰谷错峰。解法:复合索引、限制批量、错峰调度,量大的把扫描任务迁到只读从库或独立任务库。第 7 篇提过索引设计,这单事故证明:提过不等于做到。

小结

七坑一表收束:乱序靠版本判断,缓存删除要兜底,大事务分批提交,XID 传递看线程,顺序消息两端配,幂等键用业务键,扫描任务建索引错峰跑。每个坑都在提醒同一件事:方案是骨架,纪律是肉。十八篇走到最后,下一篇收官——把分布式事务的全部家当收进一张作战地图。

503

10 年全栈工程师 · 503咖啡馆主理人

#分布式事务#踩坑#消息乱序#缓存一致性#全局事务

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 4 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞