Event Sourcing 事件溯源是什么?和普通操作日志有什么区别?
下面是一段教学用的模拟面试。
🧑💻 面试官:你知道事件溯源吗?
🙋♂️ 我:每次操作都记录日志,出问题能查回来。
🧑💻 面试官:如果删掉日志,当前状态还在数据库里,系统仍然照常工作,这就是事件溯源吗?
🙋♂️ 我:不一定,事件历史得是状态的来源。
🧑💻 面试官:重放历史事件会不会再发一次邮件?三年前的事件格式改了怎么办?快照能直接替代历史吗?
要看状态从哪里来:普通日志辅助解释发生过什么,事件溯源则用可信事件历史重建状态。
面试速答(60 秒版)
Event Sourcing 把已经发生的业务事件按规则追加保存,事件历史是重建相关业务状态的权威来源。
普通操作日志通常用于审计或排错,当前状态仍来自其他存储。即使有很多日志,也不代表系统能用它们完整、确定地还原业务状态。
事件溯源通过应用事件计算当前状态,可以配合快照减少重放工作,也可以生成读投影。但快照和读模型不应被误当成自动替代事件历史的另一份真相。
它还需要处理事件版本、同一聚合的顺序和并发、投影重复处理,以及重放副作用隔离。需要历史追踪和重建时有价值,简单项目未必值得承担这些成本。

知识点详解:一条事件必须足够支持下一次状态变化
记录操作,和记录已经发生的事实
假设用户申请更改预约时间。“更改预约”是命令,系统还要判断是否允许。
检查通过并提交以后,“预约时间已改为某个时刻”才是已经发生的事件。被拒绝的命令不能当作成功事件应用到状态里。
事件应包含状态转换需要的信息和身份、版本等元数据,而不是一段只能给人看的日志文字。“用户操作成功”通常不足以重建到底改了什么。
当前状态怎样从历史得到?
假设某个预约先有创建事件,再有改期事件,最后有取消事件。系统按合法顺序应用这些事件,就得到当前预约状态。
应用事件的函数应尽可能确定:同样的历史,应得到相同的状态。不能在重建时重新读取当前时间或请求一个变化中的外部接口,来猜当时发生了什么。
事件历史是状态来源,是它与旁路日志最重要的区别。Microsoft Event Sourcing 文档
重建状态不等于重新执行业务动作
假设创建预约时还发了一封邮件。以后重放历史,只是计算“预约已创建”的状态,不应再次向用户发送创建邮件。
因此状态演算和对外副作用要分开。更新读投影可以按事件身份和版本幂等处理;邮件、扣款等外部动作则有自己的可信执行与恢复机制。
不能在 apply(event) 里同时更新状态和直接发邮件,然后说“重放前把网络关掉就行”。隔离应该是设计的一部分。
快照解决的是重放成本
历史越来越长,每次从第一条事件开始可能很慢。快照保存某个版本时已经计算出的状态,后续只应用这个版本以后的事件。
快照要标明对应位置和格式版本,才能知道从哪里继续。它是加速用的派生状态,不是让所有历史都可以无条件删除。
如果快照不可信或不兼容,应有验证和重建策略。保留范围还涉及法规、隐私和产品要求,不能把“事件追加保存”当成永远不能处理敏感数据的借口。

图中的版本 2、版本 3 表示该事件流的位置。快照数据格式的版本需要另外管理,不能把两者混为一谈。
旧事件怎么面对新代码?
已经保存的历史事件不能因为代码升级就自动改变含义。可以采用明确事件版本、兼容读取或转换策略,让新代码理解旧记录。
转换也要维护原始语义,不能为了适配新字段而凭空补一个改变业务意义的默认值。必要时用历史样本测试重建后的状态。
同一聚合的并发追加也需要版本约束,防止两个命令基于同一个旧状态各自写入冲突事件。分布式全局总顺序则不是所有事件溯源系统都必须拥有的条件。
哪些项目值得用?
需要追踪业务事实、解释历史状态、生成不同投影或重建特定状态时,事件溯源可能提供价值。
代价是事件模型和版本需要长期维护,重建、备份、隐私处理及调试也更复杂。只有“希望多留一点日志”,通常不足以支持把整个状态存储方式改掉。

面试官继续追问
事件溯源必须用 Kafka 吗?
不必。消息分发系统与权威事件存储职责不同,要核对持久性、顺序、版本约束和读取能力,不能按产品名字认定架构。
CQRS 和事件溯源为什么常一起出现?
事件方便生成不同读投影,因此适合配合使用,但一个分职责,一个决定状态来源,仍然可以单独采用。
测试重放时应该检查什么?
历史样本能否得到预期状态,旧事件版本是否可读,投影重复处理是否安全,以及是否意外触发对外副作用。
面试速记卡
- 事件溯源:事件历史是业务状态的权威来源。
- 普通日志:辅助审计和排错,不一定足够重建状态。
- 重放要求:顺序、版本和确定的状态转换。
- 副作用边界:重建状态不能自动再次发邮件或扣款。
- 快照:加速重放的派生状态,需要位置与版本信息。
公司面试真题
这道题暂未收录可核验的公司真题来源。你可以先阅读本文解析,或浏览已收录的公司面试真题。
浏览公司面试真题 →