当前位置: 首页 > news >正文

一次线上事故,我学到了事件驱动架构的5个教训

凌晨3点17分,监控大屏突然一片血红。用户订单"成功"了,但库存没扣、支付没扣、物流没发...上百万的交易数据人间蒸发。排查结果让所有人傻眼:只是一个"无关紧要"的代码改动,让整个事件驱动系统安静地"死亡"了——没有报错,没有日志,就像什么都没发生过。

1 那个让我们全员进制服的夜晚

2025 年 12 月 12 日,双 12 大促。

凌晨 2:47,我正在值班室刷手机,突然手机疯狂震动

🚨 监控告警:订单事件处理量 0

🚨 监控告警:库存事件处理量 0

🚨 监控告警:支付回调处理量 0

🚨 监控告警:物流通知处理量 0

我瞬间清醒,裤子都没穿好就冲进了机房。

发生了什么?

用户下单成功了,订单服务正常返回了。但后续所有流程 - 库存扣减、支付处理、物流通知、短信推送 - 全都消失了。

后来排查发现:只是因为某个同事修改了一行看似无关紧要的日志打印代码。导致事件总线初始化失败。

最恐怖的是:系统没有任何报错

它只是安静的不工作了。

这就是事件驱动(EDA)最可怕的地方

传统架构:某个服务挂了,你会收到报错

事件驱动架构:事件总线挂了,所有人都相安无事

2 消息重复消费 - 用户收到 5 条扣款短信

2.1 发生了什么?

大促高峰期,某用户手机突然收到了 5 条“库存已扣减”的短信。投诉电话被打爆。

2.2 根因分析

消息队列的“至少投递一次”语义 + 消费者没有“去重保护” = 用户收到5条消息

2.3 怎么解决?

核心原则:消费者必须幂等

方案一:数据库唯一键

重复插入? 报错!

方案二:Redis 缓存

已存在? 跳过!

方案三:业务状态机

已经是 RESERVED? 跳过!

3 时间丢失 - 127笔订单“成功”但没付款

3.1 发生了什么?

财务对账时发现:当天 127 笔订单显示“成功”,但数据库里没有任何支付记录。

顺着链路查下去:订单服务确实发布了事件,但支付服务所在的服务器正好在发版重启,消息还没来得及处理,就丢了...

3.2 根因分析

发布时没持久化

消费失败直接丢弃

网络抖动丢消息

3.3 怎么解决?

核心原则:宁可慢,也不能丢

发布时,先持久化,再发送

消费失败,重试 + 死信

死信队列,人工兜底

时间乱序 - 后下的单先发货

4.1 发生了什么?

用户投诉:命名先下的单 A,后下的单 B,为什么 B 先发货了?

排查发现:订单 A 和订单 B 的时间恰好被分发到不同的消费者分区,加上高峰期处理延迟不同,事件处理顺序完全乱套。

4.2 根因分析

事件乱序,大有可能因为消息队列分区策略(轮询)将订单 A 分发到了处理速度慢的消费者所监听的分区,订单 B 分发到了处理速度快的消费者所监听的分区。导致了订单 B 比订单 A 先处理完。

4.3 怎么解决?

核心原则:同一个订单的时间,必须交给同一个消费者处理

方案:分区键

可以通过订单 ID 作为分区键,同一个订单的所有事件,一定进入同一个分区

备选方案消费者端排序

通过事件序列号,消费者收到后,按照 seq 排序再处理

5 事件循环 - CPU 100%罪魁祸首

5.1 发生了什么?

监控显示:某个服务器 CPU 100%,内存持续飙升。日志显示同一个事件被反复处理,每秒数万次。

最终定位:

  1. 订单服务发布“订单已创建”事件
  2. 触发通知服务发送短信
  3. 通知服务发完短信后,发布“通知已发送”事件
  4. 订单服务订阅了这个事件,一激动又发布了“订单已创建”事件
  5. ...无限循环开始

5.2 根因分析

订单服务发布了 order 事件会触发服务通知,服务通知发布了 notify 事件,同时订单服务订阅了 notify 事件,循环启动!每秒数万次,直到服务器挂掉!

5.3 怎么解决?

核心原则:要么预防,要么检测

方案一:状态机控制

订单的状态不可逆:

CREATED ──▶ STOCK_RESERVED ──▶ PAID ──▶ SHIPPED ──▶ COMPLETED

只有 CREATED 状态才能处理 "order.created" 事件

如果已经是 STOCK_RESERVED,收到 "order.created" 直接忽略

方案二:循环检测

最简单的计数器机制就可以做到

计数器机制如下:

