🔔 本文 8000+ 字深度原创,含 10+ 张架构图和 iPaaS 事件中心、IoT、电商三大实战案例拆解。创作不易,如果对你有帮助,请点赞 👍 收藏 ⭐ 关注 🔥 三连支持,你的认可是我持续输出的最大动力!
写在前面
做了几年分布式系统之后,我发现一个很有意思的现象:
大部分系统的"慢"和"脆",不是因为单个服务写得差,而是因为服务之间"连"得太紧。
你肯定见过这些场景:
- 用户下了一个订单,系统同步调了库存服务、支付服务、通知服务、积分服务------一个超時全链路挂掉
- 老板说"加一个新的下游系统",你打开代码发现要改上游三四个服务的代码
- 系统吞吐量上不去,因为所有操作都串行执行,一个慢查询拖垮全链路
- Webhook 回调超时了,但业务逻辑还在同步执行,连接池被撑爆
这些问题的根源都是同一个------同步调用的紧耦合架构。
事件驱动架构(Event-Driven Architecture,EDA)就是为了解决这类问题而生的。它的核心思想很简单:系统中的组件不直接调用彼此,而是通过"事件"来通信------我做了什么(事件),谁关心谁来听。
本文从事件驱动的本质出发,拆解核心概念、架构拓扑、消息可靠投递、事件溯源、在 iPaaS 平台事件中心的实战落地,以及我在真实项目中踩过的坑。不讲教科书定义,只讲"能落地"的东西。
一、事件驱动架构到底在解决什么问题?
1.1 同步调用的"三重困境"
在传统的 RPC/HTTP 同步调用架构中,系统面临三重困境:
#mermaid-svg-jNQdkODuinvZ4p2X{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jNQdkODuinvZ4p2X .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jNQdkODuinvZ4p2X .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jNQdkODuinvZ4p2X .error-icon{fill:#552222;}#mermaid-svg-jNQdkODuinvZ4p2X .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jNQdkODuinvZ4p2X .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jNQdkODuinvZ4p2X .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jNQdkODuinvZ4p2X .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jNQdkODuinvZ4p2X .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jNQdkODuinvZ4p2X .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jNQdkODuinvZ4p2X .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jNQdkODuinvZ4p2X .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jNQdkODuinvZ4p2X .marker.cross{stroke:#333333;}#mermaid-svg-jNQdkODuinvZ4p2X svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jNQdkODuinvZ4p2X p{margin:0;}#mermaid-svg-jNQdkODuinvZ4p2X .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jNQdkODuinvZ4p2X .cluster-label text{fill:#333;}#mermaid-svg-jNQdkODuinvZ4p2X .cluster-label span{color:#333;}#mermaid-svg-jNQdkODuinvZ4p2X .cluster-label span p{background-color:transparent;}#mermaid-svg-jNQdkODuinvZ4p2X .label text,#mermaid-svg-jNQdkODuinvZ4p2X span{fill:#333;color:#333;}#mermaid-svg-jNQdkODuinvZ4p2X .node rect,#mermaid-svg-jNQdkODuinvZ4p2X .node circle,#mermaid-svg-jNQdkODuinvZ4p2X .node ellipse,#mermaid-svg-jNQdkODuinvZ4p2X .node polygon,#mermaid-svg-jNQdkODuinvZ4p2X .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jNQdkODuinvZ4p2X .rough-node .label text,#mermaid-svg-jNQdkODuinvZ4p2X .node .label text,#mermaid-svg-jNQdkODuinvZ4p2X .image-shape .label,#mermaid-svg-jNQdkODuinvZ4p2X .icon-shape .label{text-anchor:middle;}#mermaid-svg-jNQdkODuinvZ4p2X .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jNQdkODuinvZ4p2X .rough-node .label,#mermaid-svg-jNQdkODuinvZ4p2X .node .label,#mermaid-svg-jNQdkODuinvZ4p2X .image-shape .label,#mermaid-svg-jNQdkODuinvZ4p2X .icon-shape .label{text-align:center;}#mermaid-svg-jNQdkODuinvZ4p2X .node.clickable{cursor:pointer;}#mermaid-svg-jNQdkODuinvZ4p2X .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jNQdkODuinvZ4p2X .arrowheadPath{fill:#333333;}#mermaid-svg-jNQdkODuinvZ4p2X .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jNQdkODuinvZ4p2X .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jNQdkODuinvZ4p2X .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jNQdkODuinvZ4p2X .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jNQdkODuinvZ4p2X .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jNQdkODuinvZ4p2X .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jNQdkODuinvZ4p2X .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jNQdkODuinvZ4p2X .cluster text{fill:#333;}#mermaid-svg-jNQdkODuinvZ4p2X .cluster span{color:#333;}#mermaid-svg-jNQdkODuinvZ4p2X div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jNQdkODuinvZ4p2X .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jNQdkODuinvZ4p2X rect.text{fill:none;stroke-width:0;}#mermaid-svg-jNQdkODuinvZ4p2X .icon-shape,#mermaid-svg-jNQdkODuinvZ4p2X .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jNQdkODuinvZ4p2X .icon-shape p,#mermaid-svg-jNQdkODuinvZ4p2X .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jNQdkODuinvZ4p2X .icon-shape .label rect,#mermaid-svg-jNQdkODuinvZ4p2X .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jNQdkODuinvZ4p2X .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jNQdkODuinvZ4p2X .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jNQdkODuinvZ4p2X :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 困境三:吞吐瓶颈
串行执行
串行执行
串行执行
API入口
处理1
处理2
处理3
困境二:扩展耦合
硬编码调用
硬编码调用
硬编码调用
订单服务
积分服务
风控服务
数据服务
困境一:级联故障
同步调用
同步调用
超时!
订单服务
库存服务
通知服务
全链路阻塞
| 困境 | 表现 | 后果 |
|---|---|---|
| 级联故障 | 下游超时,上游连接堆积 | 一个慢服务拖垮全系统 |
| 扩展耦合 | 加一个下游要改上游代码 | 系统无法独立演进 |
| 吞吐瓶颈 | 串行同步执行 | 整体延迟 = 各环节延迟之和 |
1.2 事件驱动的核心主张
事件驱动架构用一句话概括:系统中的事件是核心,组件通过异步产生和响应事件来协作,解耦生产者和消费者。
#mermaid-svg-RURz89mARnW2Wv71{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RURz89mARnW2Wv71 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RURz89mARnW2Wv71 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RURz89mARnW2Wv71 .error-icon{fill:#552222;}#mermaid-svg-RURz89mARnW2Wv71 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RURz89mARnW2Wv71 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RURz89mARnW2Wv71 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RURz89mARnW2Wv71 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RURz89mARnW2Wv71 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RURz89mARnW2Wv71 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RURz89mARnW2Wv71 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RURz89mARnW2Wv71 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RURz89mARnW2Wv71 .marker.cross{stroke:#333333;}#mermaid-svg-RURz89mARnW2Wv71 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RURz89mARnW2Wv71 p{margin:0;}#mermaid-svg-RURz89mARnW2Wv71 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RURz89mARnW2Wv71 .cluster-label text{fill:#333;}#mermaid-svg-RURz89mARnW2Wv71 .cluster-label span{color:#333;}#mermaid-svg-RURz89mARnW2Wv71 .cluster-label span p{background-color:transparent;}#mermaid-svg-RURz89mARnW2Wv71 .label text,#mermaid-svg-RURz89mARnW2Wv71 span{fill:#333;color:#333;}#mermaid-svg-RURz89mARnW2Wv71 .node rect,#mermaid-svg-RURz89mARnW2Wv71 .node circle,#mermaid-svg-RURz89mARnW2Wv71 .node ellipse,#mermaid-svg-RURz89mARnW2Wv71 .node polygon,#mermaid-svg-RURz89mARnW2Wv71 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RURz89mARnW2Wv71 .rough-node .label text,#mermaid-svg-RURz89mARnW2Wv71 .node .label text,#mermaid-svg-RURz89mARnW2Wv71 .image-shape .label,#mermaid-svg-RURz89mARnW2Wv71 .icon-shape .label{text-anchor:middle;}#mermaid-svg-RURz89mARnW2Wv71 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RURz89mARnW2Wv71 .rough-node .label,#mermaid-svg-RURz89mARnW2Wv71 .node .label,#mermaid-svg-RURz89mARnW2Wv71 .image-shape .label,#mermaid-svg-RURz89mARnW2Wv71 .icon-shape .label{text-align:center;}#mermaid-svg-RURz89mARnW2Wv71 .node.clickable{cursor:pointer;}#mermaid-svg-RURz89mARnW2Wv71 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RURz89mARnW2Wv71 .arrowheadPath{fill:#333333;}#mermaid-svg-RURz89mARnW2Wv71 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RURz89mARnW2Wv71 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RURz89mARnW2Wv71 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RURz89mARnW2Wv71 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RURz89mARnW2Wv71 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RURz89mARnW2Wv71 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RURz89mARnW2Wv71 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RURz89mARnW2Wv71 .cluster text{fill:#333;}#mermaid-svg-RURz89mARnW2Wv71 .cluster span{color:#333;}#mermaid-svg-RURz89mARnW2Wv71 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RURz89mARnW2Wv71 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RURz89mARnW2Wv71 rect.text{fill:none;stroke-width:0;}#mermaid-svg-RURz89mARnW2Wv71 .icon-shape,#mermaid-svg-RURz89mARnW2Wv71 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RURz89mARnW2Wv71 .icon-shape p,#mermaid-svg-RURz89mARnW2Wv71 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RURz89mARnW2Wv71 .icon-shape .label rect,#mermaid-svg-RURz89mARnW2Wv71 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RURz89mARnW2Wv71 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RURz89mARnW2Wv71 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RURz89mARnW2Wv71 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 发布事件
发布事件
订阅分发
订阅分发
订阅分发
生产者A
事件总线
Event Broker
生产者B
消费者1
消费者2
消费者3
三个核心主张:
-
解耦(Decoupling):生产者不知道消费者的存在,消费者不知道事件从哪来。加一个新的消费者?注册一个订阅就行,生产者的代码一行不改。
-
异步(Asynchrony):生产者发出事件就返回,不等消费者处理完。整体延迟从"串行累加"变成"并行处理"。
-
弹性(Resilience):消费者挂了?事件在消息队列里等着,恢复后继续消费。不会因为一个节点故障导致全链路崩溃。
1.3 事件驱动适合什么系统?
| 系统类型 | 是否适合 EDA | 原因 |
|---|---|---|
| CRUD 管理后台 | ❌ 不适合 | 请求-响应模型,同步调用更直观 |
| 报表查询系统 | ❌ 不适合 | 用户需要即时结果,异步无意义 |
| IoT 物联网平台 | ✅ 非常适合 | 海量设备异步上报,高吞吐实时处理 |
| 实时推荐系统 | ✅ 非常适合 | 用户行为事件流 → 实时特征计算 → 推荐更新 |
| iPaaS 集成平台 | ✅ 非常适合 | Webhook/CDC/定时触发 → 异步流程编排 |
| 电商交易系统 | ✅ 部分适合 | 下单后的库存、通知、积分适合事件驱动 |
| 金融交易系统 | ⚠️ 谨慎使用 | 高吞吐场景适合,但强一致性场景需额外设计 |
判断标准:你的系统是否存在"一个操作触发多个下游"、"需要高吞吐异步处理"、"下游数量经常变化"这三个特征中的至少一个。
二、事件驱动架构的核心概念
2.1 事件(Event)
事件是"已经发生的事实"------不可变的、轻量级的、带有时间戳的状态变更记录。
#mermaid-svg-40snaFmQ2a0nh5tM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-40snaFmQ2a0nh5tM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-40snaFmQ2a0nh5tM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-40snaFmQ2a0nh5tM .error-icon{fill:#552222;}#mermaid-svg-40snaFmQ2a0nh5tM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-40snaFmQ2a0nh5tM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-40snaFmQ2a0nh5tM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-40snaFmQ2a0nh5tM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-40snaFmQ2a0nh5tM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-40snaFmQ2a0nh5tM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-40snaFmQ2a0nh5tM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-40snaFmQ2a0nh5tM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-40snaFmQ2a0nh5tM .marker.cross{stroke:#333333;}#mermaid-svg-40snaFmQ2a0nh5tM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-40snaFmQ2a0nh5tM p{margin:0;}#mermaid-svg-40snaFmQ2a0nh5tM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-40snaFmQ2a0nh5tM .cluster-label text{fill:#333;}#mermaid-svg-40snaFmQ2a0nh5tM .cluster-label span{color:#333;}#mermaid-svg-40snaFmQ2a0nh5tM .cluster-label span p{background-color:transparent;}#mermaid-svg-40snaFmQ2a0nh5tM .label text,#mermaid-svg-40snaFmQ2a0nh5tM span{fill:#333;color:#333;}#mermaid-svg-40snaFmQ2a0nh5tM .node rect,#mermaid-svg-40snaFmQ2a0nh5tM .node circle,#mermaid-svg-40snaFmQ2a0nh5tM .node ellipse,#mermaid-svg-40snaFmQ2a0nh5tM .node polygon,#mermaid-svg-40snaFmQ2a0nh5tM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-40snaFmQ2a0nh5tM .rough-node .label text,#mermaid-svg-40snaFmQ2a0nh5tM .node .label text,#mermaid-svg-40snaFmQ2a0nh5tM .image-shape .label,#mermaid-svg-40snaFmQ2a0nh5tM .icon-shape .label{text-anchor:middle;}#mermaid-svg-40snaFmQ2a0nh5tM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-40snaFmQ2a0nh5tM .rough-node .label,#mermaid-svg-40snaFmQ2a0nh5tM .node .label,#mermaid-svg-40snaFmQ2a0nh5tM .image-shape .label,#mermaid-svg-40snaFmQ2a0nh5tM .icon-shape .label{text-align:center;}#mermaid-svg-40snaFmQ2a0nh5tM .node.clickable{cursor:pointer;}#mermaid-svg-40snaFmQ2a0nh5tM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-40snaFmQ2a0nh5tM .arrowheadPath{fill:#333333;}#mermaid-svg-40snaFmQ2a0nh5tM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-40snaFmQ2a0nh5tM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-40snaFmQ2a0nh5tM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-40snaFmQ2a0nh5tM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-40snaFmQ2a0nh5tM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-40snaFmQ2a0nh5tM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-40snaFmQ2a0nh5tM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-40snaFmQ2a0nh5tM .cluster text{fill:#333;}#mermaid-svg-40snaFmQ2a0nh5tM .cluster span{color:#333;}#mermaid-svg-40snaFmQ2a0nh5tM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-40snaFmQ2a0nh5tM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-40snaFmQ2a0nh5tM rect.text{fill:none;stroke-width:0;}#mermaid-svg-40snaFmQ2a0nh5tM .icon-shape,#mermaid-svg-40snaFmQ2a0nh5tM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-40snaFmQ2a0nh5tM .icon-shape p,#mermaid-svg-40snaFmQ2a0nh5tM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-40snaFmQ2a0nh5tM .icon-shape .label rect,#mermaid-svg-40snaFmQ2a0nh5tM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-40snaFmQ2a0nh5tM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-40snaFmQ2a0nh5tM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-40snaFmQ2a0nh5tM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 事件的本质
事件名(过去式)
OrderCreated
事件载荷(Payload)
订单ID/金额/商品列表
元数据(Metadata)
时间戳/来源/追踪ID
事件和命令(Command)的区别:
| 维度 | 事件(Event) | 命令(Command) |
|---|---|---|
| 语义 | "已经发生了什么" | "请去做什么" |
| 命名 | 过去式:OrderCreated |
祈使句:CreateOrder |
| 目标 | 无特定目标,广播给所有订阅者 | 有明确目标,发给指定处理器 |
| 耦合度 | 低(生产者不知道谁在消费) | 高(调用方知道被调用方) |
| 失败处理 | 消费者各自重试 | 调用方重试或回滚 |
java
// 事件:已经发生的事实
public record OrderCreatedEvent(
String orderId,
String userId,
BigDecimal totalAmount,
List<OrderItem> items,
Instant createdAt // 时间戳
) {}
// 命令:要求执行的操作
public record CreateOrderCommand(
String userId,
List<OrderItem> items
) {}
2.2 事件通道(Event Channel)
事件从生产者到消费者的传输管道。根据传输方式不同,分为三种:
#mermaid-svg-TknrVCHhrLAf2QZD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TknrVCHhrLAf2QZD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TknrVCHhrLAf2QZD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TknrVCHhrLAf2QZD .error-icon{fill:#552222;}#mermaid-svg-TknrVCHhrLAf2QZD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TknrVCHhrLAf2QZD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TknrVCHhrLAf2QZD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TknrVCHhrLAf2QZD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TknrVCHhrLAf2QZD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TknrVCHhrLAf2QZD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TknrVCHhrLAf2QZD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TknrVCHhrLAf2QZD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TknrVCHhrLAf2QZD .marker.cross{stroke:#333333;}#mermaid-svg-TknrVCHhrLAf2QZD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TknrVCHhrLAf2QZD p{margin:0;}#mermaid-svg-TknrVCHhrLAf2QZD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TknrVCHhrLAf2QZD .cluster-label text{fill:#333;}#mermaid-svg-TknrVCHhrLAf2QZD .cluster-label span{color:#333;}#mermaid-svg-TknrVCHhrLAf2QZD .cluster-label span p{background-color:transparent;}#mermaid-svg-TknrVCHhrLAf2QZD .label text,#mermaid-svg-TknrVCHhrLAf2QZD span{fill:#333;color:#333;}#mermaid-svg-TknrVCHhrLAf2QZD .node rect,#mermaid-svg-TknrVCHhrLAf2QZD .node circle,#mermaid-svg-TknrVCHhrLAf2QZD .node ellipse,#mermaid-svg-TknrVCHhrLAf2QZD .node polygon,#mermaid-svg-TknrVCHhrLAf2QZD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TknrVCHhrLAf2QZD .rough-node .label text,#mermaid-svg-TknrVCHhrLAf2QZD .node .label text,#mermaid-svg-TknrVCHhrLAf2QZD .image-shape .label,#mermaid-svg-TknrVCHhrLAf2QZD .icon-shape .label{text-anchor:middle;}#mermaid-svg-TknrVCHhrLAf2QZD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TknrVCHhrLAf2QZD .rough-node .label,#mermaid-svg-TknrVCHhrLAf2QZD .node .label,#mermaid-svg-TknrVCHhrLAf2QZD .image-shape .label,#mermaid-svg-TknrVCHhrLAf2QZD .icon-shape .label{text-align:center;}#mermaid-svg-TknrVCHhrLAf2QZD .node.clickable{cursor:pointer;}#mermaid-svg-TknrVCHhrLAf2QZD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TknrVCHhrLAf2QZD .arrowheadPath{fill:#333333;}#mermaid-svg-TknrVCHhrLAf2QZD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TknrVCHhrLAf2QZD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TknrVCHhrLAf2QZD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TknrVCHhrLAf2QZD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TknrVCHhrLAf2QZD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TknrVCHhrLAf2QZD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TknrVCHhrLAf2QZD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TknrVCHhrLAf2QZD .cluster text{fill:#333;}#mermaid-svg-TknrVCHhrLAf2QZD .cluster span{color:#333;}#mermaid-svg-TknrVCHhrLAf2QZD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TknrVCHhrLAf2QZD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TknrVCHhrLAf2QZD rect.text{fill:none;stroke-width:0;}#mermaid-svg-TknrVCHhrLAf2QZD .icon-shape,#mermaid-svg-TknrVCHhrLAf2QZD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TknrVCHhrLAf2QZD .icon-shape p,#mermaid-svg-TknrVCHhrLAf2QZD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TknrVCHhrLAf2QZD .icon-shape .label rect,#mermaid-svg-TknrVCHhrLAf2QZD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TknrVCHhrLAf2QZD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TknrVCHhrLAf2QZD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TknrVCHhrLAf2QZD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 请求-响应通道
请求事件
响应事件
请求者
请求队列
响应者
响应队列
发布-订阅通道
生产者
Topic
消费者A
消费者B
消费者C
点对点通道
生产者
队列
唯一消费者
| 通道类型 | 典型实现 | 适用场景 |
|---|---|---|
| 点对点 | RabbitMQ Queue、RocketMQ 普通消息 | 任务分发,一个事件只需一个消费者处理 |
| 发布-订阅 | Kafka Topic、RocketMQ Topic | 事件广播,一个事件需要多个消费者各自处理 |
| 请求-响应 | NATS Request-Reply | 需要异步获取结果的场景 |
2.3 事件代理(Event Broker)
事件代理是事件驱动架构的"中枢神经"------所有事件都经过它来路由和分发。
#mermaid-svg-8qcp7oeZDGL4pONH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8qcp7oeZDGL4pONH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8qcp7oeZDGL4pONH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8qcp7oeZDGL4pONH .error-icon{fill:#552222;}#mermaid-svg-8qcp7oeZDGL4pONH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8qcp7oeZDGL4pONH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8qcp7oeZDGL4pONH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8qcp7oeZDGL4pONH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8qcp7oeZDGL4pONH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8qcp7oeZDGL4pONH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8qcp7oeZDGL4pONH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8qcp7oeZDGL4pONH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8qcp7oeZDGL4pONH .marker.cross{stroke:#333333;}#mermaid-svg-8qcp7oeZDGL4pONH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8qcp7oeZDGL4pONH p{margin:0;}#mermaid-svg-8qcp7oeZDGL4pONH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8qcp7oeZDGL4pONH .cluster-label text{fill:#333;}#mermaid-svg-8qcp7oeZDGL4pONH .cluster-label span{color:#333;}#mermaid-svg-8qcp7oeZDGL4pONH .cluster-label span p{background-color:transparent;}#mermaid-svg-8qcp7oeZDGL4pONH .label text,#mermaid-svg-8qcp7oeZDGL4pONH span{fill:#333;color:#333;}#mermaid-svg-8qcp7oeZDGL4pONH .node rect,#mermaid-svg-8qcp7oeZDGL4pONH .node circle,#mermaid-svg-8qcp7oeZDGL4pONH .node ellipse,#mermaid-svg-8qcp7oeZDGL4pONH .node polygon,#mermaid-svg-8qcp7oeZDGL4pONH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8qcp7oeZDGL4pONH .rough-node .label text,#mermaid-svg-8qcp7oeZDGL4pONH .node .label text,#mermaid-svg-8qcp7oeZDGL4pONH .image-shape .label,#mermaid-svg-8qcp7oeZDGL4pONH .icon-shape .label{text-anchor:middle;}#mermaid-svg-8qcp7oeZDGL4pONH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8qcp7oeZDGL4pONH .rough-node .label,#mermaid-svg-8qcp7oeZDGL4pONH .node .label,#mermaid-svg-8qcp7oeZDGL4pONH .image-shape .label,#mermaid-svg-8qcp7oeZDGL4pONH .icon-shape .label{text-align:center;}#mermaid-svg-8qcp7oeZDGL4pONH .node.clickable{cursor:pointer;}#mermaid-svg-8qcp7oeZDGL4pONH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8qcp7oeZDGL4pONH .arrowheadPath{fill:#333333;}#mermaid-svg-8qcp7oeZDGL4pONH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8qcp7oeZDGL4pONH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8qcp7oeZDGL4pONH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8qcp7oeZDGL4pONH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8qcp7oeZDGL4pONH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8qcp7oeZDGL4pONH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8qcp7oeZDGL4pONH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8qcp7oeZDGL4pONH .cluster text{fill:#333;}#mermaid-svg-8qcp7oeZDGL4pONH .cluster span{color:#333;}#mermaid-svg-8qcp7oeZDGL4pONH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8qcp7oeZDGL4pONH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8qcp7oeZDGL4pONH rect.text{fill:none;stroke-width:0;}#mermaid-svg-8qcp7oeZDGL4pONH .icon-shape,#mermaid-svg-8qcp7oeZDGL4pONH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8qcp7oeZDGL4pONH .icon-shape p,#mermaid-svg-8qcp7oeZDGL4pONH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8qcp7oeZDGL4pONH .icon-shape .label rect,#mermaid-svg-8qcp7oeZDGL4pONH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8qcp7oeZDGL4pONH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8qcp7oeZDGL4pONH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8qcp7oeZDGL4pONH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 事件代理
事件接收
协议适配/鉴权
事件存储
持久化/回放
事件路由
Topic/Tag过滤
事件投递
推送/拉取
生产者1
生产者2
消费者1
消费者2
消费者3
主流消息中间件的选型对比:
| 中间件 | 吞吐量 | 延迟 | 消息模型 | 适用场景 |
|---|---|---|---|---|
| RocketMQ | 十万级/秒 | 毫秒级 | 发布-订阅+点对点 | 电商/金融/企业集成 |
| Kafka | 百万级/秒 | 毫秒级 | 发布-订阅(Log) | 日志/大数据/IoT |
| RabbitMQ | 万级/秒 | 微秒级 | 灵活路由 | 企业内部集成/任务队列 |
| Pulsar | 百万级/秒 | 毫秒级 | 多租户+分层存储 | 超大规模多租户场景 |
我们在 iPaaS 平台选择了 RocketMQ------它的事务消息、顺序消息、延时消息等企业级特性,非常契合集成平台的场景。
2.4 事件处理的三种模式
#mermaid-svg-HhTUkAXIssch2uaE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HhTUkAXIssch2uaE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HhTUkAXIssch2uaE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HhTUkAXIssch2uaE .error-icon{fill:#552222;}#mermaid-svg-HhTUkAXIssch2uaE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HhTUkAXIssch2uaE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HhTUkAXIssch2uaE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HhTUkAXIssch2uaE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HhTUkAXIssch2uaE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HhTUkAXIssch2uaE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HhTUkAXIssch2uaE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HhTUkAXIssch2uaE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HhTUkAXIssch2uaE .marker.cross{stroke:#333333;}#mermaid-svg-HhTUkAXIssch2uaE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HhTUkAXIssch2uaE p{margin:0;}#mermaid-svg-HhTUkAXIssch2uaE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HhTUkAXIssch2uaE .cluster-label text{fill:#333;}#mermaid-svg-HhTUkAXIssch2uaE .cluster-label span{color:#333;}#mermaid-svg-HhTUkAXIssch2uaE .cluster-label span p{background-color:transparent;}#mermaid-svg-HhTUkAXIssch2uaE .label text,#mermaid-svg-HhTUkAXIssch2uaE span{fill:#333;color:#333;}#mermaid-svg-HhTUkAXIssch2uaE .node rect,#mermaid-svg-HhTUkAXIssch2uaE .node circle,#mermaid-svg-HhTUkAXIssch2uaE .node ellipse,#mermaid-svg-HhTUkAXIssch2uaE .node polygon,#mermaid-svg-HhTUkAXIssch2uaE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HhTUkAXIssch2uaE .rough-node .label text,#mermaid-svg-HhTUkAXIssch2uaE .node .label text,#mermaid-svg-HhTUkAXIssch2uaE .image-shape .label,#mermaid-svg-HhTUkAXIssch2uaE .icon-shape .label{text-anchor:middle;}#mermaid-svg-HhTUkAXIssch2uaE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HhTUkAXIssch2uaE .rough-node .label,#mermaid-svg-HhTUkAXIssch2uaE .node .label,#mermaid-svg-HhTUkAXIssch2uaE .image-shape .label,#mermaid-svg-HhTUkAXIssch2uaE .icon-shape .label{text-align:center;}#mermaid-svg-HhTUkAXIssch2uaE .node.clickable{cursor:pointer;}#mermaid-svg-HhTUkAXIssch2uaE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HhTUkAXIssch2uaE .arrowheadPath{fill:#333333;}#mermaid-svg-HhTUkAXIssch2uaE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HhTUkAXIssch2uaE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HhTUkAXIssch2uaE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HhTUkAXIssch2uaE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HhTUkAXIssch2uaE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HhTUkAXIssch2uaE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HhTUkAXIssch2uaE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HhTUkAXIssch2uaE .cluster text{fill:#333;}#mermaid-svg-HhTUkAXIssch2uaE .cluster span{color:#333;}#mermaid-svg-HhTUkAXIssch2uaE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HhTUkAXIssch2uaE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HhTUkAXIssch2uaE rect.text{fill:none;stroke-width:0;}#mermaid-svg-HhTUkAXIssch2uaE .icon-shape,#mermaid-svg-HhTUkAXIssch2uaE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HhTUkAXIssch2uaE .icon-shape p,#mermaid-svg-HhTUkAXIssch2uaE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HhTUkAXIssch2uaE .icon-shape .label rect,#mermaid-svg-HhTUkAXIssch2uaE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HhTUkAXIssch2uaE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HhTUkAXIssch2uaE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HhTUkAXIssch2uaE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Saga 编排
成功
失败
订单事件
步骤1: 扣库存
步骤2: 扣款
补偿: 恢复库存
复杂事件处理(CEP)
事件流
CEP引擎
模式匹配/时间窗口
触发动作
简单事件处理
事件
单处理器
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 简单事件处理 | 一个事件触发一个处理器 | 大多数业务场景 |
| 复杂事件处理(CEP) | 多个事件组合满足模式后触发 | 风控/告警/IoT 异常检测 |
| Saga 编排 | 长事务拆成多个事件驱动的步骤 | 跨服务的分布式事务 |
三、事件驱动架构的四种拓扑
3.1 中介者拓扑(Mediator Topology)
#mermaid-svg-CenZqxHc5n47vvBb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CenZqxHc5n47vvBb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CenZqxHc5n47vvBb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CenZqxHc5n47vvBb .error-icon{fill:#552222;}#mermaid-svg-CenZqxHc5n47vvBb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CenZqxHc5n47vvBb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CenZqxHc5n47vvBb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CenZqxHc5n47vvBb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CenZqxHc5n47vvBb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CenZqxHc5n47vvBb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CenZqxHc5n47vvBb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CenZqxHc5n47vvBb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CenZqxHc5n47vvBb .marker.cross{stroke:#333333;}#mermaid-svg-CenZqxHc5n47vvBb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CenZqxHc5n47vvBb p{margin:0;}#mermaid-svg-CenZqxHc5n47vvBb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CenZqxHc5n47vvBb .cluster-label text{fill:#333;}#mermaid-svg-CenZqxHc5n47vvBb .cluster-label span{color:#333;}#mermaid-svg-CenZqxHc5n47vvBb .cluster-label span p{background-color:transparent;}#mermaid-svg-CenZqxHc5n47vvBb .label text,#mermaid-svg-CenZqxHc5n47vvBb span{fill:#333;color:#333;}#mermaid-svg-CenZqxHc5n47vvBb .node rect,#mermaid-svg-CenZqxHc5n47vvBb .node circle,#mermaid-svg-CenZqxHc5n47vvBb .node ellipse,#mermaid-svg-CenZqxHc5n47vvBb .node polygon,#mermaid-svg-CenZqxHc5n47vvBb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CenZqxHc5n47vvBb .rough-node .label text,#mermaid-svg-CenZqxHc5n47vvBb .node .label text,#mermaid-svg-CenZqxHc5n47vvBb .image-shape .label,#mermaid-svg-CenZqxHc5n47vvBb .icon-shape .label{text-anchor:middle;}#mermaid-svg-CenZqxHc5n47vvBb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CenZqxHc5n47vvBb .rough-node .label,#mermaid-svg-CenZqxHc5n47vvBb .node .label,#mermaid-svg-CenZqxHc5n47vvBb .image-shape .label,#mermaid-svg-CenZqxHc5n47vvBb .icon-shape .label{text-align:center;}#mermaid-svg-CenZqxHc5n47vvBb .node.clickable{cursor:pointer;}#mermaid-svg-CenZqxHc5n47vvBb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CenZqxHc5n47vvBb .arrowheadPath{fill:#333333;}#mermaid-svg-CenZqxHc5n47vvBb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CenZqxHc5n47vvBb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CenZqxHc5n47vvBb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CenZqxHc5n47vvBb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CenZqxHc5n47vvBb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CenZqxHc5n47vvBb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CenZqxHc5n47vvBb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CenZqxHc5n47vvBb .cluster text{fill:#333;}#mermaid-svg-CenZqxHc5n47vvBb .cluster span{color:#333;}#mermaid-svg-CenZqxHc5n47vvBb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CenZqxHc5n47vvBb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CenZqxHc5n47vvBb rect.text{fill:none;stroke-width:0;}#mermaid-svg-CenZqxHc5n47vvBb .icon-shape,#mermaid-svg-CenZqxHc5n47vvBb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CenZqxHc5n47vvBb .icon-shape p,#mermaid-svg-CenZqxHc5n47vvBb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CenZqxHc5n47vvBb .icon-shape .label rect,#mermaid-svg-CenZqxHc5n47vvBb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CenZqxHc5n47vvBb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CenZqxHc5n47vvBb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CenZqxHc5n47vvBb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 中介者拓扑
步骤1
事件A完成
步骤2
事件B完成
步骤3
初始事件
事件中介者
编排步骤
事件处理器A
事件处理器B
事件处理器C
特点:有一个"指挥官"决定事件处理的顺序。类似流程引擎------事件到达后,由中介者编排处理步骤。
适用场景:业务流程有明确的步骤顺序,如订单处理(校验→扣库存→支付→发货)。
3.2 代理者拓扑(Broker Topology)
#mermaid-svg-HBdIEfMzQjYjSlpx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HBdIEfMzQjYjSlpx .error-icon{fill:#552222;}#mermaid-svg-HBdIEfMzQjYjSlpx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HBdIEfMzQjYjSlpx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HBdIEfMzQjYjSlpx .marker.cross{stroke:#333333;}#mermaid-svg-HBdIEfMzQjYjSlpx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HBdIEfMzQjYjSlpx p{margin:0;}#mermaid-svg-HBdIEfMzQjYjSlpx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HBdIEfMzQjYjSlpx .cluster-label text{fill:#333;}#mermaid-svg-HBdIEfMzQjYjSlpx .cluster-label span{color:#333;}#mermaid-svg-HBdIEfMzQjYjSlpx .cluster-label span p{background-color:transparent;}#mermaid-svg-HBdIEfMzQjYjSlpx .label text,#mermaid-svg-HBdIEfMzQjYjSlpx span{fill:#333;color:#333;}#mermaid-svg-HBdIEfMzQjYjSlpx .node rect,#mermaid-svg-HBdIEfMzQjYjSlpx .node circle,#mermaid-svg-HBdIEfMzQjYjSlpx .node ellipse,#mermaid-svg-HBdIEfMzQjYjSlpx .node polygon,#mermaid-svg-HBdIEfMzQjYjSlpx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HBdIEfMzQjYjSlpx .rough-node .label text,#mermaid-svg-HBdIEfMzQjYjSlpx .node .label text,#mermaid-svg-HBdIEfMzQjYjSlpx .image-shape .label,#mermaid-svg-HBdIEfMzQjYjSlpx .icon-shape .label{text-anchor:middle;}#mermaid-svg-HBdIEfMzQjYjSlpx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HBdIEfMzQjYjSlpx .rough-node .label,#mermaid-svg-HBdIEfMzQjYjSlpx .node .label,#mermaid-svg-HBdIEfMzQjYjSlpx .image-shape .label,#mermaid-svg-HBdIEfMzQjYjSlpx .icon-shape .label{text-align:center;}#mermaid-svg-HBdIEfMzQjYjSlpx .node.clickable{cursor:pointer;}#mermaid-svg-HBdIEfMzQjYjSlpx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HBdIEfMzQjYjSlpx .arrowheadPath{fill:#333333;}#mermaid-svg-HBdIEfMzQjYjSlpx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HBdIEfMzQjYjSlpx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HBdIEfMzQjYjSlpx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HBdIEfMzQjYjSlpx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HBdIEfMzQjYjSlpx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HBdIEfMzQjYjSlpx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HBdIEfMzQjYjSlpx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HBdIEfMzQjYjSlpx .cluster text{fill:#333;}#mermaid-svg-HBdIEfMzQjYjSlpx .cluster span{color:#333;}#mermaid-svg-HBdIEfMzQjYjSlpx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HBdIEfMzQjYjSlpx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HBdIEfMzQjYjSlpx rect.text{fill:none;stroke-width:0;}#mermaid-svg-HBdIEfMzQjYjSlpx .icon-shape,#mermaid-svg-HBdIEfMzQjYjSlpx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HBdIEfMzQjYjSlpx .icon-shape p,#mermaid-svg-HBdIEfMzQjYjSlpx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HBdIEfMzQjYjSlpx .icon-shape .label rect,#mermaid-svg-HBdIEfMzQjYjSlpx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HBdIEfMzQjYjSlpx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HBdIEfMzQjYjSlpx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HBdIEfMzQjYjSlpx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 代理者拓扑
产生事件B
产生事件C
事件A
处理器1
处理器2
处理器3
特点:没有中心编排者,每个处理器消费事件后产生新事件,下一个处理器自动接力。像"接力赛"------每一棒跑完把接力棒传下去。
适用场景:处理链路比较固定、步骤间有自然的数据流转关系。
3.3 广播拓扑(Broadcast Topology)
#mermaid-svg-AqDYtNo2DK1Brlyn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AqDYtNo2DK1Brlyn .error-icon{fill:#552222;}#mermaid-svg-AqDYtNo2DK1Brlyn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AqDYtNo2DK1Brlyn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AqDYtNo2DK1Brlyn .marker.cross{stroke:#333333;}#mermaid-svg-AqDYtNo2DK1Brlyn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AqDYtNo2DK1Brlyn p{margin:0;}#mermaid-svg-AqDYtNo2DK1Brlyn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AqDYtNo2DK1Brlyn .cluster-label text{fill:#333;}#mermaid-svg-AqDYtNo2DK1Brlyn .cluster-label span{color:#333;}#mermaid-svg-AqDYtNo2DK1Brlyn .cluster-label span p{background-color:transparent;}#mermaid-svg-AqDYtNo2DK1Brlyn .label text,#mermaid-svg-AqDYtNo2DK1Brlyn span{fill:#333;color:#333;}#mermaid-svg-AqDYtNo2DK1Brlyn .node rect,#mermaid-svg-AqDYtNo2DK1Brlyn .node circle,#mermaid-svg-AqDYtNo2DK1Brlyn .node ellipse,#mermaid-svg-AqDYtNo2DK1Brlyn .node polygon,#mermaid-svg-AqDYtNo2DK1Brlyn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AqDYtNo2DK1Brlyn .rough-node .label text,#mermaid-svg-AqDYtNo2DK1Brlyn .node .label text,#mermaid-svg-AqDYtNo2DK1Brlyn .image-shape .label,#mermaid-svg-AqDYtNo2DK1Brlyn .icon-shape .label{text-anchor:middle;}#mermaid-svg-AqDYtNo2DK1Brlyn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AqDYtNo2DK1Brlyn .rough-node .label,#mermaid-svg-AqDYtNo2DK1Brlyn .node .label,#mermaid-svg-AqDYtNo2DK1Brlyn .image-shape .label,#mermaid-svg-AqDYtNo2DK1Brlyn .icon-shape .label{text-align:center;}#mermaid-svg-AqDYtNo2DK1Brlyn .node.clickable{cursor:pointer;}#mermaid-svg-AqDYtNo2DK1Brlyn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AqDYtNo2DK1Brlyn .arrowheadPath{fill:#333333;}#mermaid-svg-AqDYtNo2DK1Brlyn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AqDYtNo2DK1Brlyn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AqDYtNo2DK1Brlyn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AqDYtNo2DK1Brlyn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AqDYtNo2DK1Brlyn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AqDYtNo2DK1Brlyn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AqDYtNo2DK1Brlyn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AqDYtNo2DK1Brlyn .cluster text{fill:#333;}#mermaid-svg-AqDYtNo2DK1Brlyn .cluster span{color:#333;}#mermaid-svg-AqDYtNo2DK1Brlyn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AqDYtNo2DK1Brlyn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AqDYtNo2DK1Brlyn rect.text{fill:none;stroke-width:0;}#mermaid-svg-AqDYtNo2DK1Brlyn .icon-shape,#mermaid-svg-AqDYtNo2DK1Brlyn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AqDYtNo2DK1Brlyn .icon-shape p,#mermaid-svg-AqDYtNo2DK1Brlyn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AqDYtNo2DK1Brlyn .icon-shape .label rect,#mermaid-svg-AqDYtNo2DK1Brlyn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AqDYtNo2DK1Brlyn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AqDYtNo2DK1Brlyn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AqDYtNo2DK1Brlyn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 广播拓扑
事件源
事件总线
处理器1
发通知
处理器2
更新缓存
处理器3
写日志
处理器4
推数据仓库
特点:一个事件同时触发多个独立的处理器,彼此之间无依赖关系。
适用场景:一个业务事件需要多个系统各自独立响应。比如"订单已支付"需要通知、日志、缓存、数仓各自处理。
3.4 中介者-代理者混合拓扑
实际系统大多是混合拓扑------关键流程用中介者编排,非关键旁路用广播分发。
#mermaid-svg-wZDvXTmrOXJg1Axk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wZDvXTmrOXJg1Axk .error-icon{fill:#552222;}#mermaid-svg-wZDvXTmrOXJg1Axk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wZDvXTmrOXJg1Axk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wZDvXTmrOXJg1Axk .marker.cross{stroke:#333333;}#mermaid-svg-wZDvXTmrOXJg1Axk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wZDvXTmrOXJg1Axk p{margin:0;}#mermaid-svg-wZDvXTmrOXJg1Axk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wZDvXTmrOXJg1Axk .cluster-label text{fill:#333;}#mermaid-svg-wZDvXTmrOXJg1Axk .cluster-label span{color:#333;}#mermaid-svg-wZDvXTmrOXJg1Axk .cluster-label span p{background-color:transparent;}#mermaid-svg-wZDvXTmrOXJg1Axk .label text,#mermaid-svg-wZDvXTmrOXJg1Axk span{fill:#333;color:#333;}#mermaid-svg-wZDvXTmrOXJg1Axk .node rect,#mermaid-svg-wZDvXTmrOXJg1Axk .node circle,#mermaid-svg-wZDvXTmrOXJg1Axk .node ellipse,#mermaid-svg-wZDvXTmrOXJg1Axk .node polygon,#mermaid-svg-wZDvXTmrOXJg1Axk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wZDvXTmrOXJg1Axk .rough-node .label text,#mermaid-svg-wZDvXTmrOXJg1Axk .node .label text,#mermaid-svg-wZDvXTmrOXJg1Axk .image-shape .label,#mermaid-svg-wZDvXTmrOXJg1Axk .icon-shape .label{text-anchor:middle;}#mermaid-svg-wZDvXTmrOXJg1Axk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wZDvXTmrOXJg1Axk .rough-node .label,#mermaid-svg-wZDvXTmrOXJg1Axk .node .label,#mermaid-svg-wZDvXTmrOXJg1Axk .image-shape .label,#mermaid-svg-wZDvXTmrOXJg1Axk .icon-shape .label{text-align:center;}#mermaid-svg-wZDvXTmrOXJg1Axk .node.clickable{cursor:pointer;}#mermaid-svg-wZDvXTmrOXJg1Axk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wZDvXTmrOXJg1Axk .arrowheadPath{fill:#333333;}#mermaid-svg-wZDvXTmrOXJg1Axk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wZDvXTmrOXJg1Axk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wZDvXTmrOXJg1Axk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wZDvXTmrOXJg1Axk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wZDvXTmrOXJg1Axk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wZDvXTmrOXJg1Axk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wZDvXTmrOXJg1Axk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wZDvXTmrOXJg1Axk .cluster text{fill:#333;}#mermaid-svg-wZDvXTmrOXJg1Axk .cluster span{color:#333;}#mermaid-svg-wZDvXTmrOXJg1Axk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wZDvXTmrOXJg1Axk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wZDvXTmrOXJg1Axk rect.text{fill:none;stroke-width:0;}#mermaid-svg-wZDvXTmrOXJg1Axk .icon-shape,#mermaid-svg-wZDvXTmrOXJg1Axk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wZDvXTmrOXJg1Axk .icon-shape p,#mermaid-svg-wZDvXTmrOXJg1Axk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wZDvXTmrOXJg1Axk .icon-shape .label rect,#mermaid-svg-wZDvXTmrOXJg1Axk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wZDvXTmrOXJg1Axk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wZDvXTmrOXJg1Axk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wZDvXTmrOXJg1Axk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 步骤1
库存已扣
步骤2
扣款成功
订单已完成
订单创建事件
订单中介者
扣库存
扣款
订单完成事件
广播总线
通知服务
日志服务
积分服务
数据仓库
四、iPaaS 事件中心实战
理论讲够了,来看我们 iPaaS 平台的真实事件驱动架构。
4.1 事件中心在 iPaaS 中的定位
iPaaS 是一个集成平台,核心使命是"让不同的系统通过事件联动起来"。事件中心是整个平台的"神经中枢":
#mermaid-svg-cM5E1rWmm4sVsVZq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cM5E1rWmm4sVsVZq .error-icon{fill:#552222;}#mermaid-svg-cM5E1rWmm4sVsVZq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cM5E1rWmm4sVsVZq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cM5E1rWmm4sVsVZq .marker.cross{stroke:#333333;}#mermaid-svg-cM5E1rWmm4sVsVZq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cM5E1rWmm4sVsVZq p{margin:0;}#mermaid-svg-cM5E1rWmm4sVsVZq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cM5E1rWmm4sVsVZq .cluster-label text{fill:#333;}#mermaid-svg-cM5E1rWmm4sVsVZq .cluster-label span{color:#333;}#mermaid-svg-cM5E1rWmm4sVsVZq .cluster-label span p{background-color:transparent;}#mermaid-svg-cM5E1rWmm4sVsVZq .label text,#mermaid-svg-cM5E1rWmm4sVsVZq span{fill:#333;color:#333;}#mermaid-svg-cM5E1rWmm4sVsVZq .node rect,#mermaid-svg-cM5E1rWmm4sVsVZq .node circle,#mermaid-svg-cM5E1rWmm4sVsVZq .node ellipse,#mermaid-svg-cM5E1rWmm4sVsVZq .node polygon,#mermaid-svg-cM5E1rWmm4sVsVZq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cM5E1rWmm4sVsVZq .rough-node .label text,#mermaid-svg-cM5E1rWmm4sVsVZq .node .label text,#mermaid-svg-cM5E1rWmm4sVsVZq .image-shape .label,#mermaid-svg-cM5E1rWmm4sVsVZq .icon-shape .label{text-anchor:middle;}#mermaid-svg-cM5E1rWmm4sVsVZq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cM5E1rWmm4sVsVZq .rough-node .label,#mermaid-svg-cM5E1rWmm4sVsVZq .node .label,#mermaid-svg-cM5E1rWmm4sVsVZq .image-shape .label,#mermaid-svg-cM5E1rWmm4sVsVZq .icon-shape .label{text-align:center;}#mermaid-svg-cM5E1rWmm4sVsVZq .node.clickable{cursor:pointer;}#mermaid-svg-cM5E1rWmm4sVsVZq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cM5E1rWmm4sVsVZq .arrowheadPath{fill:#333333;}#mermaid-svg-cM5E1rWmm4sVsVZq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cM5E1rWmm4sVsVZq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cM5E1rWmm4sVsVZq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cM5E1rWmm4sVsVZq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cM5E1rWmm4sVsVZq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cM5E1rWmm4sVsVZq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cM5E1rWmm4sVsVZq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cM5E1rWmm4sVsVZq .cluster text{fill:#333;}#mermaid-svg-cM5E1rWmm4sVsVZq .cluster span{color:#333;}#mermaid-svg-cM5E1rWmm4sVsVZq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cM5E1rWmm4sVsVZq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cM5E1rWmm4sVsVZq rect.text{fill:none;stroke-width:0;}#mermaid-svg-cM5E1rWmm4sVsVZq .icon-shape,#mermaid-svg-cM5E1rWmm4sVsVZq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cM5E1rWmm4sVsVZq .icon-shape p,#mermaid-svg-cM5E1rWmm4sVsVZq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cM5E1rWmm4sVsVZq .icon-shape .label rect,#mermaid-svg-cM5E1rWmm4sVsVZq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cM5E1rWmm4sVsVZq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cM5E1rWmm4sVsVZq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cM5E1rWmm4sVsVZq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 基础设施
流程引擎
事件中心
事件来源(600+ 第三方应用)
Webhook 推送
钉钉/企微/飞书/...
定时轮询
HTTP/数据库/API
数据变更捕获
MySQL/PG/MongoDB
定时触发
Cron 表达式
事件接收层
BaseEventController
消息生产者
PushEventProducer
消息消费者
PushEventListener
触发器系统
Push/Polling/CDC
流程编排执行
连接器调用
RocketMQ
push_event Topic
Redis
去重/限流
4.2 事件中心的架构设计
事件中心的核心职责:接收第三方系统推送的事件 → 标准化封装 → 异步投递到 RocketMQ → 引擎消费并触发流程。
4.2.1 事件接收层
每个第三方应用有独立的 EventController,继承 BaseEventController,负责接收 Webhook 回调:
java
// 事件接收基类------统一的事件入口
@Component
public class BaseEventController {
@Resource
PushEventProducer pushEventProducer;
/**
* 构建标准化事件消息体
*/
protected PushEventMessage buildBody(
String processor,
ConnectorKey connectorKey,
AssetKey assetKey,
@Nullable Object event,
String body) {
PushEventMessage msg = new PushEventMessage();
msg.setProcessor(processor);
msg.setConnectorKey(connectorKey);
msg.setAssetKey(assetKey);
msg.setEvent(event);
msg.setMessage(body);
return msg;
}
/**
* 异步投递事件到 MQ
*/
protected void sendMessage(PushEventMessage pushEventMessage) {
String message = JSON.toJSONString(pushEventMessage);
pushEventProducer.sendMessage(message);
}
/**
* 快速响应第三方------不等流程执行完
*/
protected Object buildResponse() {
Map<String, Object> response = new HashMap<>();
response.put("code", 0);
response.put("msg", "success");
return response;
}
}
以小红书事件控制器为例:
java
@RestController
@RequestMapping("/event/xiaohongshu")
public class XiaohongshuEventController extends BaseEventController {
@PostMapping("/pushEvent")
public Object pushEvent(
@RequestHeader("app-key") String appKey,
@RequestHeader("sign") String sign,
@RequestBody String body) {
// 1. 解析第三方推送的事件数据
List<XiaohongshuPushing> messages = JSON.parseArray(body, ...);
// 2. 逐条封装为标准化事件消息
messages.forEach(message -> {
PushEventMessage msg = this.buildBody(
PROCESS_NAME,
new ConnectorKey(CONNECTOR_ID, connectorVersion),
AssetKey.builder()
.type(AssetOwnerTypeEnum.ASSET_UK.getCode())
.assetId(message.getSellerId())
.build(),
message.getMsgTag(),
(String) message.getData()
);
// 3. 异步投递到 MQ------不等流程执行
this.sendMessage(msg);
});
// 4. 立即响应第三方------毫秒级返回
return buildResponse();
}
}
关键设计决策:
- 快速 ACK:收到 Webhook 后立即返回成功,不等流程执行完。如果同步执行流程,第三方超时会导致连接堆积
- 标准化封装 :600+ 个第三方应用的事件格式千差万别,但在事件中心统一封装成
PushEventMessage,下游引擎不需要关心来源差异 - 继承体系 :
BaseEventController封装公共逻辑,各应用的 Controller 只关注自身的事件解析
4.2.2 消息投递层
java
@Component
public class PushEventProducer {
@Resource
private Producer producer;
@Value("${rocketmq.push.event.topic:push_event}")
private String topic;
/**
* 发送普通事件消息
*/
public void sendMessage(String msgBody) {
final Message message = provider.newMessageBuilder()
.setTopic(topic)
.setKeys(PUSH_EVENT_TAG)
.setTag(PUSH_EVENT_TAG)
.setBody(msgBody.getBytes(StandardCharsets.UTF_8))
.build();
producer.send(message);
}
/**
* 发送顺序消息------保证同一事件源的顺序性
*/
public void sendOrderlyMessage(String msgBody, String orderTag) {
// 按 orderTag 分区,保证同一分区内消息有序
...
}
}
4.2.3 消息消费层
java
@Component
public class PushEventListener extends BaseConsumer implements MessageListener {
@Override
public ConsumeResult consume(MessageView messageView) {
// 从 MQ 消费事件消息,触发流程引擎执行
String body = ByteBufferUtils.byteBufferToString(messageView.getBody());
return doConsume(body);
}
}
4.3 引擎侧的触发器体系
事件中心投递消息后,引擎侧通过触发器体系响应事件:
#mermaid-svg-B3BdQ2PWBn9NuLc5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .error-icon{fill:#552222;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .marker.cross{stroke:#333333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 p{margin:0;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .cluster-label text{fill:#333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .cluster-label span{color:#333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .cluster-label span p{background-color:transparent;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .label text,#mermaid-svg-B3BdQ2PWBn9NuLc5 span{fill:#333;color:#333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .node rect,#mermaid-svg-B3BdQ2PWBn9NuLc5 .node circle,#mermaid-svg-B3BdQ2PWBn9NuLc5 .node ellipse,#mermaid-svg-B3BdQ2PWBn9NuLc5 .node polygon,#mermaid-svg-B3BdQ2PWBn9NuLc5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .rough-node .label text,#mermaid-svg-B3BdQ2PWBn9NuLc5 .node .label text,#mermaid-svg-B3BdQ2PWBn9NuLc5 .image-shape .label,#mermaid-svg-B3BdQ2PWBn9NuLc5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .rough-node .label,#mermaid-svg-B3BdQ2PWBn9NuLc5 .node .label,#mermaid-svg-B3BdQ2PWBn9NuLc5 .image-shape .label,#mermaid-svg-B3BdQ2PWBn9NuLc5 .icon-shape .label{text-align:center;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .node.clickable{cursor:pointer;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .arrowheadPath{fill:#333333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-B3BdQ2PWBn9NuLc5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-B3BdQ2PWBn9NuLc5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .cluster text{fill:#333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .cluster span{color:#333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .icon-shape,#mermaid-svg-B3BdQ2PWBn9NuLc5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .icon-shape p,#mermaid-svg-B3BdQ2PWBn9NuLc5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .icon-shape .label rect,#mermaid-svg-B3BdQ2PWBn9NuLc5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-B3BdQ2PWBn9NuLc5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-B3BdQ2PWBn9NuLc5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 流程引擎执行
四种触发器类型
推送触发器 PushEventTrigger
接收 Webhook/MQ 事件
轮询触发器 HttpPollingTrigger
Cron 定时拉取
CDC触发器 DataBaseCdcTrigger
数据库变更捕获
定时触发器 TimerTrigger
Cron 定时执行
TriggerExchange
事件上下文
FlowInstance
流程实例
ExecuteMachine
执行机
四种触发模式对比:
| 触发器 | 事件来源 | 实时性 | 适用场景 |
|---|---|---|---|
| 推送触发器 | 第三方 Webhook → MQ | 实时 | 钉钉消息回调、电商订单推送 |
| 轮询触发器 | Cron 定时拉取 API | 准实时(秒级) | 不支持 Webhook 的系统 |
| CDC 触发器 | 数据库 Binlog | 准实时(秒级) | 数据库变更监听 |
| 定时触发器 | Cron 定时执行 | 定时(分钟级) | 定时报表、数据同步 |
4.4 一个事件的完整生命周期
以"钉钉机器人消息触发流程"为例:
SAP系统 流程引擎 RocketMQ 事件中心 钉钉服务器 SAP系统 流程引擎 RocketMQ 事件中心 钉钉服务器 #mermaid-svg-tN0G8UoFBJ8DNTOB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tN0G8UoFBJ8DNTOB .error-icon{fill:#552222;}#mermaid-svg-tN0G8UoFBJ8DNTOB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tN0G8UoFBJ8DNTOB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tN0G8UoFBJ8DNTOB .marker.cross{stroke:#333333;}#mermaid-svg-tN0G8UoFBJ8DNTOB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tN0G8UoFBJ8DNTOB p{margin:0;}#mermaid-svg-tN0G8UoFBJ8DNTOB .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tN0G8UoFBJ8DNTOB text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-tN0G8UoFBJ8DNTOB .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tN0G8UoFBJ8DNTOB .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-tN0G8UoFBJ8DNTOB .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-tN0G8UoFBJ8DNTOB .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-tN0G8UoFBJ8DNTOB #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-tN0G8UoFBJ8DNTOB .sequenceNumber{fill:white;}#mermaid-svg-tN0G8UoFBJ8DNTOB #sequencenumber{fill:#333;}#mermaid-svg-tN0G8UoFBJ8DNTOB #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-tN0G8UoFBJ8DNTOB .messageText{fill:#333;stroke:none;}#mermaid-svg-tN0G8UoFBJ8DNTOB .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tN0G8UoFBJ8DNTOB .labelText,#mermaid-svg-tN0G8UoFBJ8DNTOB .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-tN0G8UoFBJ8DNTOB .loopText,#mermaid-svg-tN0G8UoFBJ8DNTOB .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-tN0G8UoFBJ8DNTOB .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tN0G8UoFBJ8DNTOB .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-tN0G8UoFBJ8DNTOB .noteText,#mermaid-svg-tN0G8UoFBJ8DNTOB .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-tN0G8UoFBJ8DNTOB .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tN0G8UoFBJ8DNTOB .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tN0G8UoFBJ8DNTOB .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tN0G8UoFBJ8DNTOB .actorPopupMenu{position:absolute;}#mermaid-svg-tN0G8UoFBJ8DNTOB .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-tN0G8UoFBJ8DNTOB .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tN0G8UoFBJ8DNTOB .actor-man circle,#mermaid-svg-tN0G8UoFBJ8DNTOB line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-tN0G8UoFBJ8DNTOB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 事件在队列中等待消费 POST /event/dingding/webhook 验签 + 解析消息 sendMessage(PushEventMessage) 200 OK(< 50ms) consume(MessageView) 匹配触发器 → 创建流程实例 加载流程定义 → 逐节点执行 HTTP 调用 SAP 接口 返回业务数据 写入执行日志
关键设计要点:
- 毫秒级响应第三方:事件中心只做"接收 → 封装 → 投递"三步,不做任何业务处理。钉钉服务器不会因为流程执行慢而超时
- 削峰填谷:高并发 Webhook 涌入时,事件堆积在 RocketMQ 中,引擎按自己的消费能力逐步处理
- 故障隔离:引擎挂了?事件在 MQ 中安全存储,恢复后继续消费,零数据丢失
五、事件驱动架构中最常踩的八个坑
5.1 坑一:事件丢失------消息发了但没人收到
症状:用户反馈"流程没有被触发",查日志发现事件确实发了,但消费者没收到。
为什么丢:
- 生产者发送失败但没重试
- MQ 宕机时未持久化的消息丢失
- 消费者处理失败但标记了消费成功
修复方案------三道防线:
#mermaid-svg-3FRja9MXT2YFDFoQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3FRja9MXT2YFDFoQ .error-icon{fill:#552222;}#mermaid-svg-3FRja9MXT2YFDFoQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3FRja9MXT2YFDFoQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3FRja9MXT2YFDFoQ .marker.cross{stroke:#333333;}#mermaid-svg-3FRja9MXT2YFDFoQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3FRja9MXT2YFDFoQ p{margin:0;}#mermaid-svg-3FRja9MXT2YFDFoQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3FRja9MXT2YFDFoQ .cluster-label text{fill:#333;}#mermaid-svg-3FRja9MXT2YFDFoQ .cluster-label span{color:#333;}#mermaid-svg-3FRja9MXT2YFDFoQ .cluster-label span p{background-color:transparent;}#mermaid-svg-3FRja9MXT2YFDFoQ .label text,#mermaid-svg-3FRja9MXT2YFDFoQ span{fill:#333;color:#333;}#mermaid-svg-3FRja9MXT2YFDFoQ .node rect,#mermaid-svg-3FRja9MXT2YFDFoQ .node circle,#mermaid-svg-3FRja9MXT2YFDFoQ .node ellipse,#mermaid-svg-3FRja9MXT2YFDFoQ .node polygon,#mermaid-svg-3FRja9MXT2YFDFoQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3FRja9MXT2YFDFoQ .rough-node .label text,#mermaid-svg-3FRja9MXT2YFDFoQ .node .label text,#mermaid-svg-3FRja9MXT2YFDFoQ .image-shape .label,#mermaid-svg-3FRja9MXT2YFDFoQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-3FRja9MXT2YFDFoQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3FRja9MXT2YFDFoQ .rough-node .label,#mermaid-svg-3FRja9MXT2YFDFoQ .node .label,#mermaid-svg-3FRja9MXT2YFDFoQ .image-shape .label,#mermaid-svg-3FRja9MXT2YFDFoQ .icon-shape .label{text-align:center;}#mermaid-svg-3FRja9MXT2YFDFoQ .node.clickable{cursor:pointer;}#mermaid-svg-3FRja9MXT2YFDFoQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3FRja9MXT2YFDFoQ .arrowheadPath{fill:#333333;}#mermaid-svg-3FRja9MXT2YFDFoQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3FRja9MXT2YFDFoQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3FRja9MXT2YFDFoQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3FRja9MXT2YFDFoQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3FRja9MXT2YFDFoQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3FRja9MXT2YFDFoQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3FRja9MXT2YFDFoQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3FRja9MXT2YFDFoQ .cluster text{fill:#333;}#mermaid-svg-3FRja9MXT2YFDFoQ .cluster span{color:#333;}#mermaid-svg-3FRja9MXT2YFDFoQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3FRja9MXT2YFDFoQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3FRja9MXT2YFDFoQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-3FRja9MXT2YFDFoQ .icon-shape,#mermaid-svg-3FRja9MXT2YFDFoQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3FRja9MXT2YFDFoQ .icon-shape p,#mermaid-svg-3FRja9MXT2YFDFoQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3FRja9MXT2YFDFoQ .icon-shape .label rect,#mermaid-svg-3FRja9MXT2YFDFoQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3FRja9MXT2YFDFoQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3FRja9MXT2YFDFoQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3FRja9MXT2YFDFoQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 防线三:消费端确认
投递
处理成功
处理失败
MQ
消费者
ACK
NACK → 重试
防线二:Broker持久化
同步刷盘
主从复制
MQ
磁盘
从节点
防线一:生产端确认
发送
ACK
失败重试
生产者
MQ
5.2 坑二:重复消费------一个事件被处理了三次
症状:用户收到三条一样的通知短信,数据库里出现重复记录。
为什么重复:
- 消费者处理成功后 ACK 失败,MQ 重新投递
- 生产者超时重试,同一条消息发了两次
- 消费者重启,未 ACK 的消息被重新投递
修复方案------幂等性设计:
java
/**
* 幂等消费:同一个事件只处理一次
*/
public ConsumeResult consume(MessageView messageView) {
String eventId = extractEventId(messageView);
// 1. 幂等检查:用 Redis 记录已处理的事件ID
if (redisTemplate.opsForValue().setIfAbsent(
"consumed:" + eventId, "1", 24, TimeUnit.HOURS)) {
// 2. 第一次处理:执行业务逻辑
return doProcess(messageView);
} else {
// 3. 重复消息:跳过
log.warn("Duplicate event detected: {}", eventId);
return ConsumeResult.SUCCESS;
}
}
幂等设计的核心原则:消费者必须假设"同一条消息可能被投递多次",并确保多次处理的结果和一次处理完全一致。
5.3 坑三:事件顺序错乱------先消费了"取消"再消费"创建"
症状:订单先收到"取消"事件,再收到"创建"事件------状态机乱了。
为什么乱序:
- MQ 的多分区并行消费
- 消费者多线程处理
- 网络延迟导致后发的消息先到
修复方案:
| 方案 | 说明 | 适用场景 |
|---|---|---|
| 分区有序 | 同一实体的事件发到同一分区 | 大部分场景(推荐) |
| 版本号 | 事件带版本号,低版本丢弃 | 数据同步场景 |
| 时间戳排序 | 消费者按时间戳重排序 | 容忍一定延迟的批处理 |
java
// 分区有序:同一 assetId 的事件进入同一分区
public void sendOrderlyMessage(String msgBody, String orderTag) {
final Message message = provider.newMessageBuilder()
.setTopic(orderTopic)
.setKeys(orderTag) // 用 orderTag 做分区键
.setTag(PUSH_ORDER_EVENT_TAG)
.setBody(msgBody.getBytes(StandardCharsets.UTF_8))
.build();
producer.send(message); // 同一 orderTag 保证顺序
}
5.4 坑四:消费者雪崩------一个慢消费者拖垮所有人
症状:某个消费者的处理逻辑特别慢(比如调外部 API 超时),导致消息堆积,其他正常消费者也受影响。
修复方案:
- 独立消费组:不同类型的消费者使用独立的消费组,互不影响
- 消费超时:设置单条消息的最大处理时间
- 死信队列:多次重试失败的消息进入死信队列,不阻塞正常消费
#mermaid-svg-dDCSwpClY5jViGXV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dDCSwpClY5jViGXV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dDCSwpClY5jViGXV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dDCSwpClY5jViGXV .error-icon{fill:#552222;}#mermaid-svg-dDCSwpClY5jViGXV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dDCSwpClY5jViGXV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dDCSwpClY5jViGXV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dDCSwpClY5jViGXV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dDCSwpClY5jViGXV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dDCSwpClY5jViGXV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dDCSwpClY5jViGXV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dDCSwpClY5jViGXV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dDCSwpClY5jViGXV .marker.cross{stroke:#333333;}#mermaid-svg-dDCSwpClY5jViGXV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dDCSwpClY5jViGXV p{margin:0;}#mermaid-svg-dDCSwpClY5jViGXV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dDCSwpClY5jViGXV .cluster-label text{fill:#333;}#mermaid-svg-dDCSwpClY5jViGXV .cluster-label span{color:#333;}#mermaid-svg-dDCSwpClY5jViGXV .cluster-label span p{background-color:transparent;}#mermaid-svg-dDCSwpClY5jViGXV .label text,#mermaid-svg-dDCSwpClY5jViGXV span{fill:#333;color:#333;}#mermaid-svg-dDCSwpClY5jViGXV .node rect,#mermaid-svg-dDCSwpClY5jViGXV .node circle,#mermaid-svg-dDCSwpClY5jViGXV .node ellipse,#mermaid-svg-dDCSwpClY5jViGXV .node polygon,#mermaid-svg-dDCSwpClY5jViGXV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dDCSwpClY5jViGXV .rough-node .label text,#mermaid-svg-dDCSwpClY5jViGXV .node .label text,#mermaid-svg-dDCSwpClY5jViGXV .image-shape .label,#mermaid-svg-dDCSwpClY5jViGXV .icon-shape .label{text-anchor:middle;}#mermaid-svg-dDCSwpClY5jViGXV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dDCSwpClY5jViGXV .rough-node .label,#mermaid-svg-dDCSwpClY5jViGXV .node .label,#mermaid-svg-dDCSwpClY5jViGXV .image-shape .label,#mermaid-svg-dDCSwpClY5jViGXV .icon-shape .label{text-align:center;}#mermaid-svg-dDCSwpClY5jViGXV .node.clickable{cursor:pointer;}#mermaid-svg-dDCSwpClY5jViGXV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dDCSwpClY5jViGXV .arrowheadPath{fill:#333333;}#mermaid-svg-dDCSwpClY5jViGXV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dDCSwpClY5jViGXV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dDCSwpClY5jViGXV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dDCSwpClY5jViGXV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dDCSwpClY5jViGXV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dDCSwpClY5jViGXV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dDCSwpClY5jViGXV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dDCSwpClY5jViGXV .cluster text{fill:#333;}#mermaid-svg-dDCSwpClY5jViGXV .cluster span{color:#333;}#mermaid-svg-dDCSwpClY5jViGXV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dDCSwpClY5jViGXV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dDCSwpClY5jViGXV rect.text{fill:none;stroke-width:0;}#mermaid-svg-dDCSwpClY5jViGXV .icon-shape,#mermaid-svg-dDCSwpClY5jViGXV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dDCSwpClY5jViGXV .icon-shape p,#mermaid-svg-dDCSwpClY5jViGXV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dDCSwpClY5jViGXV .icon-shape .label rect,#mermaid-svg-dDCSwpClY5jViGXV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dDCSwpClY5jViGXV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dDCSwpClY5jViGXV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dDCSwpClY5jViGXV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 死信处理
正常消费
重试3次失败
Topic
消费组A
通知服务
消费组B
日志服务
消费组C
积分服务
死信队列
DLQ
告警通知
人工重试
5.5 坑五:事件风暴------一个操作触发一百个事件
症状:用户点了一个按钮,系统产生了几百个事件,MQ 被塞满,消费延迟飙升。
为什么 :批量操作没有合并事件。比如"批量导入 1000 条客户",如果每条都发一个 CustomerCreated 事件,就是 1000 个事件。
修复方案:
| 方案 | 说明 |
|---|---|
| 事件合并 | 批量操作发一个 CustomerBatchImported 事件,包含 ID 列表 |
| 事件节流 | 限制同一源头在单位时间内的最大事件数 |
| 批量消费 | 消费者一次拉取多条消息批量处理 |
5.6 坑六:同步伪装成异步------发事件后还在等结果
症状 :Controller 发了事件后,还轮询数据库等结果,或者用 Thread.sleep 等待。
为什么是坑:本质上还是同步思维,白白浪费了异步的优势,还多了一层 MQ 的开销。
修复方案:真正的异步是"发了就走"。前端需要结果?用 WebSocket 推送,或者轮询状态接口。
5.7 坑七:事件模式未定义------生产者和消费者各说各话
症状:生产者改了事件字段名,消费者还在用旧字段名读取------线上静默失败,数据全是 null。
修复方案:
- 事件模式注册:每个事件有明确的 Schema(JSON Schema / Protobuf)
- 版本管理:事件结构变更时递增版本号,消费者兼容多版本
- 契约测试:生产者和消费者共享 Schema,CI 中校验兼容性
5.8 坑八:忽视可观测性------出了问题完全无法排查
症状:用户说"我的流程没触发",你查了一圈不知道事件到哪儿了。
修复方案------事件追踪三件套:
| 维度 | 做法 | 价值 |
|---|---|---|
| Trace ID | 事件携带全局唯一的追踪 ID | 串联从生产到消费的全链路 |
| 事件日志 | 记录每个事件的投递/消费/ACK 状态 | 出问题时有据可查 |
| 消费监控 | 监控消费延迟/堆积量/失败率 | 问题发生前就能预警 |
六、事件溯源(Event Sourcing):把事件当数据库
6.1 什么是事件溯源?
传统做法:数据库里存"当前状态"------一条订单记录存了 status = 'COMPLETED',你不知道它经历了什么。
事件溯源:不存状态,只存事件。当前状态 = 所有事件的叠加。
#mermaid-svg-oFu7iVwhhv2fofxT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oFu7iVwhhv2fofxT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oFu7iVwhhv2fofxT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oFu7iVwhhv2fofxT .error-icon{fill:#552222;}#mermaid-svg-oFu7iVwhhv2fofxT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oFu7iVwhhv2fofxT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oFu7iVwhhv2fofxT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oFu7iVwhhv2fofxT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oFu7iVwhhv2fofxT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oFu7iVwhhv2fofxT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oFu7iVwhhv2fofxT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oFu7iVwhhv2fofxT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oFu7iVwhhv2fofxT .marker.cross{stroke:#333333;}#mermaid-svg-oFu7iVwhhv2fofxT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oFu7iVwhhv2fofxT p{margin:0;}#mermaid-svg-oFu7iVwhhv2fofxT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oFu7iVwhhv2fofxT .cluster-label text{fill:#333;}#mermaid-svg-oFu7iVwhhv2fofxT .cluster-label span{color:#333;}#mermaid-svg-oFu7iVwhhv2fofxT .cluster-label span p{background-color:transparent;}#mermaid-svg-oFu7iVwhhv2fofxT .label text,#mermaid-svg-oFu7iVwhhv2fofxT span{fill:#333;color:#333;}#mermaid-svg-oFu7iVwhhv2fofxT .node rect,#mermaid-svg-oFu7iVwhhv2fofxT .node circle,#mermaid-svg-oFu7iVwhhv2fofxT .node ellipse,#mermaid-svg-oFu7iVwhhv2fofxT .node polygon,#mermaid-svg-oFu7iVwhhv2fofxT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oFu7iVwhhv2fofxT .rough-node .label text,#mermaid-svg-oFu7iVwhhv2fofxT .node .label text,#mermaid-svg-oFu7iVwhhv2fofxT .image-shape .label,#mermaid-svg-oFu7iVwhhv2fofxT .icon-shape .label{text-anchor:middle;}#mermaid-svg-oFu7iVwhhv2fofxT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oFu7iVwhhv2fofxT .rough-node .label,#mermaid-svg-oFu7iVwhhv2fofxT .node .label,#mermaid-svg-oFu7iVwhhv2fofxT .image-shape .label,#mermaid-svg-oFu7iVwhhv2fofxT .icon-shape .label{text-align:center;}#mermaid-svg-oFu7iVwhhv2fofxT .node.clickable{cursor:pointer;}#mermaid-svg-oFu7iVwhhv2fofxT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oFu7iVwhhv2fofxT .arrowheadPath{fill:#333333;}#mermaid-svg-oFu7iVwhhv2fofxT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oFu7iVwhhv2fofxT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oFu7iVwhhv2fofxT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oFu7iVwhhv2fofxT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oFu7iVwhhv2fofxT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oFu7iVwhhv2fofxT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oFu7iVwhhv2fofxT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oFu7iVwhhv2fofxT .cluster text{fill:#333;}#mermaid-svg-oFu7iVwhhv2fofxT .cluster span{color:#333;}#mermaid-svg-oFu7iVwhhv2fofxT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oFu7iVwhhv2fofxT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oFu7iVwhhv2fofxT rect.text{fill:none;stroke-width:0;}#mermaid-svg-oFu7iVwhhv2fofxT .icon-shape,#mermaid-svg-oFu7iVwhhv2fofxT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oFu7iVwhhv2fofxT .icon-shape p,#mermaid-svg-oFu7iVwhhv2fofxT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oFu7iVwhhv2fofxT .icon-shape .label rect,#mermaid-svg-oFu7iVwhhv2fofxT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oFu7iVwhhv2fofxT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oFu7iVwhhv2fofxT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oFu7iVwhhv2fofxT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 事件溯源:事件存储
重放
Event Store
OrderCreated
PaymentReceived
OrderShipped
OrderDelivered
当前状态
= COMPLETED
传统:状态存储
MySQL
status = COMPLETED
6.2 事件溯源的收益
| 收益 | 说明 |
|---|---|
| 完整审计 | 每一步变更都有记录,满足金融/医疗合规要求 |
| 时间旅行 | 可以回放任意时间点的事件,重建当时的状态 |
| 投影灵活 | 同一组事件可以投影成不同的读模型(订单视图/统计视图/风控视图) |
| 天然解耦 | 写操作只追加事件,读操作从投影读取 |
6.3 事件溯源的代价
| 代价 | 说明 |
|---|---|
| 查询复杂 | 不能直接查"所有已完成的订单"------需要投影 |
| 性能开销 | 重放百万条事件来重建状态,需要快照机制 |
| 学习曲线 | 团队要从"更新状态"的思维转为"追加事件"的思维 |
| 模式演进 | 事件结构变更后,旧事件和新事件的兼容处理 |
适用场景:金融交易审计、保险保单全生命周期、医疗病历------任何"需要知道发生了什么而不只是现在是什么"的场景。
七、事件驱动 + CQRS:读写分离的终极形态
7.1 架构全景
#mermaid-svg-f5mx92VaRWH5T4wc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-f5mx92VaRWH5T4wc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-f5mx92VaRWH5T4wc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-f5mx92VaRWH5T4wc .error-icon{fill:#552222;}#mermaid-svg-f5mx92VaRWH5T4wc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-f5mx92VaRWH5T4wc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-f5mx92VaRWH5T4wc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-f5mx92VaRWH5T4wc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-f5mx92VaRWH5T4wc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-f5mx92VaRWH5T4wc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-f5mx92VaRWH5T4wc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-f5mx92VaRWH5T4wc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-f5mx92VaRWH5T4wc .marker.cross{stroke:#333333;}#mermaid-svg-f5mx92VaRWH5T4wc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-f5mx92VaRWH5T4wc p{margin:0;}#mermaid-svg-f5mx92VaRWH5T4wc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-f5mx92VaRWH5T4wc .cluster-label text{fill:#333;}#mermaid-svg-f5mx92VaRWH5T4wc .cluster-label span{color:#333;}#mermaid-svg-f5mx92VaRWH5T4wc .cluster-label span p{background-color:transparent;}#mermaid-svg-f5mx92VaRWH5T4wc .label text,#mermaid-svg-f5mx92VaRWH5T4wc span{fill:#333;color:#333;}#mermaid-svg-f5mx92VaRWH5T4wc .node rect,#mermaid-svg-f5mx92VaRWH5T4wc .node circle,#mermaid-svg-f5mx92VaRWH5T4wc .node ellipse,#mermaid-svg-f5mx92VaRWH5T4wc .node polygon,#mermaid-svg-f5mx92VaRWH5T4wc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-f5mx92VaRWH5T4wc .rough-node .label text,#mermaid-svg-f5mx92VaRWH5T4wc .node .label text,#mermaid-svg-f5mx92VaRWH5T4wc .image-shape .label,#mermaid-svg-f5mx92VaRWH5T4wc .icon-shape .label{text-anchor:middle;}#mermaid-svg-f5mx92VaRWH5T4wc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-f5mx92VaRWH5T4wc .rough-node .label,#mermaid-svg-f5mx92VaRWH5T4wc .node .label,#mermaid-svg-f5mx92VaRWH5T4wc .image-shape .label,#mermaid-svg-f5mx92VaRWH5T4wc .icon-shape .label{text-align:center;}#mermaid-svg-f5mx92VaRWH5T4wc .node.clickable{cursor:pointer;}#mermaid-svg-f5mx92VaRWH5T4wc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-f5mx92VaRWH5T4wc .arrowheadPath{fill:#333333;}#mermaid-svg-f5mx92VaRWH5T4wc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-f5mx92VaRWH5T4wc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-f5mx92VaRWH5T4wc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f5mx92VaRWH5T4wc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-f5mx92VaRWH5T4wc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f5mx92VaRWH5T4wc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-f5mx92VaRWH5T4wc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-f5mx92VaRWH5T4wc .cluster text{fill:#333;}#mermaid-svg-f5mx92VaRWH5T4wc .cluster span{color:#333;}#mermaid-svg-f5mx92VaRWH5T4wc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-f5mx92VaRWH5T4wc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-f5mx92VaRWH5T4wc rect.text{fill:none;stroke-width:0;}#mermaid-svg-f5mx92VaRWH5T4wc .icon-shape,#mermaid-svg-f5mx92VaRWH5T4wc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f5mx92VaRWH5T4wc .icon-shape p,#mermaid-svg-f5mx92VaRWH5T4wc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-f5mx92VaRWH5T4wc .icon-shape .label rect,#mermaid-svg-f5mx92VaRWH5T4wc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f5mx92VaRWH5T4wc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-f5mx92VaRWH5T4wc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-f5mx92VaRWH5T4wc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 查询
读端:Query
写端:Command
事件流
写入 API
命令处理器
领域模型
业务规则校验
事件存储
Event Store
事件投影器
订单视图
MySQL
统计视图
Doris
搜索视图
ES
查询 API
写端 只负责接收命令 → 校验业务规则 → 追加事件。
读端 消费事件 → 更新各自的读模型(可以是不同的数据库)。
查询端直连读模型,毫秒级返回。
7.2 iPaaS 的实践:执行日志的 CQRS
我们在 iPaaS 的执行日志场景就使用了类似 CQRS 的思路:
#mermaid-svg-KXOAOKNwTAQb8rrR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KXOAOKNwTAQb8rrR .error-icon{fill:#552222;}#mermaid-svg-KXOAOKNwTAQb8rrR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KXOAOKNwTAQb8rrR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KXOAOKNwTAQb8rrR .marker.cross{stroke:#333333;}#mermaid-svg-KXOAOKNwTAQb8rrR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KXOAOKNwTAQb8rrR p{margin:0;}#mermaid-svg-KXOAOKNwTAQb8rrR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KXOAOKNwTAQb8rrR .cluster-label text{fill:#333;}#mermaid-svg-KXOAOKNwTAQb8rrR .cluster-label span{color:#333;}#mermaid-svg-KXOAOKNwTAQb8rrR .cluster-label span p{background-color:transparent;}#mermaid-svg-KXOAOKNwTAQb8rrR .label text,#mermaid-svg-KXOAOKNwTAQb8rrR span{fill:#333;color:#333;}#mermaid-svg-KXOAOKNwTAQb8rrR .node rect,#mermaid-svg-KXOAOKNwTAQb8rrR .node circle,#mermaid-svg-KXOAOKNwTAQb8rrR .node ellipse,#mermaid-svg-KXOAOKNwTAQb8rrR .node polygon,#mermaid-svg-KXOAOKNwTAQb8rrR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KXOAOKNwTAQb8rrR .rough-node .label text,#mermaid-svg-KXOAOKNwTAQb8rrR .node .label text,#mermaid-svg-KXOAOKNwTAQb8rrR .image-shape .label,#mermaid-svg-KXOAOKNwTAQb8rrR .icon-shape .label{text-anchor:middle;}#mermaid-svg-KXOAOKNwTAQb8rrR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KXOAOKNwTAQb8rrR .rough-node .label,#mermaid-svg-KXOAOKNwTAQb8rrR .node .label,#mermaid-svg-KXOAOKNwTAQb8rrR .image-shape .label,#mermaid-svg-KXOAOKNwTAQb8rrR .icon-shape .label{text-align:center;}#mermaid-svg-KXOAOKNwTAQb8rrR .node.clickable{cursor:pointer;}#mermaid-svg-KXOAOKNwTAQb8rrR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KXOAOKNwTAQb8rrR .arrowheadPath{fill:#333333;}#mermaid-svg-KXOAOKNwTAQb8rrR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KXOAOKNwTAQb8rrR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KXOAOKNwTAQb8rrR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KXOAOKNwTAQb8rrR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KXOAOKNwTAQb8rrR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KXOAOKNwTAQb8rrR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KXOAOKNwTAQb8rrR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KXOAOKNwTAQb8rrR .cluster text{fill:#333;}#mermaid-svg-KXOAOKNwTAQb8rrR .cluster span{color:#333;}#mermaid-svg-KXOAOKNwTAQb8rrR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KXOAOKNwTAQb8rrR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KXOAOKNwTAQb8rrR rect.text{fill:none;stroke-width:0;}#mermaid-svg-KXOAOKNwTAQb8rrR .icon-shape,#mermaid-svg-KXOAOKNwTAQb8rrR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KXOAOKNwTAQb8rrR .icon-shape p,#mermaid-svg-KXOAOKNwTAQb8rrR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KXOAOKNwTAQb8rrR .icon-shape .label rect,#mermaid-svg-KXOAOKNwTAQb8rrR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KXOAOKNwTAQb8rrR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KXOAOKNwTAQb8rrR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KXOAOKNwTAQb8rrR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 读端
数据同步
写端
Flink CDC
流程引擎
MySQL
权威数据源
Doris
列存引擎
查询 API
- 写链路:流程执行日志写入 MySQL,经过业务规则校验
- 同步层:Flink CDC 异步将数据同步到 Doris
- 读链路:查询直接走 Doris 列存引擎,亿级日志毫秒级返回
八、事件驱动架构落地 Checklist
事件设计
- 事件名使用过去式(
OrderCreated而非CreateOrder) - 事件载荷包含足够下游处理的信息,不过多不过少
- 事件携带元数据(时间戳、来源、Trace ID)
- 事件 Schema 有版本管理,变更向后兼容
消息可靠性
- 生产端有发送确认 + 失败重试
- MQ 配置持久化 + 主从复制
- 消费端实现幂等处理
- 死信队列配置 + 告警
架构设计
- 生产者和消费者通过 MQ 解耦,不直接依赖
- 关键流程使用顺序消息保证有序性
- 消费者有独立消费组,互不影响
- 事件中心有削峰能力(MQ 缓冲)
可观测性
- 事件全链路有 Trace ID 串联
- 消费延迟/堆积量有监控告警
- 事件日志可查看投递和消费状态
- 有死信队列的巡检和重试机制
九、事件驱动架构的七个反模式
反模式 1:同步伪装异步
Controller 发了事件后轮询数据库等结果,本质上还是同步。
修复:发了就走,前端用 WebSocket 或轮询状态接口获取结果。
反模式 2:事件大杂烩
所有业务变更都发一个 DataChanged 事件,消费者要自己判断到底是什么变了。
修复 :事件类型要细粒度------OrderCreated、OrderPaid、OrderShipped,消费者只订阅自己关心的事件。
反模式 3:消费者耦合
消费者在处理事件时直接调用另一个消费者的 API------变成了隐式的同步调用链。
修复:消费者只处理事件、产生新事件,不直接调用其他消费者。
反模式 4:忽视幂等
消费者假设"每条消息只会被处理一次"------重试时产生重复数据。
修复:每个消费者必须实现幂等处理。用 Redis 去重、数据库唯一约束、或状态机校验。
反模式 5:上帝事件
一个事件包含了系统中所有字段(几百个字段),每个消费者只用其中几个。
修复:事件只包含必要信息。消费者需要更多数据?通过事件中的 ID 去查询。
反模式 6:事件黑洞
事件被消费后没有任何可观测的输出------不知道处理成功了还是失败了。
修复:每个消费者的处理结果(成功/失败/跳过)都要有日志和指标。关键事件处理完可以发一个"结果事件"。
反模式 7:过度事件化
一个三层的 CRUD 后台,连用户改个密码都发事件。简单的事情用方法调用就够了。
修复:只在以下场景使用事件------跨服务通信、一个操作多个下游、需要异步处理、需要审计追溯。其他场景用同步调用。
十、事件驱动 vs 其他架构模式
| 维度 | 同步调用 | 事件驱动 | 请求-响应 + 事件混合 |
|---|---|---|---|
| 耦合度 | 高(硬编码调用) | 低(通过 MQ 解耦) | 中等 |
| 延迟 | 高(串行累加) | 低(并行处理) | 视场景而定 |
| 吞吐量 | 受限于最慢环节 | 高(MQ 削峰缓冲) | 高 |
| 一致性 | 强一致(分布式事务) | 最终一致 | 混合 |
| 可观测性 | 调用链清晰 | 需要额外追踪 | 较复杂 |
| 调试难度 | 低(单步调试) | 高(异步链路) | 中等 |
| 适用场景 | 强一致/简单流程 | 高吞吐/多下游/异步 | 大多数企业系统 |
没有银弹:大部分企业系统最终会走向"混合架构"------关键路径用同步调用保证强一致,旁路操作用事件驱动解耦。
十一、常见问题(FAQ)
Q:事件驱动和消息队列是一回事吗?
A:不是。消息队列是事件驱动架构的基础设施之一,但事件驱动是一套架构思想------包括事件设计、拓扑选择、可靠性保证、幂等处理等一系列设计决策。用了 MQ 不代表做了事件驱动,就像用了 Spring 不代表做了微服务。
Q:事件驱动能保证数据一致性吗?
A:能保证,但保证的是"最终一致性"而非"强一致性"。如果业务必须强一致(如银行转账),要么用分布式事务(Saga/TCC),要么这部分用同步调用。事件驱动和强一致性不是对立的------RocketMQ 的事务消息可以在一定程度上兼顾两者。
Q:消费者处理失败了怎么办?
A:三层兜底:① MQ 自动重试(指数退避);② 死信队列 + 告警;③ 人工介入 + 手动重试。关键是消费者要幂等------重试不能产生副作用。
Q:事件太多会不会把 MQ 撑爆?
A:会的。需要做好容量规划:① 合理设置 Topic 分区数;② 事件有过期时间(TTL);③ 监控消费延迟和堆积量;④ 批量事件做合并。
Q:什么时候该用顺序消息?
A:当同一实体的事件有严格的时序要求时。比如"订单创建"必须在"订单支付"之前被消费。通过分区键(如 orderId)保证同一实体的事件进入同一分区,分区内有序。
Q:事件驱动和微服务是什么关系?
A:事件驱动是微服务之间通信的最佳实践之一。微服务解决"服务怎么拆",事件驱动解决"服务怎么连"。同步 RPC 适合强依赖,事件驱动适合松耦合。两者结合使用。
十二、小结
事件驱动架构是分布式系统中最强大、也最容易被误用的架构模式之一。三句话总结:
- 事件驱动的本质是"解耦+异步"------生产者不管谁在消费,消费者不管事件从哪来,中间通过 MQ 做缓冲和路由
- 事件驱动不是银弹------它用"最终一致性"和"架构复杂度"换取"高弹性"和"高扩展性"------适合多下游、高吞吐、异步处理的场景,不适合强一致、简单查询的场景
- 落地事件驱动的三大纪律是"可靠投递+幂等消费+全链路追踪"------做不到这三点,事件驱动就是一个"出了问题查不到"的黑盒
从明天开始,审视你手头的项目:哪些同步调用可以改成事件驱动?哪些事件的可靠性没有保证?消费者的幂等做了吗?
想清楚这些,你的分布式系统就稳了一半。
附录:本文涉及的架构图全部使用 Mermaid 绘制,源码可直接复制到任何支持 Mermaid 的 Markdown 编辑器(Typora / VSCode / GitHub / GitLab / Notion)查看。
关于数环通
数环通 iPaaS 是一款面向企业的低代码集成平台,提供 600+ 应用连接器、可视化流程编排、全链路执行监控等核心能力,帮助企业快速打通系统孤岛,实现业务流程自动化。
标签:#事件驱动架构 #EDA #消息队列 #RocketMQ #Kafka #异步架构 #事件溯源 #CQRS #分布式系统 #微服务 #Webhook #CDC #架构设计 #系统设计 #技术分享
事件驱动架构从概念到落地------让系统像神经反射一样响应变化
🔔 本文 8000+ 字深度原创,含 10+ 张架构图和 iPaaS 事件中心、IoT、电商三大实战案例拆解。创作不易,如果对你有帮助,请点赞 👍 收藏 ⭐ 关注 🔥 三连支持,你的认可是我持续输出的最大动力!
写在前面
做了几年分布式系统之后,我发现一个很有意思的现象:
大部分系统的"慢"和"脆",不是因为单个服务写得差,而是因为服务之间"连"得太紧。
你肯定见过这些场景:
- 用户下了一个订单,系统同步调了库存服务、支付服务、通知服务、积分服务------一个超時全链路挂掉
- 老板说"加一个新的下游系统",你打开代码发现要改上游三四个服务的代码
- 系统吞吐量上不去,因为所有操作都串行执行,一个慢查询拖垮全链路
- Webhook 回调超时了,但业务逻辑还在同步执行,连接池被撑爆
这些问题的根源都是同一个------同步调用的紧耦合架构。
事件驱动架构(Event-Driven Architecture,EDA)就是为了解决这类问题而生的。它的核心思想很简单:系统中的组件不直接调用彼此,而是通过"事件"来通信------我做了什么(事件),谁关心谁来听。
本文从事件驱动的本质出发,拆解核心概念、架构拓扑、消息可靠投递、事件溯源、在 iPaaS 平台事件中心的实战落地,以及我在真实项目中踩过的坑。不讲教科书定义,只讲"能落地"的东西。
一、事件驱动架构到底在解决什么问题?
1.1 同步调用的"三重困境"
在传统的 RPC/HTTP 同步调用架构中,系统面临三重困境:
#mermaid-svg-jNQdkODuinvZ4p2X{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jNQdkODuinvZ4p2X .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jNQdkODuinvZ4p2X .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jNQdkODuinvZ4p2X .error-icon{fill:#552222;}#mermaid-svg-jNQdkODuinvZ4p2X .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jNQdkODuinvZ4p2X .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jNQdkODuinvZ4p2X .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jNQdkODuinvZ4p2X .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jNQdkODuinvZ4p2X .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jNQdkODuinvZ4p2X .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jNQdkODuinvZ4p2X .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jNQdkODuinvZ4p2X .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jNQdkODuinvZ4p2X .marker.cross{stroke:#333333;}#mermaid-svg-jNQdkODuinvZ4p2X svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jNQdkODuinvZ4p2X p{margin:0;}#mermaid-svg-jNQdkODuinvZ4p2X .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jNQdkODuinvZ4p2X .cluster-label text{fill:#333;}#mermaid-svg-jNQdkODuinvZ4p2X .cluster-label span{color:#333;}#mermaid-svg-jNQdkODuinvZ4p2X .cluster-label span p{background-color:transparent;}#mermaid-svg-jNQdkODuinvZ4p2X .label text,#mermaid-svg-jNQdkODuinvZ4p2X span{fill:#333;color:#333;}#mermaid-svg-jNQdkODuinvZ4p2X .node rect,#mermaid-svg-jNQdkODuinvZ4p2X .node circle,#mermaid-svg-jNQdkODuinvZ4p2X .node ellipse,#mermaid-svg-jNQdkODuinvZ4p2X .node polygon,#mermaid-svg-jNQdkODuinvZ4p2X .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jNQdkODuinvZ4p2X .rough-node .label text,#mermaid-svg-jNQdkODuinvZ4p2X .node .label text,#mermaid-svg-jNQdkODuinvZ4p2X .image-shape .label,#mermaid-svg-jNQdkODuinvZ4p2X .icon-shape .label{text-anchor:middle;}#mermaid-svg-jNQdkODuinvZ4p2X .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jNQdkODuinvZ4p2X .rough-node .label,#mermaid-svg-jNQdkODuinvZ4p2X .node .label,#mermaid-svg-jNQdkODuinvZ4p2X .image-shape .label,#mermaid-svg-jNQdkODuinvZ4p2X .icon-shape .label{text-align:center;}#mermaid-svg-jNQdkODuinvZ4p2X .node.clickable{cursor:pointer;}#mermaid-svg-jNQdkODuinvZ4p2X .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jNQdkODuinvZ4p2X .arrowheadPath{fill:#333333;}#mermaid-svg-jNQdkODuinvZ4p2X .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jNQdkODuinvZ4p2X .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jNQdkODuinvZ4p2X .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jNQdkODuinvZ4p2X .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jNQdkODuinvZ4p2X .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jNQdkODuinvZ4p2X .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jNQdkODuinvZ4p2X .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jNQdkODuinvZ4p2X .cluster text{fill:#333;}#mermaid-svg-jNQdkODuinvZ4p2X .cluster span{color:#333;}#mermaid-svg-jNQdkODuinvZ4p2X div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jNQdkODuinvZ4p2X .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jNQdkODuinvZ4p2X rect.text{fill:none;stroke-width:0;}#mermaid-svg-jNQdkODuinvZ4p2X .icon-shape,#mermaid-svg-jNQdkODuinvZ4p2X .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jNQdkODuinvZ4p2X .icon-shape p,#mermaid-svg-jNQdkODuinvZ4p2X .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jNQdkODuinvZ4p2X .icon-shape .label rect,#mermaid-svg-jNQdkODuinvZ4p2X .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jNQdkODuinvZ4p2X .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jNQdkODuinvZ4p2X .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jNQdkODuinvZ4p2X :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 困境三:吞吐瓶颈
串行执行
串行执行
串行执行
API入口
处理1
处理2
处理3
困境二:扩展耦合
硬编码调用
硬编码调用
硬编码调用
订单服务
积分服务
风控服务
数据服务
困境一:级联故障
同步调用
同步调用
超时!
订单服务
库存服务
通知服务
全链路阻塞
| 困境 | 表现 | 后果 |
|---|---|---|
| 级联故障 | 下游超时,上游连接堆积 | 一个慢服务拖垮全系统 |
| 扩展耦合 | 加一个下游要改上游代码 | 系统无法独立演进 |
| 吞吐瓶颈 | 串行同步执行 | 整体延迟 = 各环节延迟之和 |
1.2 事件驱动的核心主张
事件驱动架构用一句话概括:系统中的事件是核心,组件通过异步产生和响应事件来协作,解耦生产者和消费者。
#mermaid-svg-RURz89mARnW2Wv71{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RURz89mARnW2Wv71 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RURz89mARnW2Wv71 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RURz89mARnW2Wv71 .error-icon{fill:#552222;}#mermaid-svg-RURz89mARnW2Wv71 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RURz89mARnW2Wv71 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RURz89mARnW2Wv71 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RURz89mARnW2Wv71 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RURz89mARnW2Wv71 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RURz89mARnW2Wv71 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RURz89mARnW2Wv71 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RURz89mARnW2Wv71 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RURz89mARnW2Wv71 .marker.cross{stroke:#333333;}#mermaid-svg-RURz89mARnW2Wv71 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RURz89mARnW2Wv71 p{margin:0;}#mermaid-svg-RURz89mARnW2Wv71 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RURz89mARnW2Wv71 .cluster-label text{fill:#333;}#mermaid-svg-RURz89mARnW2Wv71 .cluster-label span{color:#333;}#mermaid-svg-RURz89mARnW2Wv71 .cluster-label span p{background-color:transparent;}#mermaid-svg-RURz89mARnW2Wv71 .label text,#mermaid-svg-RURz89mARnW2Wv71 span{fill:#333;color:#333;}#mermaid-svg-RURz89mARnW2Wv71 .node rect,#mermaid-svg-RURz89mARnW2Wv71 .node circle,#mermaid-svg-RURz89mARnW2Wv71 .node ellipse,#mermaid-svg-RURz89mARnW2Wv71 .node polygon,#mermaid-svg-RURz89mARnW2Wv71 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RURz89mARnW2Wv71 .rough-node .label text,#mermaid-svg-RURz89mARnW2Wv71 .node .label text,#mermaid-svg-RURz89mARnW2Wv71 .image-shape .label,#mermaid-svg-RURz89mARnW2Wv71 .icon-shape .label{text-anchor:middle;}#mermaid-svg-RURz89mARnW2Wv71 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RURz89mARnW2Wv71 .rough-node .label,#mermaid-svg-RURz89mARnW2Wv71 .node .label,#mermaid-svg-RURz89mARnW2Wv71 .image-shape .label,#mermaid-svg-RURz89mARnW2Wv71 .icon-shape .label{text-align:center;}#mermaid-svg-RURz89mARnW2Wv71 .node.clickable{cursor:pointer;}#mermaid-svg-RURz89mARnW2Wv71 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RURz89mARnW2Wv71 .arrowheadPath{fill:#333333;}#mermaid-svg-RURz89mARnW2Wv71 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RURz89mARnW2Wv71 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RURz89mARnW2Wv71 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RURz89mARnW2Wv71 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RURz89mARnW2Wv71 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RURz89mARnW2Wv71 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RURz89mARnW2Wv71 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RURz89mARnW2Wv71 .cluster text{fill:#333;}#mermaid-svg-RURz89mARnW2Wv71 .cluster span{color:#333;}#mermaid-svg-RURz89mARnW2Wv71 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RURz89mARnW2Wv71 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RURz89mARnW2Wv71 rect.text{fill:none;stroke-width:0;}#mermaid-svg-RURz89mARnW2Wv71 .icon-shape,#mermaid-svg-RURz89mARnW2Wv71 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RURz89mARnW2Wv71 .icon-shape p,#mermaid-svg-RURz89mARnW2Wv71 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RURz89mARnW2Wv71 .icon-shape .label rect,#mermaid-svg-RURz89mARnW2Wv71 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RURz89mARnW2Wv71 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RURz89mARnW2Wv71 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RURz89mARnW2Wv71 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 发布事件
发布事件
订阅分发
订阅分发
订阅分发
生产者A
事件总线
Event Broker
生产者B
消费者1
消费者2
消费者3
三个核心主张:
-
解耦(Decoupling):生产者不知道消费者的存在,消费者不知道事件从哪来。加一个新的消费者?注册一个订阅就行,生产者的代码一行不改。
-
异步(Asynchrony):生产者发出事件就返回,不等消费者处理完。整体延迟从"串行累加"变成"并行处理"。
-
弹性(Resilience):消费者挂了?事件在消息队列里等着,恢复后继续消费。不会因为一个节点故障导致全链路崩溃。
1.3 事件驱动适合什么系统?
| 系统类型 | 是否适合 EDA | 原因 |
|---|---|---|
| CRUD 管理后台 | ❌ 不适合 | 请求-响应模型,同步调用更直观 |
| 报表查询系统 | ❌ 不适合 | 用户需要即时结果,异步无意义 |
| IoT 物联网平台 | ✅ 非常适合 | 海量设备异步上报,高吞吐实时处理 |
| 实时推荐系统 | ✅ 非常适合 | 用户行为事件流 → 实时特征计算 → 推荐更新 |
| iPaaS 集成平台 | ✅ 非常适合 | Webhook/CDC/定时触发 → 异步流程编排 |
| 电商交易系统 | ✅ 部分适合 | 下单后的库存、通知、积分适合事件驱动 |
| 金融交易系统 | ⚠️ 谨慎使用 | 高吞吐场景适合,但强一致性场景需额外设计 |
判断标准:你的系统是否存在"一个操作触发多个下游"、"需要高吞吐异步处理"、"下游数量经常变化"这三个特征中的至少一个。
二、事件驱动架构的核心概念
2.1 事件(Event)
事件是"已经发生的事实"------不可变的、轻量级的、带有时间戳的状态变更记录。
#mermaid-svg-40snaFmQ2a0nh5tM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-40snaFmQ2a0nh5tM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-40snaFmQ2a0nh5tM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-40snaFmQ2a0nh5tM .error-icon{fill:#552222;}#mermaid-svg-40snaFmQ2a0nh5tM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-40snaFmQ2a0nh5tM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-40snaFmQ2a0nh5tM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-40snaFmQ2a0nh5tM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-40snaFmQ2a0nh5tM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-40snaFmQ2a0nh5tM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-40snaFmQ2a0nh5tM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-40snaFmQ2a0nh5tM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-40snaFmQ2a0nh5tM .marker.cross{stroke:#333333;}#mermaid-svg-40snaFmQ2a0nh5tM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-40snaFmQ2a0nh5tM p{margin:0;}#mermaid-svg-40snaFmQ2a0nh5tM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-40snaFmQ2a0nh5tM .cluster-label text{fill:#333;}#mermaid-svg-40snaFmQ2a0nh5tM .cluster-label span{color:#333;}#mermaid-svg-40snaFmQ2a0nh5tM .cluster-label span p{background-color:transparent;}#mermaid-svg-40snaFmQ2a0nh5tM .label text,#mermaid-svg-40snaFmQ2a0nh5tM span{fill:#333;color:#333;}#mermaid-svg-40snaFmQ2a0nh5tM .node rect,#mermaid-svg-40snaFmQ2a0nh5tM .node circle,#mermaid-svg-40snaFmQ2a0nh5tM .node ellipse,#mermaid-svg-40snaFmQ2a0nh5tM .node polygon,#mermaid-svg-40snaFmQ2a0nh5tM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-40snaFmQ2a0nh5tM .rough-node .label text,#mermaid-svg-40snaFmQ2a0nh5tM .node .label text,#mermaid-svg-40snaFmQ2a0nh5tM .image-shape .label,#mermaid-svg-40snaFmQ2a0nh5tM .icon-shape .label{text-anchor:middle;}#mermaid-svg-40snaFmQ2a0nh5tM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-40snaFmQ2a0nh5tM .rough-node .label,#mermaid-svg-40snaFmQ2a0nh5tM .node .label,#mermaid-svg-40snaFmQ2a0nh5tM .image-shape .label,#mermaid-svg-40snaFmQ2a0nh5tM .icon-shape .label{text-align:center;}#mermaid-svg-40snaFmQ2a0nh5tM .node.clickable{cursor:pointer;}#mermaid-svg-40snaFmQ2a0nh5tM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-40snaFmQ2a0nh5tM .arrowheadPath{fill:#333333;}#mermaid-svg-40snaFmQ2a0nh5tM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-40snaFmQ2a0nh5tM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-40snaFmQ2a0nh5tM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-40snaFmQ2a0nh5tM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-40snaFmQ2a0nh5tM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-40snaFmQ2a0nh5tM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-40snaFmQ2a0nh5tM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-40snaFmQ2a0nh5tM .cluster text{fill:#333;}#mermaid-svg-40snaFmQ2a0nh5tM .cluster span{color:#333;}#mermaid-svg-40snaFmQ2a0nh5tM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-40snaFmQ2a0nh5tM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-40snaFmQ2a0nh5tM rect.text{fill:none;stroke-width:0;}#mermaid-svg-40snaFmQ2a0nh5tM .icon-shape,#mermaid-svg-40snaFmQ2a0nh5tM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-40snaFmQ2a0nh5tM .icon-shape p,#mermaid-svg-40snaFmQ2a0nh5tM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-40snaFmQ2a0nh5tM .icon-shape .label rect,#mermaid-svg-40snaFmQ2a0nh5tM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-40snaFmQ2a0nh5tM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-40snaFmQ2a0nh5tM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-40snaFmQ2a0nh5tM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 事件的本质
事件名(过去式)
OrderCreated
事件载荷(Payload)
订单ID/金额/商品列表
元数据(Metadata)
时间戳/来源/追踪ID
事件和命令(Command)的区别:
| 维度 | 事件(Event) | 命令(Command) |
|---|---|---|
| 语义 | "已经发生了什么" | "请去做什么" |
| 命名 | 过去式:OrderCreated |
祈使句:CreateOrder |
| 目标 | 无特定目标,广播给所有订阅者 | 有明确目标,发给指定处理器 |
| 耦合度 | 低(生产者不知道谁在消费) | 高(调用方知道被调用方) |
| 失败处理 | 消费者各自重试 | 调用方重试或回滚 |
java
// 事件:已经发生的事实
public record OrderCreatedEvent(
String orderId,
String userId,
BigDecimal totalAmount,
List<OrderItem> items,
Instant createdAt // 时间戳
) {}
// 命令:要求执行的操作
public record CreateOrderCommand(
String userId,
List<OrderItem> items
) {}
2.2 事件通道(Event Channel)
事件从生产者到消费者的传输管道。根据传输方式不同,分为三种:
#mermaid-svg-TknrVCHhrLAf2QZD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TknrVCHhrLAf2QZD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TknrVCHhrLAf2QZD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TknrVCHhrLAf2QZD .error-icon{fill:#552222;}#mermaid-svg-TknrVCHhrLAf2QZD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TknrVCHhrLAf2QZD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TknrVCHhrLAf2QZD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TknrVCHhrLAf2QZD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TknrVCHhrLAf2QZD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TknrVCHhrLAf2QZD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TknrVCHhrLAf2QZD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TknrVCHhrLAf2QZD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TknrVCHhrLAf2QZD .marker.cross{stroke:#333333;}#mermaid-svg-TknrVCHhrLAf2QZD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TknrVCHhrLAf2QZD p{margin:0;}#mermaid-svg-TknrVCHhrLAf2QZD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TknrVCHhrLAf2QZD .cluster-label text{fill:#333;}#mermaid-svg-TknrVCHhrLAf2QZD .cluster-label span{color:#333;}#mermaid-svg-TknrVCHhrLAf2QZD .cluster-label span p{background-color:transparent;}#mermaid-svg-TknrVCHhrLAf2QZD .label text,#mermaid-svg-TknrVCHhrLAf2QZD span{fill:#333;color:#333;}#mermaid-svg-TknrVCHhrLAf2QZD .node rect,#mermaid-svg-TknrVCHhrLAf2QZD .node circle,#mermaid-svg-TknrVCHhrLAf2QZD .node ellipse,#mermaid-svg-TknrVCHhrLAf2QZD .node polygon,#mermaid-svg-TknrVCHhrLAf2QZD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TknrVCHhrLAf2QZD .rough-node .label text,#mermaid-svg-TknrVCHhrLAf2QZD .node .label text,#mermaid-svg-TknrVCHhrLAf2QZD .image-shape .label,#mermaid-svg-TknrVCHhrLAf2QZD .icon-shape .label{text-anchor:middle;}#mermaid-svg-TknrVCHhrLAf2QZD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TknrVCHhrLAf2QZD .rough-node .label,#mermaid-svg-TknrVCHhrLAf2QZD .node .label,#mermaid-svg-TknrVCHhrLAf2QZD .image-shape .label,#mermaid-svg-TknrVCHhrLAf2QZD .icon-shape .label{text-align:center;}#mermaid-svg-TknrVCHhrLAf2QZD .node.clickable{cursor:pointer;}#mermaid-svg-TknrVCHhrLAf2QZD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TknrVCHhrLAf2QZD .arrowheadPath{fill:#333333;}#mermaid-svg-TknrVCHhrLAf2QZD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TknrVCHhrLAf2QZD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TknrVCHhrLAf2QZD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TknrVCHhrLAf2QZD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TknrVCHhrLAf2QZD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TknrVCHhrLAf2QZD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TknrVCHhrLAf2QZD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TknrVCHhrLAf2QZD .cluster text{fill:#333;}#mermaid-svg-TknrVCHhrLAf2QZD .cluster span{color:#333;}#mermaid-svg-TknrVCHhrLAf2QZD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TknrVCHhrLAf2QZD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TknrVCHhrLAf2QZD rect.text{fill:none;stroke-width:0;}#mermaid-svg-TknrVCHhrLAf2QZD .icon-shape,#mermaid-svg-TknrVCHhrLAf2QZD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TknrVCHhrLAf2QZD .icon-shape p,#mermaid-svg-TknrVCHhrLAf2QZD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TknrVCHhrLAf2QZD .icon-shape .label rect,#mermaid-svg-TknrVCHhrLAf2QZD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TknrVCHhrLAf2QZD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TknrVCHhrLAf2QZD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TknrVCHhrLAf2QZD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 请求-响应通道
请求事件
响应事件
请求者
请求队列
响应者
响应队列
发布-订阅通道
生产者
Topic
消费者A
消费者B
消费者C
点对点通道
生产者
队列
唯一消费者
| 通道类型 | 典型实现 | 适用场景 |
|---|---|---|
| 点对点 | RabbitMQ Queue、RocketMQ 普通消息 | 任务分发,一个事件只需一个消费者处理 |
| 发布-订阅 | Kafka Topic、RocketMQ Topic | 事件广播,一个事件需要多个消费者各自处理 |
| 请求-响应 | NATS Request-Reply | 需要异步获取结果的场景 |
2.3 事件代理(Event Broker)
事件代理是事件驱动架构的"中枢神经"------所有事件都经过它来路由和分发。
#mermaid-svg-8qcp7oeZDGL4pONH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8qcp7oeZDGL4pONH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8qcp7oeZDGL4pONH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8qcp7oeZDGL4pONH .error-icon{fill:#552222;}#mermaid-svg-8qcp7oeZDGL4pONH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8qcp7oeZDGL4pONH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8qcp7oeZDGL4pONH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8qcp7oeZDGL4pONH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8qcp7oeZDGL4pONH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8qcp7oeZDGL4pONH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8qcp7oeZDGL4pONH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8qcp7oeZDGL4pONH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8qcp7oeZDGL4pONH .marker.cross{stroke:#333333;}#mermaid-svg-8qcp7oeZDGL4pONH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8qcp7oeZDGL4pONH p{margin:0;}#mermaid-svg-8qcp7oeZDGL4pONH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8qcp7oeZDGL4pONH .cluster-label text{fill:#333;}#mermaid-svg-8qcp7oeZDGL4pONH .cluster-label span{color:#333;}#mermaid-svg-8qcp7oeZDGL4pONH .cluster-label span p{background-color:transparent;}#mermaid-svg-8qcp7oeZDGL4pONH .label text,#mermaid-svg-8qcp7oeZDGL4pONH span{fill:#333;color:#333;}#mermaid-svg-8qcp7oeZDGL4pONH .node rect,#mermaid-svg-8qcp7oeZDGL4pONH .node circle,#mermaid-svg-8qcp7oeZDGL4pONH .node ellipse,#mermaid-svg-8qcp7oeZDGL4pONH .node polygon,#mermaid-svg-8qcp7oeZDGL4pONH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8qcp7oeZDGL4pONH .rough-node .label text,#mermaid-svg-8qcp7oeZDGL4pONH .node .label text,#mermaid-svg-8qcp7oeZDGL4pONH .image-shape .label,#mermaid-svg-8qcp7oeZDGL4pONH .icon-shape .label{text-anchor:middle;}#mermaid-svg-8qcp7oeZDGL4pONH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8qcp7oeZDGL4pONH .rough-node .label,#mermaid-svg-8qcp7oeZDGL4pONH .node .label,#mermaid-svg-8qcp7oeZDGL4pONH .image-shape .label,#mermaid-svg-8qcp7oeZDGL4pONH .icon-shape .label{text-align:center;}#mermaid-svg-8qcp7oeZDGL4pONH .node.clickable{cursor:pointer;}#mermaid-svg-8qcp7oeZDGL4pONH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8qcp7oeZDGL4pONH .arrowheadPath{fill:#333333;}#mermaid-svg-8qcp7oeZDGL4pONH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8qcp7oeZDGL4pONH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8qcp7oeZDGL4pONH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8qcp7oeZDGL4pONH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8qcp7oeZDGL4pONH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8qcp7oeZDGL4pONH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8qcp7oeZDGL4pONH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8qcp7oeZDGL4pONH .cluster text{fill:#333;}#mermaid-svg-8qcp7oeZDGL4pONH .cluster span{color:#333;}#mermaid-svg-8qcp7oeZDGL4pONH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8qcp7oeZDGL4pONH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8qcp7oeZDGL4pONH rect.text{fill:none;stroke-width:0;}#mermaid-svg-8qcp7oeZDGL4pONH .icon-shape,#mermaid-svg-8qcp7oeZDGL4pONH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8qcp7oeZDGL4pONH .icon-shape p,#mermaid-svg-8qcp7oeZDGL4pONH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8qcp7oeZDGL4pONH .icon-shape .label rect,#mermaid-svg-8qcp7oeZDGL4pONH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8qcp7oeZDGL4pONH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8qcp7oeZDGL4pONH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8qcp7oeZDGL4pONH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 事件代理
事件接收
协议适配/鉴权
事件存储
持久化/回放
事件路由
Topic/Tag过滤
事件投递
推送/拉取
生产者1
生产者2
消费者1
消费者2
消费者3
主流消息中间件的选型对比:
| 中间件 | 吞吐量 | 延迟 | 消息模型 | 适用场景 |
|---|---|---|---|---|
| RocketMQ | 十万级/秒 | 毫秒级 | 发布-订阅+点对点 | 电商/金融/企业集成 |
| Kafka | 百万级/秒 | 毫秒级 | 发布-订阅(Log) | 日志/大数据/IoT |
| RabbitMQ | 万级/秒 | 微秒级 | 灵活路由 | 企业内部集成/任务队列 |
| Pulsar | 百万级/秒 | 毫秒级 | 多租户+分层存储 | 超大规模多租户场景 |
我们在 iPaaS 平台选择了 RocketMQ------它的事务消息、顺序消息、延时消息等企业级特性,非常契合集成平台的场景。
2.4 事件处理的三种模式
#mermaid-svg-HhTUkAXIssch2uaE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HhTUkAXIssch2uaE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HhTUkAXIssch2uaE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HhTUkAXIssch2uaE .error-icon{fill:#552222;}#mermaid-svg-HhTUkAXIssch2uaE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HhTUkAXIssch2uaE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HhTUkAXIssch2uaE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HhTUkAXIssch2uaE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HhTUkAXIssch2uaE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HhTUkAXIssch2uaE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HhTUkAXIssch2uaE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HhTUkAXIssch2uaE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HhTUkAXIssch2uaE .marker.cross{stroke:#333333;}#mermaid-svg-HhTUkAXIssch2uaE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HhTUkAXIssch2uaE p{margin:0;}#mermaid-svg-HhTUkAXIssch2uaE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HhTUkAXIssch2uaE .cluster-label text{fill:#333;}#mermaid-svg-HhTUkAXIssch2uaE .cluster-label span{color:#333;}#mermaid-svg-HhTUkAXIssch2uaE .cluster-label span p{background-color:transparent;}#mermaid-svg-HhTUkAXIssch2uaE .label text,#mermaid-svg-HhTUkAXIssch2uaE span{fill:#333;color:#333;}#mermaid-svg-HhTUkAXIssch2uaE .node rect,#mermaid-svg-HhTUkAXIssch2uaE .node circle,#mermaid-svg-HhTUkAXIssch2uaE .node ellipse,#mermaid-svg-HhTUkAXIssch2uaE .node polygon,#mermaid-svg-HhTUkAXIssch2uaE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HhTUkAXIssch2uaE .rough-node .label text,#mermaid-svg-HhTUkAXIssch2uaE .node .label text,#mermaid-svg-HhTUkAXIssch2uaE .image-shape .label,#mermaid-svg-HhTUkAXIssch2uaE .icon-shape .label{text-anchor:middle;}#mermaid-svg-HhTUkAXIssch2uaE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HhTUkAXIssch2uaE .rough-node .label,#mermaid-svg-HhTUkAXIssch2uaE .node .label,#mermaid-svg-HhTUkAXIssch2uaE .image-shape .label,#mermaid-svg-HhTUkAXIssch2uaE .icon-shape .label{text-align:center;}#mermaid-svg-HhTUkAXIssch2uaE .node.clickable{cursor:pointer;}#mermaid-svg-HhTUkAXIssch2uaE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HhTUkAXIssch2uaE .arrowheadPath{fill:#333333;}#mermaid-svg-HhTUkAXIssch2uaE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HhTUkAXIssch2uaE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HhTUkAXIssch2uaE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HhTUkAXIssch2uaE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HhTUkAXIssch2uaE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HhTUkAXIssch2uaE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HhTUkAXIssch2uaE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HhTUkAXIssch2uaE .cluster text{fill:#333;}#mermaid-svg-HhTUkAXIssch2uaE .cluster span{color:#333;}#mermaid-svg-HhTUkAXIssch2uaE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HhTUkAXIssch2uaE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HhTUkAXIssch2uaE rect.text{fill:none;stroke-width:0;}#mermaid-svg-HhTUkAXIssch2uaE .icon-shape,#mermaid-svg-HhTUkAXIssch2uaE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HhTUkAXIssch2uaE .icon-shape p,#mermaid-svg-HhTUkAXIssch2uaE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HhTUkAXIssch2uaE .icon-shape .label rect,#mermaid-svg-HhTUkAXIssch2uaE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HhTUkAXIssch2uaE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HhTUkAXIssch2uaE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HhTUkAXIssch2uaE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Saga 编排
成功
失败
订单事件
步骤1: 扣库存
步骤2: 扣款
补偿: 恢复库存
复杂事件处理(CEP)
事件流
CEP引擎
模式匹配/时间窗口
触发动作
简单事件处理
事件
单处理器
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 简单事件处理 | 一个事件触发一个处理器 | 大多数业务场景 |
| 复杂事件处理(CEP) | 多个事件组合满足模式后触发 | 风控/告警/IoT 异常检测 |
| Saga 编排 | 长事务拆成多个事件驱动的步骤 | 跨服务的分布式事务 |
三、事件驱动架构的四种拓扑
3.1 中介者拓扑(Mediator Topology)
#mermaid-svg-CenZqxHc5n47vvBb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CenZqxHc5n47vvBb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CenZqxHc5n47vvBb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CenZqxHc5n47vvBb .error-icon{fill:#552222;}#mermaid-svg-CenZqxHc5n47vvBb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CenZqxHc5n47vvBb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CenZqxHc5n47vvBb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CenZqxHc5n47vvBb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CenZqxHc5n47vvBb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CenZqxHc5n47vvBb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CenZqxHc5n47vvBb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CenZqxHc5n47vvBb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CenZqxHc5n47vvBb .marker.cross{stroke:#333333;}#mermaid-svg-CenZqxHc5n47vvBb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CenZqxHc5n47vvBb p{margin:0;}#mermaid-svg-CenZqxHc5n47vvBb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CenZqxHc5n47vvBb .cluster-label text{fill:#333;}#mermaid-svg-CenZqxHc5n47vvBb .cluster-label span{color:#333;}#mermaid-svg-CenZqxHc5n47vvBb .cluster-label span p{background-color:transparent;}#mermaid-svg-CenZqxHc5n47vvBb .label text,#mermaid-svg-CenZqxHc5n47vvBb span{fill:#333;color:#333;}#mermaid-svg-CenZqxHc5n47vvBb .node rect,#mermaid-svg-CenZqxHc5n47vvBb .node circle,#mermaid-svg-CenZqxHc5n47vvBb .node ellipse,#mermaid-svg-CenZqxHc5n47vvBb .node polygon,#mermaid-svg-CenZqxHc5n47vvBb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CenZqxHc5n47vvBb .rough-node .label text,#mermaid-svg-CenZqxHc5n47vvBb .node .label text,#mermaid-svg-CenZqxHc5n47vvBb .image-shape .label,#mermaid-svg-CenZqxHc5n47vvBb .icon-shape .label{text-anchor:middle;}#mermaid-svg-CenZqxHc5n47vvBb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CenZqxHc5n47vvBb .rough-node .label,#mermaid-svg-CenZqxHc5n47vvBb .node .label,#mermaid-svg-CenZqxHc5n47vvBb .image-shape .label,#mermaid-svg-CenZqxHc5n47vvBb .icon-shape .label{text-align:center;}#mermaid-svg-CenZqxHc5n47vvBb .node.clickable{cursor:pointer;}#mermaid-svg-CenZqxHc5n47vvBb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CenZqxHc5n47vvBb .arrowheadPath{fill:#333333;}#mermaid-svg-CenZqxHc5n47vvBb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CenZqxHc5n47vvBb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CenZqxHc5n47vvBb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CenZqxHc5n47vvBb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CenZqxHc5n47vvBb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CenZqxHc5n47vvBb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CenZqxHc5n47vvBb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CenZqxHc5n47vvBb .cluster text{fill:#333;}#mermaid-svg-CenZqxHc5n47vvBb .cluster span{color:#333;}#mermaid-svg-CenZqxHc5n47vvBb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CenZqxHc5n47vvBb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CenZqxHc5n47vvBb rect.text{fill:none;stroke-width:0;}#mermaid-svg-CenZqxHc5n47vvBb .icon-shape,#mermaid-svg-CenZqxHc5n47vvBb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CenZqxHc5n47vvBb .icon-shape p,#mermaid-svg-CenZqxHc5n47vvBb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CenZqxHc5n47vvBb .icon-shape .label rect,#mermaid-svg-CenZqxHc5n47vvBb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CenZqxHc5n47vvBb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CenZqxHc5n47vvBb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CenZqxHc5n47vvBb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 中介者拓扑
步骤1
事件A完成
步骤2
事件B完成
步骤3
初始事件
事件中介者
编排步骤
事件处理器A
事件处理器B
事件处理器C
特点:有一个"指挥官"决定事件处理的顺序。类似流程引擎------事件到达后,由中介者编排处理步骤。
适用场景:业务流程有明确的步骤顺序,如订单处理(校验→扣库存→支付→发货)。
3.2 代理者拓扑(Broker Topology)
#mermaid-svg-HBdIEfMzQjYjSlpx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HBdIEfMzQjYjSlpx .error-icon{fill:#552222;}#mermaid-svg-HBdIEfMzQjYjSlpx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HBdIEfMzQjYjSlpx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HBdIEfMzQjYjSlpx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HBdIEfMzQjYjSlpx .marker.cross{stroke:#333333;}#mermaid-svg-HBdIEfMzQjYjSlpx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HBdIEfMzQjYjSlpx p{margin:0;}#mermaid-svg-HBdIEfMzQjYjSlpx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HBdIEfMzQjYjSlpx .cluster-label text{fill:#333;}#mermaid-svg-HBdIEfMzQjYjSlpx .cluster-label span{color:#333;}#mermaid-svg-HBdIEfMzQjYjSlpx .cluster-label span p{background-color:transparent;}#mermaid-svg-HBdIEfMzQjYjSlpx .label text,#mermaid-svg-HBdIEfMzQjYjSlpx span{fill:#333;color:#333;}#mermaid-svg-HBdIEfMzQjYjSlpx .node rect,#mermaid-svg-HBdIEfMzQjYjSlpx .node circle,#mermaid-svg-HBdIEfMzQjYjSlpx .node ellipse,#mermaid-svg-HBdIEfMzQjYjSlpx .node polygon,#mermaid-svg-HBdIEfMzQjYjSlpx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HBdIEfMzQjYjSlpx .rough-node .label text,#mermaid-svg-HBdIEfMzQjYjSlpx .node .label text,#mermaid-svg-HBdIEfMzQjYjSlpx .image-shape .label,#mermaid-svg-HBdIEfMzQjYjSlpx .icon-shape .label{text-anchor:middle;}#mermaid-svg-HBdIEfMzQjYjSlpx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HBdIEfMzQjYjSlpx .rough-node .label,#mermaid-svg-HBdIEfMzQjYjSlpx .node .label,#mermaid-svg-HBdIEfMzQjYjSlpx .image-shape .label,#mermaid-svg-HBdIEfMzQjYjSlpx .icon-shape .label{text-align:center;}#mermaid-svg-HBdIEfMzQjYjSlpx .node.clickable{cursor:pointer;}#mermaid-svg-HBdIEfMzQjYjSlpx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HBdIEfMzQjYjSlpx .arrowheadPath{fill:#333333;}#mermaid-svg-HBdIEfMzQjYjSlpx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HBdIEfMzQjYjSlpx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HBdIEfMzQjYjSlpx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HBdIEfMzQjYjSlpx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HBdIEfMzQjYjSlpx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HBdIEfMzQjYjSlpx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HBdIEfMzQjYjSlpx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HBdIEfMzQjYjSlpx .cluster text{fill:#333;}#mermaid-svg-HBdIEfMzQjYjSlpx .cluster span{color:#333;}#mermaid-svg-HBdIEfMzQjYjSlpx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HBdIEfMzQjYjSlpx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HBdIEfMzQjYjSlpx rect.text{fill:none;stroke-width:0;}#mermaid-svg-HBdIEfMzQjYjSlpx .icon-shape,#mermaid-svg-HBdIEfMzQjYjSlpx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HBdIEfMzQjYjSlpx .icon-shape p,#mermaid-svg-HBdIEfMzQjYjSlpx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HBdIEfMzQjYjSlpx .icon-shape .label rect,#mermaid-svg-HBdIEfMzQjYjSlpx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HBdIEfMzQjYjSlpx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HBdIEfMzQjYjSlpx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HBdIEfMzQjYjSlpx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 代理者拓扑
产生事件B
产生事件C
事件A
处理器1
处理器2
处理器3
特点:没有中心编排者,每个处理器消费事件后产生新事件,下一个处理器自动接力。像"接力赛"------每一棒跑完把接力棒传下去。
适用场景:处理链路比较固定、步骤间有自然的数据流转关系。
3.3 广播拓扑(Broadcast Topology)
#mermaid-svg-AqDYtNo2DK1Brlyn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AqDYtNo2DK1Brlyn .error-icon{fill:#552222;}#mermaid-svg-AqDYtNo2DK1Brlyn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AqDYtNo2DK1Brlyn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AqDYtNo2DK1Brlyn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AqDYtNo2DK1Brlyn .marker.cross{stroke:#333333;}#mermaid-svg-AqDYtNo2DK1Brlyn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AqDYtNo2DK1Brlyn p{margin:0;}#mermaid-svg-AqDYtNo2DK1Brlyn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AqDYtNo2DK1Brlyn .cluster-label text{fill:#333;}#mermaid-svg-AqDYtNo2DK1Brlyn .cluster-label span{color:#333;}#mermaid-svg-AqDYtNo2DK1Brlyn .cluster-label span p{background-color:transparent;}#mermaid-svg-AqDYtNo2DK1Brlyn .label text,#mermaid-svg-AqDYtNo2DK1Brlyn span{fill:#333;color:#333;}#mermaid-svg-AqDYtNo2DK1Brlyn .node rect,#mermaid-svg-AqDYtNo2DK1Brlyn .node circle,#mermaid-svg-AqDYtNo2DK1Brlyn .node ellipse,#mermaid-svg-AqDYtNo2DK1Brlyn .node polygon,#mermaid-svg-AqDYtNo2DK1Brlyn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AqDYtNo2DK1Brlyn .rough-node .label text,#mermaid-svg-AqDYtNo2DK1Brlyn .node .label text,#mermaid-svg-AqDYtNo2DK1Brlyn .image-shape .label,#mermaid-svg-AqDYtNo2DK1Brlyn .icon-shape .label{text-anchor:middle;}#mermaid-svg-AqDYtNo2DK1Brlyn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AqDYtNo2DK1Brlyn .rough-node .label,#mermaid-svg-AqDYtNo2DK1Brlyn .node .label,#mermaid-svg-AqDYtNo2DK1Brlyn .image-shape .label,#mermaid-svg-AqDYtNo2DK1Brlyn .icon-shape .label{text-align:center;}#mermaid-svg-AqDYtNo2DK1Brlyn .node.clickable{cursor:pointer;}#mermaid-svg-AqDYtNo2DK1Brlyn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AqDYtNo2DK1Brlyn .arrowheadPath{fill:#333333;}#mermaid-svg-AqDYtNo2DK1Brlyn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AqDYtNo2DK1Brlyn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AqDYtNo2DK1Brlyn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AqDYtNo2DK1Brlyn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AqDYtNo2DK1Brlyn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AqDYtNo2DK1Brlyn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AqDYtNo2DK1Brlyn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AqDYtNo2DK1Brlyn .cluster text{fill:#333;}#mermaid-svg-AqDYtNo2DK1Brlyn .cluster span{color:#333;}#mermaid-svg-AqDYtNo2DK1Brlyn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AqDYtNo2DK1Brlyn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AqDYtNo2DK1Brlyn rect.text{fill:none;stroke-width:0;}#mermaid-svg-AqDYtNo2DK1Brlyn .icon-shape,#mermaid-svg-AqDYtNo2DK1Brlyn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AqDYtNo2DK1Brlyn .icon-shape p,#mermaid-svg-AqDYtNo2DK1Brlyn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AqDYtNo2DK1Brlyn .icon-shape .label rect,#mermaid-svg-AqDYtNo2DK1Brlyn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AqDYtNo2DK1Brlyn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AqDYtNo2DK1Brlyn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AqDYtNo2DK1Brlyn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 广播拓扑
事件源
事件总线
处理器1
发通知
处理器2
更新缓存
处理器3
写日志
处理器4
推数据仓库
特点:一个事件同时触发多个独立的处理器,彼此之间无依赖关系。
适用场景:一个业务事件需要多个系统各自独立响应。比如"订单已支付"需要通知、日志、缓存、数仓各自处理。
3.4 中介者-代理者混合拓扑
实际系统大多是混合拓扑------关键流程用中介者编排,非关键旁路用广播分发。
#mermaid-svg-wZDvXTmrOXJg1Axk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wZDvXTmrOXJg1Axk .error-icon{fill:#552222;}#mermaid-svg-wZDvXTmrOXJg1Axk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wZDvXTmrOXJg1Axk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wZDvXTmrOXJg1Axk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wZDvXTmrOXJg1Axk .marker.cross{stroke:#333333;}#mermaid-svg-wZDvXTmrOXJg1Axk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wZDvXTmrOXJg1Axk p{margin:0;}#mermaid-svg-wZDvXTmrOXJg1Axk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wZDvXTmrOXJg1Axk .cluster-label text{fill:#333;}#mermaid-svg-wZDvXTmrOXJg1Axk .cluster-label span{color:#333;}#mermaid-svg-wZDvXTmrOXJg1Axk .cluster-label span p{background-color:transparent;}#mermaid-svg-wZDvXTmrOXJg1Axk .label text,#mermaid-svg-wZDvXTmrOXJg1Axk span{fill:#333;color:#333;}#mermaid-svg-wZDvXTmrOXJg1Axk .node rect,#mermaid-svg-wZDvXTmrOXJg1Axk .node circle,#mermaid-svg-wZDvXTmrOXJg1Axk .node ellipse,#mermaid-svg-wZDvXTmrOXJg1Axk .node polygon,#mermaid-svg-wZDvXTmrOXJg1Axk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wZDvXTmrOXJg1Axk .rough-node .label text,#mermaid-svg-wZDvXTmrOXJg1Axk .node .label text,#mermaid-svg-wZDvXTmrOXJg1Axk .image-shape .label,#mermaid-svg-wZDvXTmrOXJg1Axk .icon-shape .label{text-anchor:middle;}#mermaid-svg-wZDvXTmrOXJg1Axk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wZDvXTmrOXJg1Axk .rough-node .label,#mermaid-svg-wZDvXTmrOXJg1Axk .node .label,#mermaid-svg-wZDvXTmrOXJg1Axk .image-shape .label,#mermaid-svg-wZDvXTmrOXJg1Axk .icon-shape .label{text-align:center;}#mermaid-svg-wZDvXTmrOXJg1Axk .node.clickable{cursor:pointer;}#mermaid-svg-wZDvXTmrOXJg1Axk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wZDvXTmrOXJg1Axk .arrowheadPath{fill:#333333;}#mermaid-svg-wZDvXTmrOXJg1Axk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wZDvXTmrOXJg1Axk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wZDvXTmrOXJg1Axk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wZDvXTmrOXJg1Axk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wZDvXTmrOXJg1Axk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wZDvXTmrOXJg1Axk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wZDvXTmrOXJg1Axk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wZDvXTmrOXJg1Axk .cluster text{fill:#333;}#mermaid-svg-wZDvXTmrOXJg1Axk .cluster span{color:#333;}#mermaid-svg-wZDvXTmrOXJg1Axk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wZDvXTmrOXJg1Axk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wZDvXTmrOXJg1Axk rect.text{fill:none;stroke-width:0;}#mermaid-svg-wZDvXTmrOXJg1Axk .icon-shape,#mermaid-svg-wZDvXTmrOXJg1Axk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wZDvXTmrOXJg1Axk .icon-shape p,#mermaid-svg-wZDvXTmrOXJg1Axk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wZDvXTmrOXJg1Axk .icon-shape .label rect,#mermaid-svg-wZDvXTmrOXJg1Axk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wZDvXTmrOXJg1Axk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wZDvXTmrOXJg1Axk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wZDvXTmrOXJg1Axk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 步骤1
库存已扣
步骤2
扣款成功
订单已完成
订单创建事件
订单中介者
扣库存
扣款
订单完成事件
广播总线
通知服务
日志服务
积分服务
数据仓库
四、iPaaS 事件中心实战
理论讲够了,来看我们 iPaaS 平台的真实事件驱动架构。
4.1 事件中心在 iPaaS 中的定位
iPaaS 是一个集成平台,核心使命是"让不同的系统通过事件联动起来"。事件中心是整个平台的"神经中枢":
#mermaid-svg-cM5E1rWmm4sVsVZq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cM5E1rWmm4sVsVZq .error-icon{fill:#552222;}#mermaid-svg-cM5E1rWmm4sVsVZq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cM5E1rWmm4sVsVZq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cM5E1rWmm4sVsVZq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cM5E1rWmm4sVsVZq .marker.cross{stroke:#333333;}#mermaid-svg-cM5E1rWmm4sVsVZq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cM5E1rWmm4sVsVZq p{margin:0;}#mermaid-svg-cM5E1rWmm4sVsVZq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cM5E1rWmm4sVsVZq .cluster-label text{fill:#333;}#mermaid-svg-cM5E1rWmm4sVsVZq .cluster-label span{color:#333;}#mermaid-svg-cM5E1rWmm4sVsVZq .cluster-label span p{background-color:transparent;}#mermaid-svg-cM5E1rWmm4sVsVZq .label text,#mermaid-svg-cM5E1rWmm4sVsVZq span{fill:#333;color:#333;}#mermaid-svg-cM5E1rWmm4sVsVZq .node rect,#mermaid-svg-cM5E1rWmm4sVsVZq .node circle,#mermaid-svg-cM5E1rWmm4sVsVZq .node ellipse,#mermaid-svg-cM5E1rWmm4sVsVZq .node polygon,#mermaid-svg-cM5E1rWmm4sVsVZq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cM5E1rWmm4sVsVZq .rough-node .label text,#mermaid-svg-cM5E1rWmm4sVsVZq .node .label text,#mermaid-svg-cM5E1rWmm4sVsVZq .image-shape .label,#mermaid-svg-cM5E1rWmm4sVsVZq .icon-shape .label{text-anchor:middle;}#mermaid-svg-cM5E1rWmm4sVsVZq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cM5E1rWmm4sVsVZq .rough-node .label,#mermaid-svg-cM5E1rWmm4sVsVZq .node .label,#mermaid-svg-cM5E1rWmm4sVsVZq .image-shape .label,#mermaid-svg-cM5E1rWmm4sVsVZq .icon-shape .label{text-align:center;}#mermaid-svg-cM5E1rWmm4sVsVZq .node.clickable{cursor:pointer;}#mermaid-svg-cM5E1rWmm4sVsVZq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cM5E1rWmm4sVsVZq .arrowheadPath{fill:#333333;}#mermaid-svg-cM5E1rWmm4sVsVZq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cM5E1rWmm4sVsVZq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cM5E1rWmm4sVsVZq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cM5E1rWmm4sVsVZq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cM5E1rWmm4sVsVZq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cM5E1rWmm4sVsVZq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cM5E1rWmm4sVsVZq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cM5E1rWmm4sVsVZq .cluster text{fill:#333;}#mermaid-svg-cM5E1rWmm4sVsVZq .cluster span{color:#333;}#mermaid-svg-cM5E1rWmm4sVsVZq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cM5E1rWmm4sVsVZq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cM5E1rWmm4sVsVZq rect.text{fill:none;stroke-width:0;}#mermaid-svg-cM5E1rWmm4sVsVZq .icon-shape,#mermaid-svg-cM5E1rWmm4sVsVZq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cM5E1rWmm4sVsVZq .icon-shape p,#mermaid-svg-cM5E1rWmm4sVsVZq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cM5E1rWmm4sVsVZq .icon-shape .label rect,#mermaid-svg-cM5E1rWmm4sVsVZq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cM5E1rWmm4sVsVZq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cM5E1rWmm4sVsVZq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cM5E1rWmm4sVsVZq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 基础设施
流程引擎
事件中心
事件来源(600+ 第三方应用)
Webhook 推送
钉钉/企微/飞书/...
定时轮询
HTTP/数据库/API
数据变更捕获
MySQL/PG/MongoDB
定时触发
Cron 表达式
事件接收层
BaseEventController
消息生产者
PushEventProducer
消息消费者
PushEventListener
触发器系统
Push/Polling/CDC
流程编排执行
连接器调用
RocketMQ
push_event Topic
Redis
去重/限流
4.2 事件中心的架构设计
事件中心的核心职责:接收第三方系统推送的事件 → 标准化封装 → 异步投递到 RocketMQ → 引擎消费并触发流程。
4.2.1 事件接收层
每个第三方应用有独立的 EventController,继承 BaseEventController,负责接收 Webhook 回调:
java
// 事件接收基类------统一的事件入口
@Component
public class BaseEventController {
@Resource
PushEventProducer pushEventProducer;
/**
* 构建标准化事件消息体
*/
protected PushEventMessage buildBody(
String processor,
ConnectorKey connectorKey,
AssetKey assetKey,
@Nullable Object event,
String body) {
PushEventMessage msg = new PushEventMessage();
msg.setProcessor(processor);
msg.setConnectorKey(connectorKey);
msg.setAssetKey(assetKey);
msg.setEvent(event);
msg.setMessage(body);
return msg;
}
/**
* 异步投递事件到 MQ
*/
protected void sendMessage(PushEventMessage pushEventMessage) {
String message = JSON.toJSONString(pushEventMessage);
pushEventProducer.sendMessage(message);
}
/**
* 快速响应第三方------不等流程执行完
*/
protected Object buildResponse() {
Map<String, Object> response = new HashMap<>();
response.put("code", 0);
response.put("msg", "success");
return response;
}
}
以小红书事件控制器为例:
java
@RestController
@RequestMapping("/event/xiaohongshu")
public class XiaohongshuEventController extends BaseEventController {
@PostMapping("/pushEvent")
public Object pushEvent(
@RequestHeader("app-key") String appKey,
@RequestHeader("sign") String sign,
@RequestBody String body) {
// 1. 解析第三方推送的事件数据
List<XiaohongshuPushing> messages = JSON.parseArray(body, ...);
// 2. 逐条封装为标准化事件消息
messages.forEach(message -> {
PushEventMessage msg = this.buildBody(
PROCESS_NAME,
new ConnectorKey(CONNECTOR_ID, connectorVersion),
AssetKey.builder()
.type(AssetOwnerTypeEnum.ASSET_UK.getCode())
.assetId(message.getSellerId())
.build(),
message.getMsgTag(),
(String) message.getData()
);
// 3. 异步投递到 MQ------不等流程执行
this.sendMessage(msg);
});
// 4. 立即响应第三方------毫秒级返回
return buildResponse();
}
}
关键设计决策:
- 快速 ACK:收到 Webhook 后立即返回成功,不等流程执行完。如果同步执行流程,第三方超时会导致连接堆积
- 标准化封装 :600+ 个第三方应用的事件格式千差万别,但在事件中心统一封装成
PushEventMessage,下游引擎不需要关心来源差异 - 继承体系 :
BaseEventController封装公共逻辑,各应用的 Controller 只关注自身的事件解析
4.2.2 消息投递层
java
@Component
public class PushEventProducer {
@Resource
private Producer producer;
@Value("${rocketmq.push.event.topic:push_event}")
private String topic;
/**
* 发送普通事件消息
*/
public void sendMessage(String msgBody) {
final Message message = provider.newMessageBuilder()
.setTopic(topic)
.setKeys(PUSH_EVENT_TAG)
.setTag(PUSH_EVENT_TAG)
.setBody(msgBody.getBytes(StandardCharsets.UTF_8))
.build();
producer.send(message);
}
/**
* 发送顺序消息------保证同一事件源的顺序性
*/
public void sendOrderlyMessage(String msgBody, String orderTag) {
// 按 orderTag 分区,保证同一分区内消息有序
...
}
}
4.2.3 消息消费层
java
@Component
public class PushEventListener extends BaseConsumer implements MessageListener {
@Override
public ConsumeResult consume(MessageView messageView) {
// 从 MQ 消费事件消息,触发流程引擎执行
String body = ByteBufferUtils.byteBufferToString(messageView.getBody());
return doConsume(body);
}
}
4.3 引擎侧的触发器体系
事件中心投递消息后,引擎侧通过触发器体系响应事件:
#mermaid-svg-B3BdQ2PWBn9NuLc5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .error-icon{fill:#552222;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .marker.cross{stroke:#333333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 p{margin:0;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .cluster-label text{fill:#333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .cluster-label span{color:#333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .cluster-label span p{background-color:transparent;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .label text,#mermaid-svg-B3BdQ2PWBn9NuLc5 span{fill:#333;color:#333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .node rect,#mermaid-svg-B3BdQ2PWBn9NuLc5 .node circle,#mermaid-svg-B3BdQ2PWBn9NuLc5 .node ellipse,#mermaid-svg-B3BdQ2PWBn9NuLc5 .node polygon,#mermaid-svg-B3BdQ2PWBn9NuLc5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .rough-node .label text,#mermaid-svg-B3BdQ2PWBn9NuLc5 .node .label text,#mermaid-svg-B3BdQ2PWBn9NuLc5 .image-shape .label,#mermaid-svg-B3BdQ2PWBn9NuLc5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .rough-node .label,#mermaid-svg-B3BdQ2PWBn9NuLc5 .node .label,#mermaid-svg-B3BdQ2PWBn9NuLc5 .image-shape .label,#mermaid-svg-B3BdQ2PWBn9NuLc5 .icon-shape .label{text-align:center;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .node.clickable{cursor:pointer;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .arrowheadPath{fill:#333333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-B3BdQ2PWBn9NuLc5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-B3BdQ2PWBn9NuLc5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-B3BdQ2PWBn9NuLc5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .cluster text{fill:#333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .cluster span{color:#333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-B3BdQ2PWBn9NuLc5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .icon-shape,#mermaid-svg-B3BdQ2PWBn9NuLc5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .icon-shape p,#mermaid-svg-B3BdQ2PWBn9NuLc5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .icon-shape .label rect,#mermaid-svg-B3BdQ2PWBn9NuLc5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-B3BdQ2PWBn9NuLc5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-B3BdQ2PWBn9NuLc5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-B3BdQ2PWBn9NuLc5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 流程引擎执行
四种触发器类型
推送触发器 PushEventTrigger
接收 Webhook/MQ 事件
轮询触发器 HttpPollingTrigger
Cron 定时拉取
CDC触发器 DataBaseCdcTrigger
数据库变更捕获
定时触发器 TimerTrigger
Cron 定时执行
TriggerExchange
事件上下文
FlowInstance
流程实例
ExecuteMachine
执行机
四种触发模式对比:
| 触发器 | 事件来源 | 实时性 | 适用场景 |
|---|---|---|---|
| 推送触发器 | 第三方 Webhook → MQ | 实时 | 钉钉消息回调、电商订单推送 |
| 轮询触发器 | Cron 定时拉取 API | 准实时(秒级) | 不支持 Webhook 的系统 |
| CDC 触发器 | 数据库 Binlog | 准实时(秒级) | 数据库变更监听 |
| 定时触发器 | Cron 定时执行 | 定时(分钟级) | 定时报表、数据同步 |
4.4 一个事件的完整生命周期
以"钉钉机器人消息触发流程"为例:
SAP系统 流程引擎 RocketMQ 事件中心 钉钉服务器 SAP系统 流程引擎 RocketMQ 事件中心 钉钉服务器 #mermaid-svg-tN0G8UoFBJ8DNTOB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tN0G8UoFBJ8DNTOB .error-icon{fill:#552222;}#mermaid-svg-tN0G8UoFBJ8DNTOB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tN0G8UoFBJ8DNTOB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tN0G8UoFBJ8DNTOB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tN0G8UoFBJ8DNTOB .marker.cross{stroke:#333333;}#mermaid-svg-tN0G8UoFBJ8DNTOB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tN0G8UoFBJ8DNTOB p{margin:0;}#mermaid-svg-tN0G8UoFBJ8DNTOB .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tN0G8UoFBJ8DNTOB text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-tN0G8UoFBJ8DNTOB .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tN0G8UoFBJ8DNTOB .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-tN0G8UoFBJ8DNTOB .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-tN0G8UoFBJ8DNTOB .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-tN0G8UoFBJ8DNTOB #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-tN0G8UoFBJ8DNTOB .sequenceNumber{fill:white;}#mermaid-svg-tN0G8UoFBJ8DNTOB #sequencenumber{fill:#333;}#mermaid-svg-tN0G8UoFBJ8DNTOB #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-tN0G8UoFBJ8DNTOB .messageText{fill:#333;stroke:none;}#mermaid-svg-tN0G8UoFBJ8DNTOB .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tN0G8UoFBJ8DNTOB .labelText,#mermaid-svg-tN0G8UoFBJ8DNTOB .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-tN0G8UoFBJ8DNTOB .loopText,#mermaid-svg-tN0G8UoFBJ8DNTOB .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-tN0G8UoFBJ8DNTOB .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tN0G8UoFBJ8DNTOB .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-tN0G8UoFBJ8DNTOB .noteText,#mermaid-svg-tN0G8UoFBJ8DNTOB .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-tN0G8UoFBJ8DNTOB .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tN0G8UoFBJ8DNTOB .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tN0G8UoFBJ8DNTOB .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tN0G8UoFBJ8DNTOB .actorPopupMenu{position:absolute;}#mermaid-svg-tN0G8UoFBJ8DNTOB .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-tN0G8UoFBJ8DNTOB .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tN0G8UoFBJ8DNTOB .actor-man circle,#mermaid-svg-tN0G8UoFBJ8DNTOB line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-tN0G8UoFBJ8DNTOB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 事件在队列中等待消费 POST /event/dingding/webhook 验签 + 解析消息 sendMessage(PushEventMessage) 200 OK(< 50ms) consume(MessageView) 匹配触发器 → 创建流程实例 加载流程定义 → 逐节点执行 HTTP 调用 SAP 接口 返回业务数据 写入执行日志
关键设计要点:
- 毫秒级响应第三方:事件中心只做"接收 → 封装 → 投递"三步,不做任何业务处理。钉钉服务器不会因为流程执行慢而超时
- 削峰填谷:高并发 Webhook 涌入时,事件堆积在 RocketMQ 中,引擎按自己的消费能力逐步处理
- 故障隔离:引擎挂了?事件在 MQ 中安全存储,恢复后继续消费,零数据丢失
五、事件驱动架构中最常踩的八个坑
5.1 坑一:事件丢失------消息发了但没人收到
症状:用户反馈"流程没有被触发",查日志发现事件确实发了,但消费者没收到。
为什么丢:
- 生产者发送失败但没重试
- MQ 宕机时未持久化的消息丢失
- 消费者处理失败但标记了消费成功
修复方案------三道防线:
#mermaid-svg-3FRja9MXT2YFDFoQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3FRja9MXT2YFDFoQ .error-icon{fill:#552222;}#mermaid-svg-3FRja9MXT2YFDFoQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3FRja9MXT2YFDFoQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3FRja9MXT2YFDFoQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3FRja9MXT2YFDFoQ .marker.cross{stroke:#333333;}#mermaid-svg-3FRja9MXT2YFDFoQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3FRja9MXT2YFDFoQ p{margin:0;}#mermaid-svg-3FRja9MXT2YFDFoQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3FRja9MXT2YFDFoQ .cluster-label text{fill:#333;}#mermaid-svg-3FRja9MXT2YFDFoQ .cluster-label span{color:#333;}#mermaid-svg-3FRja9MXT2YFDFoQ .cluster-label span p{background-color:transparent;}#mermaid-svg-3FRja9MXT2YFDFoQ .label text,#mermaid-svg-3FRja9MXT2YFDFoQ span{fill:#333;color:#333;}#mermaid-svg-3FRja9MXT2YFDFoQ .node rect,#mermaid-svg-3FRja9MXT2YFDFoQ .node circle,#mermaid-svg-3FRja9MXT2YFDFoQ .node ellipse,#mermaid-svg-3FRja9MXT2YFDFoQ .node polygon,#mermaid-svg-3FRja9MXT2YFDFoQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3FRja9MXT2YFDFoQ .rough-node .label text,#mermaid-svg-3FRja9MXT2YFDFoQ .node .label text,#mermaid-svg-3FRja9MXT2YFDFoQ .image-shape .label,#mermaid-svg-3FRja9MXT2YFDFoQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-3FRja9MXT2YFDFoQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3FRja9MXT2YFDFoQ .rough-node .label,#mermaid-svg-3FRja9MXT2YFDFoQ .node .label,#mermaid-svg-3FRja9MXT2YFDFoQ .image-shape .label,#mermaid-svg-3FRja9MXT2YFDFoQ .icon-shape .label{text-align:center;}#mermaid-svg-3FRja9MXT2YFDFoQ .node.clickable{cursor:pointer;}#mermaid-svg-3FRja9MXT2YFDFoQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3FRja9MXT2YFDFoQ .arrowheadPath{fill:#333333;}#mermaid-svg-3FRja9MXT2YFDFoQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3FRja9MXT2YFDFoQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3FRja9MXT2YFDFoQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3FRja9MXT2YFDFoQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3FRja9MXT2YFDFoQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3FRja9MXT2YFDFoQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3FRja9MXT2YFDFoQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3FRja9MXT2YFDFoQ .cluster text{fill:#333;}#mermaid-svg-3FRja9MXT2YFDFoQ .cluster span{color:#333;}#mermaid-svg-3FRja9MXT2YFDFoQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3FRja9MXT2YFDFoQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3FRja9MXT2YFDFoQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-3FRja9MXT2YFDFoQ .icon-shape,#mermaid-svg-3FRja9MXT2YFDFoQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3FRja9MXT2YFDFoQ .icon-shape p,#mermaid-svg-3FRja9MXT2YFDFoQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3FRja9MXT2YFDFoQ .icon-shape .label rect,#mermaid-svg-3FRja9MXT2YFDFoQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3FRja9MXT2YFDFoQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3FRja9MXT2YFDFoQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3FRja9MXT2YFDFoQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 防线三:消费端确认
投递
处理成功
处理失败
MQ
消费者
ACK
NACK → 重试
防线二:Broker持久化
同步刷盘
主从复制
MQ
磁盘
从节点
防线一:生产端确认
发送
ACK
失败重试
生产者
MQ
5.2 坑二:重复消费------一个事件被处理了三次
症状:用户收到三条一样的通知短信,数据库里出现重复记录。
为什么重复:
- 消费者处理成功后 ACK 失败,MQ 重新投递
- 生产者超时重试,同一条消息发了两次
- 消费者重启,未 ACK 的消息被重新投递
修复方案------幂等性设计:
java
/**
* 幂等消费:同一个事件只处理一次
*/
public ConsumeResult consume(MessageView messageView) {
String eventId = extractEventId(messageView);
// 1. 幂等检查:用 Redis 记录已处理的事件ID
if (redisTemplate.opsForValue().setIfAbsent(
"consumed:" + eventId, "1", 24, TimeUnit.HOURS)) {
// 2. 第一次处理:执行业务逻辑
return doProcess(messageView);
} else {
// 3. 重复消息:跳过
log.warn("Duplicate event detected: {}", eventId);
return ConsumeResult.SUCCESS;
}
}
幂等设计的核心原则:消费者必须假设"同一条消息可能被投递多次",并确保多次处理的结果和一次处理完全一致。
5.3 坑三:事件顺序错乱------先消费了"取消"再消费"创建"
症状:订单先收到"取消"事件,再收到"创建"事件------状态机乱了。
为什么乱序:
- MQ 的多分区并行消费
- 消费者多线程处理
- 网络延迟导致后发的消息先到
修复方案:
| 方案 | 说明 | 适用场景 |
|---|---|---|
| 分区有序 | 同一实体的事件发到同一分区 | 大部分场景(推荐) |
| 版本号 | 事件带版本号,低版本丢弃 | 数据同步场景 |
| 时间戳排序 | 消费者按时间戳重排序 | 容忍一定延迟的批处理 |
java
// 分区有序:同一 assetId 的事件进入同一分区
public void sendOrderlyMessage(String msgBody, String orderTag) {
final Message message = provider.newMessageBuilder()
.setTopic(orderTopic)
.setKeys(orderTag) // 用 orderTag 做分区键
.setTag(PUSH_ORDER_EVENT_TAG)
.setBody(msgBody.getBytes(StandardCharsets.UTF_8))
.build();
producer.send(message); // 同一 orderTag 保证顺序
}
5.4 坑四:消费者雪崩------一个慢消费者拖垮所有人
症状:某个消费者的处理逻辑特别慢(比如调外部 API 超时),导致消息堆积,其他正常消费者也受影响。
修复方案:
- 独立消费组:不同类型的消费者使用独立的消费组,互不影响
- 消费超时:设置单条消息的最大处理时间
- 死信队列:多次重试失败的消息进入死信队列,不阻塞正常消费
#mermaid-svg-dDCSwpClY5jViGXV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dDCSwpClY5jViGXV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dDCSwpClY5jViGXV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dDCSwpClY5jViGXV .error-icon{fill:#552222;}#mermaid-svg-dDCSwpClY5jViGXV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dDCSwpClY5jViGXV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dDCSwpClY5jViGXV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dDCSwpClY5jViGXV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dDCSwpClY5jViGXV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dDCSwpClY5jViGXV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dDCSwpClY5jViGXV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dDCSwpClY5jViGXV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dDCSwpClY5jViGXV .marker.cross{stroke:#333333;}#mermaid-svg-dDCSwpClY5jViGXV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dDCSwpClY5jViGXV p{margin:0;}#mermaid-svg-dDCSwpClY5jViGXV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dDCSwpClY5jViGXV .cluster-label text{fill:#333;}#mermaid-svg-dDCSwpClY5jViGXV .cluster-label span{color:#333;}#mermaid-svg-dDCSwpClY5jViGXV .cluster-label span p{background-color:transparent;}#mermaid-svg-dDCSwpClY5jViGXV .label text,#mermaid-svg-dDCSwpClY5jViGXV span{fill:#333;color:#333;}#mermaid-svg-dDCSwpClY5jViGXV .node rect,#mermaid-svg-dDCSwpClY5jViGXV .node circle,#mermaid-svg-dDCSwpClY5jViGXV .node ellipse,#mermaid-svg-dDCSwpClY5jViGXV .node polygon,#mermaid-svg-dDCSwpClY5jViGXV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dDCSwpClY5jViGXV .rough-node .label text,#mermaid-svg-dDCSwpClY5jViGXV .node .label text,#mermaid-svg-dDCSwpClY5jViGXV .image-shape .label,#mermaid-svg-dDCSwpClY5jViGXV .icon-shape .label{text-anchor:middle;}#mermaid-svg-dDCSwpClY5jViGXV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dDCSwpClY5jViGXV .rough-node .label,#mermaid-svg-dDCSwpClY5jViGXV .node .label,#mermaid-svg-dDCSwpClY5jViGXV .image-shape .label,#mermaid-svg-dDCSwpClY5jViGXV .icon-shape .label{text-align:center;}#mermaid-svg-dDCSwpClY5jViGXV .node.clickable{cursor:pointer;}#mermaid-svg-dDCSwpClY5jViGXV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dDCSwpClY5jViGXV .arrowheadPath{fill:#333333;}#mermaid-svg-dDCSwpClY5jViGXV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dDCSwpClY5jViGXV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dDCSwpClY5jViGXV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dDCSwpClY5jViGXV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dDCSwpClY5jViGXV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dDCSwpClY5jViGXV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dDCSwpClY5jViGXV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dDCSwpClY5jViGXV .cluster text{fill:#333;}#mermaid-svg-dDCSwpClY5jViGXV .cluster span{color:#333;}#mermaid-svg-dDCSwpClY5jViGXV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dDCSwpClY5jViGXV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dDCSwpClY5jViGXV rect.text{fill:none;stroke-width:0;}#mermaid-svg-dDCSwpClY5jViGXV .icon-shape,#mermaid-svg-dDCSwpClY5jViGXV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dDCSwpClY5jViGXV .icon-shape p,#mermaid-svg-dDCSwpClY5jViGXV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dDCSwpClY5jViGXV .icon-shape .label rect,#mermaid-svg-dDCSwpClY5jViGXV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dDCSwpClY5jViGXV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dDCSwpClY5jViGXV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dDCSwpClY5jViGXV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 死信处理
正常消费
重试3次失败
Topic
消费组A
通知服务
消费组B
日志服务
消费组C
积分服务
死信队列
DLQ
告警通知
人工重试
5.5 坑五:事件风暴------一个操作触发一百个事件
症状:用户点了一个按钮,系统产生了几百个事件,MQ 被塞满,消费延迟飙升。
为什么 :批量操作没有合并事件。比如"批量导入 1000 条客户",如果每条都发一个 CustomerCreated 事件,就是 1000 个事件。
修复方案:
| 方案 | 说明 |
|---|---|
| 事件合并 | 批量操作发一个 CustomerBatchImported 事件,包含 ID 列表 |
| 事件节流 | 限制同一源头在单位时间内的最大事件数 |
| 批量消费 | 消费者一次拉取多条消息批量处理 |
5.6 坑六:同步伪装成异步------发事件后还在等结果
症状 :Controller 发了事件后,还轮询数据库等结果,或者用 Thread.sleep 等待。
为什么是坑:本质上还是同步思维,白白浪费了异步的优势,还多了一层 MQ 的开销。
修复方案:真正的异步是"发了就走"。前端需要结果?用 WebSocket 推送,或者轮询状态接口。
5.7 坑七:事件模式未定义------生产者和消费者各说各话
症状:生产者改了事件字段名,消费者还在用旧字段名读取------线上静默失败,数据全是 null。
修复方案:
- 事件模式注册:每个事件有明确的 Schema(JSON Schema / Protobuf)
- 版本管理:事件结构变更时递增版本号,消费者兼容多版本
- 契约测试:生产者和消费者共享 Schema,CI 中校验兼容性
5.8 坑八:忽视可观测性------出了问题完全无法排查
症状:用户说"我的流程没触发",你查了一圈不知道事件到哪儿了。
修复方案------事件追踪三件套:
| 维度 | 做法 | 价值 |
|---|---|---|
| Trace ID | 事件携带全局唯一的追踪 ID | 串联从生产到消费的全链路 |
| 事件日志 | 记录每个事件的投递/消费/ACK 状态 | 出问题时有据可查 |
| 消费监控 | 监控消费延迟/堆积量/失败率 | 问题发生前就能预警 |
六、事件溯源(Event Sourcing):把事件当数据库
6.1 什么是事件溯源?
传统做法:数据库里存"当前状态"------一条订单记录存了 status = 'COMPLETED',你不知道它经历了什么。
事件溯源:不存状态,只存事件。当前状态 = 所有事件的叠加。
#mermaid-svg-oFu7iVwhhv2fofxT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oFu7iVwhhv2fofxT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oFu7iVwhhv2fofxT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oFu7iVwhhv2fofxT .error-icon{fill:#552222;}#mermaid-svg-oFu7iVwhhv2fofxT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oFu7iVwhhv2fofxT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oFu7iVwhhv2fofxT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oFu7iVwhhv2fofxT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oFu7iVwhhv2fofxT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oFu7iVwhhv2fofxT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oFu7iVwhhv2fofxT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oFu7iVwhhv2fofxT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oFu7iVwhhv2fofxT .marker.cross{stroke:#333333;}#mermaid-svg-oFu7iVwhhv2fofxT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oFu7iVwhhv2fofxT p{margin:0;}#mermaid-svg-oFu7iVwhhv2fofxT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oFu7iVwhhv2fofxT .cluster-label text{fill:#333;}#mermaid-svg-oFu7iVwhhv2fofxT .cluster-label span{color:#333;}#mermaid-svg-oFu7iVwhhv2fofxT .cluster-label span p{background-color:transparent;}#mermaid-svg-oFu7iVwhhv2fofxT .label text,#mermaid-svg-oFu7iVwhhv2fofxT span{fill:#333;color:#333;}#mermaid-svg-oFu7iVwhhv2fofxT .node rect,#mermaid-svg-oFu7iVwhhv2fofxT .node circle,#mermaid-svg-oFu7iVwhhv2fofxT .node ellipse,#mermaid-svg-oFu7iVwhhv2fofxT .node polygon,#mermaid-svg-oFu7iVwhhv2fofxT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oFu7iVwhhv2fofxT .rough-node .label text,#mermaid-svg-oFu7iVwhhv2fofxT .node .label text,#mermaid-svg-oFu7iVwhhv2fofxT .image-shape .label,#mermaid-svg-oFu7iVwhhv2fofxT .icon-shape .label{text-anchor:middle;}#mermaid-svg-oFu7iVwhhv2fofxT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oFu7iVwhhv2fofxT .rough-node .label,#mermaid-svg-oFu7iVwhhv2fofxT .node .label,#mermaid-svg-oFu7iVwhhv2fofxT .image-shape .label,#mermaid-svg-oFu7iVwhhv2fofxT .icon-shape .label{text-align:center;}#mermaid-svg-oFu7iVwhhv2fofxT .node.clickable{cursor:pointer;}#mermaid-svg-oFu7iVwhhv2fofxT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oFu7iVwhhv2fofxT .arrowheadPath{fill:#333333;}#mermaid-svg-oFu7iVwhhv2fofxT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oFu7iVwhhv2fofxT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oFu7iVwhhv2fofxT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oFu7iVwhhv2fofxT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oFu7iVwhhv2fofxT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oFu7iVwhhv2fofxT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oFu7iVwhhv2fofxT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oFu7iVwhhv2fofxT .cluster text{fill:#333;}#mermaid-svg-oFu7iVwhhv2fofxT .cluster span{color:#333;}#mermaid-svg-oFu7iVwhhv2fofxT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oFu7iVwhhv2fofxT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oFu7iVwhhv2fofxT rect.text{fill:none;stroke-width:0;}#mermaid-svg-oFu7iVwhhv2fofxT .icon-shape,#mermaid-svg-oFu7iVwhhv2fofxT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oFu7iVwhhv2fofxT .icon-shape p,#mermaid-svg-oFu7iVwhhv2fofxT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oFu7iVwhhv2fofxT .icon-shape .label rect,#mermaid-svg-oFu7iVwhhv2fofxT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oFu7iVwhhv2fofxT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oFu7iVwhhv2fofxT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oFu7iVwhhv2fofxT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 事件溯源:事件存储
重放
Event Store
OrderCreated
PaymentReceived
OrderShipped
OrderDelivered
当前状态
= COMPLETED
传统:状态存储
MySQL
status = COMPLETED
6.2 事件溯源的收益
| 收益 | 说明 |
|---|---|
| 完整审计 | 每一步变更都有记录,满足金融/医疗合规要求 |
| 时间旅行 | 可以回放任意时间点的事件,重建当时的状态 |
| 投影灵活 | 同一组事件可以投影成不同的读模型(订单视图/统计视图/风控视图) |
| 天然解耦 | 写操作只追加事件,读操作从投影读取 |
6.3 事件溯源的代价
| 代价 | 说明 |
|---|---|
| 查询复杂 | 不能直接查"所有已完成的订单"------需要投影 |
| 性能开销 | 重放百万条事件来重建状态,需要快照机制 |
| 学习曲线 | 团队要从"更新状态"的思维转为"追加事件"的思维 |
| 模式演进 | 事件结构变更后,旧事件和新事件的兼容处理 |
适用场景:金融交易审计、保险保单全生命周期、医疗病历------任何"需要知道发生了什么而不只是现在是什么"的场景。
七、事件驱动 + CQRS:读写分离的终极形态
7.1 架构全景
#mermaid-svg-f5mx92VaRWH5T4wc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-f5mx92VaRWH5T4wc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-f5mx92VaRWH5T4wc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-f5mx92VaRWH5T4wc .error-icon{fill:#552222;}#mermaid-svg-f5mx92VaRWH5T4wc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-f5mx92VaRWH5T4wc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-f5mx92VaRWH5T4wc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-f5mx92VaRWH5T4wc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-f5mx92VaRWH5T4wc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-f5mx92VaRWH5T4wc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-f5mx92VaRWH5T4wc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-f5mx92VaRWH5T4wc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-f5mx92VaRWH5T4wc .marker.cross{stroke:#333333;}#mermaid-svg-f5mx92VaRWH5T4wc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-f5mx92VaRWH5T4wc p{margin:0;}#mermaid-svg-f5mx92VaRWH5T4wc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-f5mx92VaRWH5T4wc .cluster-label text{fill:#333;}#mermaid-svg-f5mx92VaRWH5T4wc .cluster-label span{color:#333;}#mermaid-svg-f5mx92VaRWH5T4wc .cluster-label span p{background-color:transparent;}#mermaid-svg-f5mx92VaRWH5T4wc .label text,#mermaid-svg-f5mx92VaRWH5T4wc span{fill:#333;color:#333;}#mermaid-svg-f5mx92VaRWH5T4wc .node rect,#mermaid-svg-f5mx92VaRWH5T4wc .node circle,#mermaid-svg-f5mx92VaRWH5T4wc .node ellipse,#mermaid-svg-f5mx92VaRWH5T4wc .node polygon,#mermaid-svg-f5mx92VaRWH5T4wc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-f5mx92VaRWH5T4wc .rough-node .label text,#mermaid-svg-f5mx92VaRWH5T4wc .node .label text,#mermaid-svg-f5mx92VaRWH5T4wc .image-shape .label,#mermaid-svg-f5mx92VaRWH5T4wc .icon-shape .label{text-anchor:middle;}#mermaid-svg-f5mx92VaRWH5T4wc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-f5mx92VaRWH5T4wc .rough-node .label,#mermaid-svg-f5mx92VaRWH5T4wc .node .label,#mermaid-svg-f5mx92VaRWH5T4wc .image-shape .label,#mermaid-svg-f5mx92VaRWH5T4wc .icon-shape .label{text-align:center;}#mermaid-svg-f5mx92VaRWH5T4wc .node.clickable{cursor:pointer;}#mermaid-svg-f5mx92VaRWH5T4wc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-f5mx92VaRWH5T4wc .arrowheadPath{fill:#333333;}#mermaid-svg-f5mx92VaRWH5T4wc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-f5mx92VaRWH5T4wc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-f5mx92VaRWH5T4wc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f5mx92VaRWH5T4wc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-f5mx92VaRWH5T4wc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f5mx92VaRWH5T4wc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-f5mx92VaRWH5T4wc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-f5mx92VaRWH5T4wc .cluster text{fill:#333;}#mermaid-svg-f5mx92VaRWH5T4wc .cluster span{color:#333;}#mermaid-svg-f5mx92VaRWH5T4wc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-f5mx92VaRWH5T4wc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-f5mx92VaRWH5T4wc rect.text{fill:none;stroke-width:0;}#mermaid-svg-f5mx92VaRWH5T4wc .icon-shape,#mermaid-svg-f5mx92VaRWH5T4wc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f5mx92VaRWH5T4wc .icon-shape p,#mermaid-svg-f5mx92VaRWH5T4wc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-f5mx92VaRWH5T4wc .icon-shape .label rect,#mermaid-svg-f5mx92VaRWH5T4wc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f5mx92VaRWH5T4wc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-f5mx92VaRWH5T4wc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-f5mx92VaRWH5T4wc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 查询
读端:Query
写端:Command
事件流
写入 API
命令处理器
领域模型
业务规则校验
事件存储
Event Store
事件投影器
订单视图
MySQL
统计视图
Doris
搜索视图
ES
查询 API
写端 只负责接收命令 → 校验业务规则 → 追加事件。
读端 消费事件 → 更新各自的读模型(可以是不同的数据库)。
查询端直连读模型,毫秒级返回。
7.2 iPaaS 的实践:执行日志的 CQRS
我们在 iPaaS 的执行日志场景就使用了类似 CQRS 的思路:
#mermaid-svg-KXOAOKNwTAQb8rrR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KXOAOKNwTAQb8rrR .error-icon{fill:#552222;}#mermaid-svg-KXOAOKNwTAQb8rrR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KXOAOKNwTAQb8rrR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KXOAOKNwTAQb8rrR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KXOAOKNwTAQb8rrR .marker.cross{stroke:#333333;}#mermaid-svg-KXOAOKNwTAQb8rrR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KXOAOKNwTAQb8rrR p{margin:0;}#mermaid-svg-KXOAOKNwTAQb8rrR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KXOAOKNwTAQb8rrR .cluster-label text{fill:#333;}#mermaid-svg-KXOAOKNwTAQb8rrR .cluster-label span{color:#333;}#mermaid-svg-KXOAOKNwTAQb8rrR .cluster-label span p{background-color:transparent;}#mermaid-svg-KXOAOKNwTAQb8rrR .label text,#mermaid-svg-KXOAOKNwTAQb8rrR span{fill:#333;color:#333;}#mermaid-svg-KXOAOKNwTAQb8rrR .node rect,#mermaid-svg-KXOAOKNwTAQb8rrR .node circle,#mermaid-svg-KXOAOKNwTAQb8rrR .node ellipse,#mermaid-svg-KXOAOKNwTAQb8rrR .node polygon,#mermaid-svg-KXOAOKNwTAQb8rrR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KXOAOKNwTAQb8rrR .rough-node .label text,#mermaid-svg-KXOAOKNwTAQb8rrR .node .label text,#mermaid-svg-KXOAOKNwTAQb8rrR .image-shape .label,#mermaid-svg-KXOAOKNwTAQb8rrR .icon-shape .label{text-anchor:middle;}#mermaid-svg-KXOAOKNwTAQb8rrR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KXOAOKNwTAQb8rrR .rough-node .label,#mermaid-svg-KXOAOKNwTAQb8rrR .node .label,#mermaid-svg-KXOAOKNwTAQb8rrR .image-shape .label,#mermaid-svg-KXOAOKNwTAQb8rrR .icon-shape .label{text-align:center;}#mermaid-svg-KXOAOKNwTAQb8rrR .node.clickable{cursor:pointer;}#mermaid-svg-KXOAOKNwTAQb8rrR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KXOAOKNwTAQb8rrR .arrowheadPath{fill:#333333;}#mermaid-svg-KXOAOKNwTAQb8rrR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KXOAOKNwTAQb8rrR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KXOAOKNwTAQb8rrR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KXOAOKNwTAQb8rrR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KXOAOKNwTAQb8rrR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KXOAOKNwTAQb8rrR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KXOAOKNwTAQb8rrR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KXOAOKNwTAQb8rrR .cluster text{fill:#333;}#mermaid-svg-KXOAOKNwTAQb8rrR .cluster span{color:#333;}#mermaid-svg-KXOAOKNwTAQb8rrR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KXOAOKNwTAQb8rrR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KXOAOKNwTAQb8rrR rect.text{fill:none;stroke-width:0;}#mermaid-svg-KXOAOKNwTAQb8rrR .icon-shape,#mermaid-svg-KXOAOKNwTAQb8rrR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KXOAOKNwTAQb8rrR .icon-shape p,#mermaid-svg-KXOAOKNwTAQb8rrR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KXOAOKNwTAQb8rrR .icon-shape .label rect,#mermaid-svg-KXOAOKNwTAQb8rrR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KXOAOKNwTAQb8rrR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KXOAOKNwTAQb8rrR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KXOAOKNwTAQb8rrR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 读端
数据同步
写端
Flink CDC
流程引擎
MySQL
权威数据源
Doris
列存引擎
查询 API
- 写链路:流程执行日志写入 MySQL,经过业务规则校验
- 同步层:Flink CDC 异步将数据同步到 Doris
- 读链路:查询直接走 Doris 列存引擎,亿级日志毫秒级返回
八、事件驱动架构落地 Checklist
事件设计
- 事件名使用过去式(
OrderCreated而非CreateOrder) - 事件载荷包含足够下游处理的信息,不过多不过少
- 事件携带元数据(时间戳、来源、Trace ID)
- 事件 Schema 有版本管理,变更向后兼容
消息可靠性
- 生产端有发送确认 + 失败重试
- MQ 配置持久化 + 主从复制
- 消费端实现幂等处理
- 死信队列配置 + 告警
架构设计
- 生产者和消费者通过 MQ 解耦,不直接依赖
- 关键流程使用顺序消息保证有序性
- 消费者有独立消费组,互不影响
- 事件中心有削峰能力(MQ 缓冲)
可观测性
- 事件全链路有 Trace ID 串联
- 消费延迟/堆积量有监控告警
- 事件日志可查看投递和消费状态
- 有死信队列的巡检和重试机制
九、事件驱动架构的七个反模式
反模式 1:同步伪装异步
Controller 发了事件后轮询数据库等结果,本质上还是同步。
修复:发了就走,前端用 WebSocket 或轮询状态接口获取结果。
反模式 2:事件大杂烩
所有业务变更都发一个 DataChanged 事件,消费者要自己判断到底是什么变了。
修复 :事件类型要细粒度------OrderCreated、OrderPaid、OrderShipped,消费者只订阅自己关心的事件。
反模式 3:消费者耦合
消费者在处理事件时直接调用另一个消费者的 API------变成了隐式的同步调用链。
修复:消费者只处理事件、产生新事件,不直接调用其他消费者。
反模式 4:忽视幂等
消费者假设"每条消息只会被处理一次"------重试时产生重复数据。
修复:每个消费者必须实现幂等处理。用 Redis 去重、数据库唯一约束、或状态机校验。
反模式 5:上帝事件
一个事件包含了系统中所有字段(几百个字段),每个消费者只用其中几个。
修复:事件只包含必要信息。消费者需要更多数据?通过事件中的 ID 去查询。
反模式 6:事件黑洞
事件被消费后没有任何可观测的输出------不知道处理成功了还是失败了。
修复:每个消费者的处理结果(成功/失败/跳过)都要有日志和指标。关键事件处理完可以发一个"结果事件"。
反模式 7:过度事件化
一个三层的 CRUD 后台,连用户改个密码都发事件。简单的事情用方法调用就够了。
修复:只在以下场景使用事件------跨服务通信、一个操作多个下游、需要异步处理、需要审计追溯。其他场景用同步调用。
十、事件驱动 vs 其他架构模式
| 维度 | 同步调用 | 事件驱动 | 请求-响应 + 事件混合 |
|---|---|---|---|
| 耦合度 | 高(硬编码调用) | 低(通过 MQ 解耦) | 中等 |
| 延迟 | 高(串行累加) | 低(并行处理) | 视场景而定 |
| 吞吐量 | 受限于最慢环节 | 高(MQ 削峰缓冲) | 高 |
| 一致性 | 强一致(分布式事务) | 最终一致 | 混合 |
| 可观测性 | 调用链清晰 | 需要额外追踪 | 较复杂 |
| 调试难度 | 低(单步调试) | 高(异步链路) | 中等 |
| 适用场景 | 强一致/简单流程 | 高吞吐/多下游/异步 | 大多数企业系统 |
没有银弹:大部分企业系统最终会走向"混合架构"------关键路径用同步调用保证强一致,旁路操作用事件驱动解耦。
十一、常见问题(FAQ)
Q:事件驱动和消息队列是一回事吗?
A:不是。消息队列是事件驱动架构的基础设施之一,但事件驱动是一套架构思想------包括事件设计、拓扑选择、可靠性保证、幂等处理等一系列设计决策。用了 MQ 不代表做了事件驱动,就像用了 Spring 不代表做了微服务。
Q:事件驱动能保证数据一致性吗?
A:能保证,但保证的是"最终一致性"而非"强一致性"。如果业务必须强一致(如银行转账),要么用分布式事务(Saga/TCC),要么这部分用同步调用。事件驱动和强一致性不是对立的------RocketMQ 的事务消息可以在一定程度上兼顾两者。
Q:消费者处理失败了怎么办?
A:三层兜底:① MQ 自动重试(指数退避);② 死信队列 + 告警;③ 人工介入 + 手动重试。关键是消费者要幂等------重试不能产生副作用。
Q:事件太多会不会把 MQ 撑爆?
A:会的。需要做好容量规划:① 合理设置 Topic 分区数;② 事件有过期时间(TTL);③ 监控消费延迟和堆积量;④ 批量事件做合并。
Q:什么时候该用顺序消息?
A:当同一实体的事件有严格的时序要求时。比如"订单创建"必须在"订单支付"之前被消费。通过分区键(如 orderId)保证同一实体的事件进入同一分区,分区内有序。
Q:事件驱动和微服务是什么关系?
A:事件驱动是微服务之间通信的最佳实践之一。微服务解决"服务怎么拆",事件驱动解决"服务怎么连"。同步 RPC 适合强依赖,事件驱动适合松耦合。两者结合使用。
十二、小结
事件驱动架构是分布式系统中最强大、也最容易被误用的架构模式之一。三句话总结:
- 事件驱动的本质是"解耦+异步"------生产者不管谁在消费,消费者不管事件从哪来,中间通过 MQ 做缓冲和路由
- 事件驱动不是银弹------它用"最终一致性"和"架构复杂度"换取"高弹性"和"高扩展性"------适合多下游、高吞吐、异步处理的场景,不适合强一致、简单查询的场景
- 落地事件驱动的三大纪律是"可靠投递+幂等消费+全链路追踪"------做不到这三点,事件驱动就是一个"出了问题查不到"的黑盒
从明天开始,审视你手头的项目:哪些同步调用可以改成事件驱动?哪些事件的可靠性没有保证?消费者的幂等做了吗?
想清楚这些,你的分布式系统就稳了一半。
标签:#事件驱动架构 #EDA #消息队列 #RocketMQ #Kafka #异步架构 #事件溯源 #CQRS #分布式系统 #微服务 #Webhook #CDC #架构设计 #系统设计 #技术分享