分布式事件溯源的核心是保证事件线性排序、可重放、不丢不乱;需用AggregateID+应用层生成的Version替代数据库自增ID和不可靠时间戳,结合发件箱模式与纯内存Apply()实现一致性与确定性。

Go 里做分布式事件溯源,核心不是“怎么分布”,而是“怎么让多个服务写入的事件能线性排序、可重放、不丢不乱”。单机版 Event Sourcing 靠 Version + Timestamp 就能撑住;分布式下,这两者都可能失效——时钟漂移让 Timestamp 不可比,多节点并发写让 Version 冲突或跳变。必须换一套锚点。
为什么不能直接用数据库自增 ID 做事件排序
自增 ID 是本地事务序号,只在单表/单实例内单调递增。跨服务、跨库、分库分表后,ID 完全不可比:A 服务插入的 event_id=1001 可能比 B 服务的 event_id=1002 发生得晚,重放时顺序就错了。更糟的是,MySQL 的 auto_increment 在主从切换、批量插入、replace into 场景下还会跳号甚至回退。
- 永远别把
event_id当因果依据,它只是存储索引 - 排序唯一可靠依据是:
AggregateID+Version(乐观锁版本),且Version必须由应用层生成(不是 DB 返回) - 如果要用时间戳辅助调试,
Timestamp必须用time.Now().UTC(),并记录 NTP 同步状态,不能信系统本地时钟
如何保证多服务写入事件的原子性与一致性
分布式下,“生成事件 → 持久化 → 发布”三步必须原子。常见翻车是:事件写进 DB 成功,但 Kafka 写失败,导致下游收不到;或者 Kafka 写成功,DB 却因唯一约束报错,状态和事件脱节。
- 用发件箱模式(Outbox Pattern):所有事件先写入本地业务库的
outbox_events表(和业务操作同事务),再由独立进程轮询该表并投递到 Kafka - 避免跨库事务:不要试图用 PostgreSQL 的
db.BeginTx()包住 Kafka 写入——kafka-go 不支持 XA,也不该支持 - 投递进程必须带幂等重试:每次投递前查
outbox_events.status = 'pending',成功后UPDATE ... SET status = 'sent',失败则 sleep 后重试 - 投递失败超过阈值(比如 5 次),转存 DLQ 表,人工介入,别静默丢弃
聚合根 Apply() 方法在分布式环境下的安全边界
Apply() 是纯内存函数,这点在分布式下更关键。任何外部调用(HTTP、DB 查询、Redis Get)都会破坏重放确定性——同一事件在不同机器、不同时间重放,结果可能不一致。
立即学习“go语言免费学习笔记(深入)”;
- Apply() 只能读当前聚合内存状态 + 输入事件字段,输出新状态(推荐返回 struct 而非修改 receiver)
- 禁止在 Apply() 里调用
http.Get、db.QueryRow、cache.Get—— 这些都属于“副作用”,应移到 Command Handler 层处理 - 聚合 ID 校验必须严格:
if event.AggregateID() != a.ID,否则 A 聚合误收 B 聚合的事件,状态直接污染 - Version 更新必须前置:生成事件时立即设
event.Version = a.Version + 1,不是入库后靠 DB 返回,否则并发写会覆盖
重放时如何应对跨服务事件格式演进
服务 A 今天发 UserRegisteredV1,明天升级成 UserRegisteredV2(加了 ReferrerID string 字段),老聚合重放新事件就会 panic。JSON 反序列化失败不是边缘情况,是常态。
- 别用
json.Unmarshal([]byte, &UserRegisteredV2{})直接解,改用json.RawMessage存原始字节 - 重放时先按
EventType和Version查 handler,再用对应 struct 解析;解析失败则 fallback 到兼容逻辑(比如缺字段设默认值,类型不匹配忽略) - 禁止用
map[string]interface{}接原始 JSON:整型 ID 会被转成float64,json.Number也容易漏处理 - 每个事件 struct 必须显式实现
EventType()和AggregateID()方法,不能靠匿名嵌入——Go 没有虚函数,嵌入不会自动继承接口
真正难的不是写代码,是定义清楚哪些东西必须全局唯一(如 AggregateID 格式)、哪些必须由应用控制(如 Version 生成时机)、哪些必须容忍不一致(如 Timestamp 仅用于审计)。分布式事件溯源不是堆技术,是划边界。


















