连载中 17/18

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

2026-06-23 · 4897 阅读 · 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)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10101 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9872 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9293 阅读 · 21 评论 · 287 赞