方案三:过滤末端事件

定义一些事件不要二次触发其他事件

6 根本不知道卡在哪? 30 分钟的订单

6.1 发生了什么?

一个订单处理了30分钟还没完成。中间经过:订单服务 → 库存服务 → 支付服务 → 物流服务 → 通知服务...

每个服务都说"处理成功了",但整体就是慢。根本不知道卡在哪一步。

传统请求:

请求A ──▶ 服务1 ──▶ 服务2 ──▶ 服务3 ──▶ 响应

(请求到哪了,一目了然)

事件驱动:

事件A ──▶ 队列 ──▶ [?][?][?] ──▶ [?][?]

(事件去哪了,谁都不知道)

找不到问题,才是最可怕的问题

6.3 怎么解决?

核心原则:全链路追踪

TraceID 贯穿始终:

任何一步出问题,都可以用 trace:nb123 搜到所有相关日志

7 避坑 checklist

EDA 保命 checklist:

  1. 消费者实现幂等性了吗? -> 用唯一键 / Redis / 状态机防止重复处理
  2. 消息持久化了吗?-> 先写数据库,再发消息队列
  3. 失败重试 + 死信队列有了吗? -> 超过3次进 DLQ,人工处理
  4. 同一订单的事件进同一分区了吗? -> 用业务 ID 做分区键
  5. 状态机防止事件循环了吗? -> 状态不可逆,跳过非法转换
  6. 全链路追踪配了吗? -> traceId 贯穿所有服务

8 最后的忠告

事件驱动架构是双刃剑

好处坏处
系统松耦合问题难排查
性能高容易丢消息
扩展性强容易损坏
容错性好容易重复

我的建议:

  1. 从小功能开始试 ---别一上来就该核心流程
  2. 监控告警先做好 ---上线前先把追踪搭建好
  3. 逐步前进--- 一个服务一个服务的迁移

宁可慢,也不要丢

凌晨3点的那些教训,现在都变成这篇文章了。觉得有用,点个赞再走?

http://www.jsqmd.com/news/496759/

相关文章:

  • TechWiz LCD 2D应用:单畴IPS仿真
  • leetcode 1409. 查询带键的排列
  • 43| 贴海报
  • 打不开游戏提示缺少D3DCompiler_47.dll文件 分享免费下载
  • 光活化标记试剂 Photobiotin acetate salt,96087-38-6
  • 2026年国内焦磷酸二氢二钠优质直销厂家实力与特点盘点 - 深度智识库
  • 2026年深圳人力资源咨询公司哪家强?靠谱可信赖 覆盖多行业需求 可落地参考 - 深度智识库
  • 国企是否有必要自建即时通讯系统,而不是采购成品?
  • [特殊字符] OpenClaw(小龙虾)CentOS 7 完整安装手册
  • 老码农和你一起学AI系列:语言模型采样方法
  • 成都劳动合同纠纷优质律所推荐指南:成都施工合同纠纷律师事务所/成都物业合同纠纷律师事务所/选择指南 - 优质品牌商家
  • 计院操作系统实验10
  • AI一键图片转3D模型工具TrOSR|离线运行·6G显存即可·附详细图文教程
  • 【靶点筛选样本前处理①】细胞膜蛋白的全流程提取实操:标准化制备及验证
  • 使用NPOI包的时候,报错NPOI.OpenXmlFormats.dll不存在
  • 【程序员转行】大厂狂加码AI,零基础程序员/小白必看,这个风口岗位年薪可达36W
  • 从0实现OnCall基于Python语言框架
  • 2026年全国精密传动设备选型:卓创精锐如何以行星、伺服减速机、换向器破解自动化厂家精度困局 - 深度智识库
  • HCIP-AI-EI Developer V2.5 第四章笔记
  • 2026年盱眙C2驾校怎么选?这份父母放心的择校指南请收好 - 2026年企业推荐榜
  • 无迹卡尔曼滤波器(Unscented Kalman Filter,简称 UKF)
  • 乐迪信息:AI防爆摄像机识别船舶违规明火作业
  • Ubuntu服务器上部署Harbor私有镜像仓库:从零到生产的完整指南
  • 分析牌谱1
  • 【PCIe 验证每日学习・Day13】DLLP 与 ACK/NAK 重传机制基础验证
  • 赋予纸片人生命力:高阶通透肌肤物理折射工作流
  • Android jetpack LiveData (二) 原理篇
  • 嵌入式Linux学习-默认规则
  • 无迹卡尔曼滤波器(UKF)在电池 SOC 估算中的应用
  • [拆解LangChain执行引擎]Agent状态是如何被写入通道的?