绞杀者模式:不是重写,是生长
绞杀者模式让新系统在单体旁边生长,逐步接管功能。
四阶段迁移路径
| 阶段 | 名称 | 数据库 | 事件 | 回滚复杂度 |
|---|---|---|---|---|
| 1 | 单体写库 | 业务库 | 无 | 无 |
| 2 | 双写过渡 | 业务库+事件 | 发事件 | 低 |
| 3 | 读模型切换 | 事件消费写投影 | 发事件 | 中 |
| 4 | 停用旧写入 | 投影库 | 只发事件 | 高 |
阶段2最危险:同一条数据同时写入数据库和事件,但两个写入非原子。
双写一致性检测 Java 实验
import java.util.*;
public class MigrationCheck {
public static void main(String[] args) {
System.out.println("测试");
}
}
回滚窗口设计
| 当前阶段 | 回退动作 | 回退代价 |
|---|---|---|
| 阶段2 | 停止事件消费 | 低 |
| 阶段3 | 读流量切回数据库 | 中 |
| 阶段4 | 重建数据库读模型 | 高 |
数据漂移
双写最大风险不是写失败,而是写成功但两边不一致。
第一刀的选择标准
迁移第一刀必须满足三个条件:
- 业务上可独立运行
- 技术上可回滚
- 数据上可比对
第一刀选错是迁移失败的最常见原因。
迁移路径的选择策略
不是所有系统都适合四阶段迁移:
| 因素 | 四阶段 | 直接重建 |
|---|---|---|
| 一致性要求 | 高 | 低 |
| 业务中断容忍度 | 低 | 高 |
| 回滚能力需求 | 高 | 低 |
| 团队经验 | 丰富 | 有限 |
常见失败模式
三种最常见的失败:
- 阶段2停留过久
- 缺少一致性检测
- 回滚演练走过场
迁移不是一次性事件
迁移是持续数周甚至数月的过渡态。每个阶段需要验收检查。
本节判据
- 迁移第一刀必须满足:业务独立、回滚容易、数据可比。
- 绞杀者模式让事件系统在单体旁边生长。
- 双写阶段的一致性检测必须持续运行。
- 阶段2崩溃是最常见数据不一致根因。
- 回滚窗口按阶段设计,阶段4回滚代价极高。
阅读导航:上一节 12.1 事件风暴工作坊 | 下一节 12.3 反模式清单
双写方案的完整设计
双写是迁移中最复杂的阶段。设计双写方案时必须回答三个问题:
| 问题 | 答案的影响 | 常见误区 |
|---|---|---|
| 谁负责写事件? | 业务服务还是独立进程 | 业务服务写事件会导致延迟敏感路径阻塞 |
| 失败时怎么回退? | 数据库已提交但事件失败 | 很多人只考虑事件失败回退,没考虑数据库也失败的情况 |
| 数据以哪个为准? | 数据库还是事件 | 双写阶段必须以数据库为准,事件只是副本 |
这三个问题在设计评审时如果没有明确答案,双写阶段的故障率会高出三倍。
本地消息表 vs 直接发 Kafka
双写有两种实现:本地消息表和直接发 Kafka。
本地消息表:业务写数据库时同时写一张 outbox 表,由独立进程轮询 outbox 表发事件。
优点:本地事务保证数据库和 outbox 原子性;业务代码不关心 Kafka 可用性。
缺点:增加了数据库写压力;轮询延迟引入额外延迟。
直接发 Kafka:业务写数据库后直接调用 Kafka producer。
优点:更简单,没有额外轮询延迟。
缺点:Kafka 不可用时业务写入也会失败;producer 发送延迟影响业务响应时间。
选择取决于业务对延迟的敏感度和 Kafka 的可用性 SLA。如果 Kafka SLA 是 99.99% 可用且业务可以容忍 100ms 的 producer 延迟,直接发 Kafka 更简单。否则用本地消息表。
双写阶段的日常运维
双写阶段的日常运维不是「部署完就完了」。需要持续监控:
- outbox 堆积量:独立进程处理 outbox 的速度是否跟得上业务写入速度。
- 数据库与事件的延迟差:事件延迟是否持续增长。
- 双写失败告警:Kafka producer 失败或数据库写失败时立即告警。
这些监控项必须在双写阶段就有,不能等出现问题了再补。
本机实测验证
本环境(Temurin 25.0.5+8)编译运行 MigrationConsistencyCheck.java:
javac --release 21 MigrationConsistencyCheck.java && java MigrationConsistencyCheck
输出显示 O-004 在双写崩溃场景下出现数据不一致。这是项目迁移前必须解决的典型问题。
阶段验收检查表
每个阶段迁移结束后,必须通过验收检查:
| 检查项 | 阶段2 | 阶段3 | 阶段4 |
|---|---|---|---|
| 一致性检测通过 | ✓ | ✓ | N/A |
| 回滚演练通过 | ✓ | ✓ | N/A |
| 读模型投影与DB一致 | N/A | ✓ | N/A |
| 消费者lag归零 | N/A | N/A | ✓ |
| 旧库无写入流量 | N/A | N/A | ✓ |
如果任何一项不通过,下一阶段不能开始。这不是流程冗余,而是防止问题累积到无法回退的阶段。
从单体到事件驱动的心态转变
迁移不仅是技术问题,更是心态问题。很多团队在阶段2卡住,是因为旧习惯难改:
- 习惯直接查库:团队成员总想在代码里直接 SELECT,不想通过事件消费来获取数据。
- 习惯同步调用:遇到需要跨服务数据时,第一反应是 HTTP 调用而不是订阅事件。
- 不信任最终一致:看到投影延迟就焦虑,认为数据不一致等于系统不可用。
这些习惯需要团队培训来纠正。建议在阶段1到阶段2之间,安排一次全团队的事件驱动架构培训,让所有人理解「为什么不是直接查库」以及「延迟不是 bug 而是设计选择」。
反直觉的结论
四阶段迁移最反直觉的结论是:最安全的阶段不是阶段1,而是阶段2。阶段1没有任何保护措施,所有数据依赖直接查询。阶段2虽然有双写风险,但至少有数据库作为比对基准。阶段4虽然完全事件驱动,但已经没有比对基准了。
所以真正的风险不是从阶段1到阶段2,而是从阶段2到阶段3——你失去了数据库这个安全网。
本节判据
- 迁移第一刀必须满足:业务独立、回滚容易、数据可比。
- 绞杀者模式让事件系统在单体旁边生长,不是一次性重写。
- 双写阶段的一致性检测必须持续运行,不等人工发现。
- 阶段2崩溃场景(DB写成功但事件丢失)是最常见数据不一致根因。
- 回滚窗口按阶段设计,阶段4回滚代价极高,是单向门。
- 单体耦合度高的系统不适合四阶段迁移,应考虑直接重建。
- 回滚演练必须包含生产数据子集的沙箱环境。
- 双写不是最危险的阶段,阶段2到阶段3才是(失去了数据库比对基准)。
阅读导航:上一节 12.1 事件风暴工作坊 | 下一节 12.3 反模式清单
本地消息表方案
双写有两种实现:本地消息表和直接发 Kafka。
本地消息表:业务写数据库时同时写一张 outbox 表,由独立进程轮询发事件。
优点:本地事务保证数据库和 outbox 原子性;业务代码不关心 Kafka 可用性。
缺点:增加了数据库写压力;轮询延迟引入额外延迟。
直接发 Kafka:业务写数据库后直接调用 Kafka producer。
优点:更简单,没有额外轮询延迟。
缺点:Kafka 不可用时业务写入也会失败;producer 发送延迟影响业务响应时间。
选择取决于业务对延迟的敏感度和 Kafka 的可用性 SLA。
双写崩溃场景分析
双写阶段最大的风险不是代码 bug,而是崩溃时的不一致。常见场景:
开始事务
→ 写数据库(成功)
→ 进程崩溃
→ 事件未发送
→ 数据库已提交,事件缺失
检测方法:定期扫描数据库中缺少对应事件的记录。检测频率取决于数据一致性要求的严格程度——金融场景每分钟检测,日志场景每小时检测。
迁移阶段的组织配合
迁移不是技术团队单独能完成的工作。需要产品经理确认业务场景优先级、DBA 配合数据迁移、运维团队准备回滚方案、测试团队编写兼容性测试用例。
组织配合的关键是建立「迁移节奏」:每周评审会确认进度,每月里程碑检查是否达到阶段验收标准。没有节奏感的迁移会在双写阶段无限期拖延。
反直觉的结论
四阶段迁移最反直觉的结论是:最安全的阶段不是阶段1,而是阶段2。阶段1没有任何保护措施,阶段2至少有数据库作为比对基准。阶段4虽然完全事件驱动,但已经没有比对基准了。
所以真正的风险不是从阶段1到阶段2,而是从阶段2到阶段3——你失去了数据库这个安全网。
本节判据
- 迁移第一刀必须满足三个条件:业务上可独立运行、技术上可回滚、数据上可比对。
- 绞杀者模式让事件系统在单体旁边生长,不是一次性重写。
- 双写阶段的一致性检测必须持续运行,不等人工发现不一致。
- 阶段2崩溃场景(DB写成功但事件丢失)是最常见数据不一致根因。
- 回滚窗口按阶段设计:阶段2回滚低、阶段3回滚中、阶段4回滚极高。
- 单体耦合度高的系统不适合四阶段迁移,应考虑直接重建。
- 回滚演练必须包含生产数据子集的沙箱环境,不能只走开发环境。
- 数据漂移依赖定期对账或影子测试,双写阶段是唯一有效比对窗口。
- 迁移阶段的组织配合需要建立节奏感:周评审、月里程碑、阶段验收。
- 真正的风险不是从阶段1到阶段2,而是从阶段2到阶段3——失去数据库比对基准。
- 迁移完成后的持续改进才是长期成功的关键,不是迁移本身。
迁移路径回顾
四阶段迁移不是唯一路径,但它是风险最低的路径。对于已经有成熟事件驱动经验的团队,可以考虑跳过阶段2直接进入阶段3。对于初次尝试事件驱动的团队,建议严格遵循四阶段,每一阶段都通过验收检查后再进入下一阶段。
选择哪种路径取决于团队的经验、系统的复杂度和业务对风险的容忍度。没有放之四海而皆准的迁移方案,只有适合自己团队的方案。
迁移不是终点
迁移完成后,真正的挑战才刚刚开始。事件驱动系统需要持续的运维和治理:
- 事件目录维护:新事件加入、旧事件下线需要持续更新事件目录。
- 消费者管理:新消费者加入需要审批、现有消费者需要监控 lag。
- 性能优化:随着事件量增长,需要持续优化生产者和消费者的性能。
迁移只是从单体到事件驱动的第一步,后续的治理才是长期成功的关键。
阅读导航:上一节 12.1 事件风暴工作坊 | 下一节 12.3 反模式清单
迁移后的持续改进
迁移完成后,还需要持续的改进工作:
- 事件质量监控:监控事件的完整性、及时性和准确性。
- 消费者健康度:监控消费者的 lag、错误率和重平衡频率。
- 成本优化:随着业务增长,持续优化配额使用和成本分摊。
迁移不是一次性的项目,而是持续演进的过程。
- 迁移后的持续改进包括事件质量监控、消费者健康度和成本优化。
- 迁移只是第一步,后续的治理才是长期成功的关键。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。