28.2 ESB
一句话
ESB 是 “中心化的集成总线”:路由、协议转换、数据映射、编排都放在中间件里;它把 N×M 的点对点集成压成 N+M,代价是业务逻辑沉淀在中间件,形成难以版本化的黑盒。
ESB 的四项能力
| 能力 | 做什么 | 今天的替代 |
|---|---|---|
| 路由 | 按内容/规则把消息转给不同后端 | API 网关 + 消息队列路由 |
| 协议桥接 | SOAP ↔ REST ↔ MQ ↔ 文件 | 适配器/Connector/边车 |
| 数据映射 | 字段级转换(XML/XSLT) | 应用内防腐层 + schema 转换 |
| 编排 | 多服务流程(BPEL) | 工作流引擎(Temporal/Camunda) |
代码:一条典型 ESB 路由
<!-- Camel 风格:内容路由 + 转换 + 重试(示意,未在本机运行) -->
<route>
<from uri="jms:queue:orders"/>
<choice>
<when><simple>${body[type]} == 'REFUND'</simple>
<to uri="bean:refundService?method=handle"/>
</when>
<otherwise>
<to uri="bean:orderService?method=create"/>
</otherwise>
</choice>
<onException>
<exception>java.lang.Exception</exception>
<redeliveryPolicy maximumRedeliveries="3"/>
<to uri="jms:queue:orders.DLQ"/>
</onException>
</route>
问题也在这里:路由规则、转换逻辑、重试策略全在 XML 里,它们本质是业务决策,却没有代码评审、单元测试与版本语义。
实测:ESB 式转换的开销(本机跑)
2 万条消息,直接字段映射 vs 走 “序列化 → XML → 解析 → 映射”(模拟总线转换):
| 方式 | 耗时 | 相对 |
|---|---|---|
| 直接字段映射 | 3.5ms | 1× |
| 经 XML 解析转换 | 101.7ms | 29× |
这说明 ESB 的成本不只是 “多一跳网络”,还有每次消息都要做序列化/解析。把它放在所有流量的必经之路上,等于给每条消息加 29 倍的 CPU 税;批量场景(如日志/事件流)尤其明显。
# 可复现:直接映射 vs XML 解析映射
direct = [{"orderId": m["id"], "amt": m["amount"]} for m in msgs]
# 对照:每条都做 ET.fromstring + findtext
反模式
| 反模式 | 后果 |
|---|---|
| 把业务规则写进 ESB | 中间件变成 “不可测试的业务层”,改一条规则要重启总线 |
| 用 ESB 做 “所有流量的单一入口” | 单点 + 全局限流,故障爆炸半径最大 |
| XSLT 做复杂转换 | 没人能维护,逻辑只存在于运行时 |
| 契约与实现脱钩 | 总线改了映射,调用方毫无感知 |
| 不设 DLQ 与重放 | 一条毒消息堵住整个队列 |
| 用 ESB 掩盖后端 “不可集成” | 适配层越来越厚,最终无人能改 |
| 路由规则进代码后仍在 ESB 维护 | 两处定义,改一处就行为不一致 | | 不做转换的单元测试 | 上线才发现字段映射错,且只能靠日志猜 |
三条铁律
🔑 ESB 只做 “连接与转换”,不做业务决策;业务必须回到服务里。
🔑 转换逻辑要能被测试与版本化;XML 里的 if-else 是最贵的债。
🔑 必须有 DLQ + 重放 + 幂等,否则一条坏消息能拖垮全链路。
自检
Q1 为什么 ESB 容易变成 “逻辑黑洞”?
▸ 答案:因为它的配置(路由、映射、重试、编排)算业务逻辑,却不在代码仓库里:没有单测、没有 code review、没有语义化版本,改动只能靠运维点击或部署 XML。时间一长,没人能说清 “这条消息到底被谁改成了什么”。
Q2 ESB 与 API 网关的分工是什么?
▸ 答案:网关面向 “外部调用”(鉴权、限流、路由、协议转换到 HTTP);ESB 面向 “内部集成”(多协议、多系统、消息级转换与编排)。现代做法是用 “网关 + 消息队列 + 工作流引擎” 拆掉 ESB 的三项能力,避免单点与黑盒。
Q3 什么场景还适合用 ESB?
▸ 答案:大量异构遗留系统(SOAP、文件、MQ 混合)、且团队已有成熟的总线运维能力时。它能在短期内统一接入;但必须限制 “只做连接与转换”,并把编排迁到可测试的工作流引擎。
Q4 为什么 “ESB 单点” 比 “服务单点” 更危险?
▸ 答案:因为 ESB 处于所有流量的必经之路:它挂掉或变慢,所有集成一起不可用;而单个服务故障只影响一条链路。它的爆炸半径等于整个企业的集成面。
Q5 迁移出 ESB 的顺序应该是什么?
▸ 答案:先搬 “编排”(到工作流引擎,最容易测),再搬 “转换”(到服务内的防腐层),最后搬 “路由”(到网关/队列)。顺序反了会让中间态更难维护——先动路由会把所有链路同时打散。
Q6 如何判断 ESB 里的某段逻辑该搬走?
▸ 答案:问 “它是否依赖业务语义”:只做协议/格式转换 → 留在连接层;涉及 “金额怎么算、状态怎么流转、谁该收到” → 必须搬到服务里。判据是 “这段逻辑变了,是否需要业务方评审”。
Q7 既然转换这么贵,为什么还保留 ESB?
▸ 答案:因为异构协议(SOAP、文件、MQ)之间的桥接没有更便宜的通用解:要么在总线集中做一次转换,要么在 N 个系统里各写一次适配。ESB 的价值是 “集中 + 可治理”,代价是 “每条消息都付转换成本”;所以它只该处理 “必须跨协议” 的流量,内部同构调用应绕开它。
故障复盘(典型模式,脱敏)
| 阶段 | 现象 | 判断 | 动作 |
|---|---|---|---|
| T+0 | 一条映射规则上线后,订单金额全部错位 | 字段映射无单测 | 回滚规则 |
| T+15m | 回滚失败——部分消息已按新规则入库 | 无重放/补偿 | 手工修数 |
| T+2h | 定位到 XSLT 里一个 substring 边界错 | 逻辑藏在 XML | 迁移到代码 |
| 之后 | 转换逻辑进代码 + 单测 | 可测试 | 保留总线做协议桥接 |
根因:把业务级转换写进 ESB 配置,没有测试与重放。代价:金额错账 4 小时。根治:转换留在代码里(可测),ESB 只做协议桥接;必须有 DLQ 与重放。