架构设计基础系列:事件溯源模式浅析
图片来源网络,侵权删

1. 引言
1.1 研究背景
- 传统CRUD模型的局限性:状态覆盖导致审计困难、无法追溯历史。
- 分布式系统复杂性的提升:微服务架构下数据一致性、回滚与调试的需求激增。
- 监管合规性要求:金融、医疗等领域对数据不可篡改性的强制需求。
1.2 事件溯源的核心价值
- 完整历史追溯:通过事件流重建任意时间点状态。
- 业务逻辑显式化:事件作为业务意图的直接表达。
- 解耦与扩展性:读写模型分离(CQRS)支持独立优化。
2. 事件溯源的理论基础
2.1 核心概念
- 事件(Event):不可变的状态变更记录,如
AccountOpened、OrderCancelled。 - 事件存储(Event Store):有序、持久化的事件流数据库。
- 聚合根(Aggregate Root):一致性边界内的业务实体,负责生成事件。
- 投影(Projection):从事件流生成实时查询视图。
2.2 事件溯源与CQRS模式
- 命令与查询职责分离:写模型通过事件溯源处理业务逻辑,读模型通过投影提供高性能查询。
- 最终一致性模型:通过异步事件传播实现读写模型同步。
2.3 事件溯源的数学基础
- 事件流作为状态函数:

其中 StSt 表示时间 tt 的状态,eiei 为事件,ff 为状态转换函数。
- 幂等性与事件顺序:事件顺序必须严格保证,幂等性设计可应对重复投递问题。
2.4 事件溯源优点
-
事件不可变,并且可使用只追加操作进行存储。 用户界面、工作流或启动事件的进程可继续,处理事件的任务可在后台运行。 此外,处理事务期间不存在争用,此过程可极大提高应用程序的性能和可伸缩性,尤其是对于演示级别或用户界面。
-
**事件是描述已发生操作的简单对象以及描述事件代表的操作所需的相关数据。 **事件不会直接更新数据存储。 只会对事件进行记录,以便在合适的时间进行处理。 使用事件可简化实现和管理。
-
事件通常对域专家而言具有意义,然而对象关系阻抗不匹配却会让复杂数据库表变得难以理解。表是表示系统的当前状态(而不是已发生事件)的人工构造。
-
事件溯源不需要直接更新数据存储中的对象,因而有助于防止并发更新造成冲突。 但是,域模型必须仍然设计为避免可能导致不一致状态的请求。
-
事件的只追加存储提供的审核线索可用于监视对数据存储采取的操作。 它可以通过随时重播事件将当前状态重新生成为具体化视图或投影,并且可以帮助测试和调试系统。 此外,使用补偿事件取消更改的要求可以提供反向更改的历史记录。 如果模型存储了当前状态,则此功能不会是这种情况。 事件列表还可用于分析应用程序性能和检测用户行为趋势。 或者,也可用于获取其他有用的业务信息。
-
事件存储会引发事件,任务会执行操作以响应这些事件。 通过将任务从事件中分离,可提供灵活性和可扩展性。 任务知道事件类型和事件数据,但不知道触发事件的操作。 此外,多个任务可以处理每个事件。 这样可实现与仅侦听事件存储引发的新事件的其他服务和系统的轻松集成。 但是,事件溯源事件的级别通常非常低,可能需要生成特定的集成事件。
通过执行响应事件的数据管理任务和具体化存储事件的视图,事件溯源通常与 CQRS 模式结合。

3. 主流事件溯源框架分析
3.1 框架分类
| 类别 | 代表框架 | 核心能力 |
|---|---|---|
| 企业级框架 | Axon Framework | 完整CQRS支持、分布式事件总线、Saga管理 |
| 轻量级库 | Eventuous (.NET) | 简化聚合根定义、多存储后端支持 |
| 事件流平台 | Kafka + Kafka Streams | 高吞吐量事件处理、流式计算集成 |
| 云原生服务 | AWS EventBridge | 无服务器事件路由、与Lambda/DynamoDB集成 |
3.2 Axon Framework 深度解析
3.2.1 架构设计
- 命令模型:
CommandGateway处理业务命令,驱动聚合根生成事件。 - 事件存储:支持JDBC、MongoDB、Axon Server(专用存储)。
- 查询模型:
QueryGateway从投影读取数据,支持订阅更新。
3.2.2 核心组件
- 聚合根生命周期管理:通过
@Aggregate注解定义,自动处理事件重放。 - Saga事务协调:长流程事务通过
@Saga实现最终一致性。 - 事件回放与快照:定期生成快照(Snapshot)优化长事件流重建效率。
3.2.3 代码示例
// 定义聚合根
@Aggregate
public class BankAccountAggregate {@AggregateIdentifierprivate String accountId;private BigDecimal balance;@CommandHandlerpublic BankAccountAggregate(OpenAccountCommand cmd) {apply(new AccountOpenedEvent(cmd.getAccountId(), cmd.getOwner()));}@EventSourcingHandlerpublic void on(AccountOpenedEvent event) {this.accountId = event.getAccountId();this.balance = BigDecimal.ZERO;}// 处理存款命令@CommandHandlerpublic void handle(DepositCommand cmd) {apply(new DepositCompletedEvent(accountId, cmd.getAmount()));}
}
3.3 EventStoreDB 的存储引擎优化
- 追加写优化:基于日志结构合并树(LSM-Tree)实现高吞吐量写入。
- 内置订阅机制:支持持久化订阅(Persistent Subscription)与流分区(Stream Partitioning)。
- 多语言支持:通过gRPC/HTTP API提供跨平台兼容性。
3.4 云原生框架对比
| 框架 | 事件存储 | 适用场景 | 局限性 |
|---|---|---|---|
| Axon Server | 专用存储 | 企业级微服务 | 运维复杂度高 |
| Kafka | 分布式日志 | 高吞吐量事件流 | 需额外实现聚合根逻辑 |
| AWS EventBridge | 无服务器事件总线 | 快速构建Serverless应用 | 功能扩展性受限 |

4. 实际案例研究
4.1 案例一:银行账户管理系统
4.1.1 需求分析
- 合规性要求:所有资金变动记录必须可审计。
- 高并发挑战:支持每秒万级交易处理。
4.1.2 技术选型
- 框架:Axon Framework + Axon Server
- 存储:EventStoreDB + PostgreSQL(读模型)
- 消息总线:RabbitMQ
4.1.3 事件流设计
1. AccountOpenedEvent
2. DepositCompletedEvent
3. TransferOutEvent
4. TransferInEvent
5. InterestCalculatedEvent
4.1.4 性能优化
- 快照机制:每1000个事件生成一次快照,减少聚合根重建时间。
- 分片策略:按账户ID哈希分片事件存储,提升并发处理能力。
4.2 案例二:电商订单系统
4.2.1 业务场景
- 订单状态追踪:从创建、支付到物流的全流程事件记录。
- 实时库存管理:通过事件流触发库存扣减与回滚。
4.2.2 技术实现
- 框架:.NET + Eventuous
- 存储:MongoDB(事件存储) + Elasticsearch(读模型)
- 投影逻辑:
public class OrderProjection : IEventHandler<OrderCreatedEvent> {public async Task Handle(OrderCreatedEvent @event) {await _readModel.UpdateAsync(@event.OrderId, model => {model.Status = "Created";model.Items = @event.Items;});}
}

5. 技术挑战与解决方案
5.1 事件版本迁移
- 问题:业务变更导致事件结构不兼容。
- 解决方案:
- 向上兼容:新增字段而非修改旧字段。
- 版本转换中间件:在读取时动态转换旧事件版本。
5.2 长事件流性能瓶颈
- 问题:重放数万事件导致响应延迟。
- 解决方案:
- 定期快照:存储聚合根的中间状态。
- 增量投影:仅处理新增事件而非全量重放。
5.3 分布式事务一致性
- 问题:跨服务事件处理的最终一致性。
- 解决方案:
- Saga模式:通过补偿事件实现回滚。
- 事件幂等性:唯一ID去重或版本号校验。
**6. **事件溯源使用场景
请在以下方案中使用此模式:
- 要捕获数据中的意图、用途或原因。 例如,可将对客户实体的更改捕获为一系列特定事件类型,例如_已搬家_、帐户已关闭_或_已身故。
- 尽量减少或完全避免出现数据更新冲突。
- 需要记录发生的事件,并重播事件以还原系统状态、回滚更改或保留历史记录和审核日志。 例如,任务涉及多个步骤时,可能需要执行操作来恢复更新,并重播某些步骤使数据重返一致的状态。
- 使用事件时。 这是应用程序操作的自然功能,且几乎不需要其他开发或实现工作。
- 需要将输入或更新数据的过程从应用这些操作所需的任务中分离。 此更改可能是为了提高 UI 的性能,或者是为了将事件分发给其他在事件发生时采取操作的侦听器。 例如,可以将工资管理系统与开支报销网站集成。 由事件存储引发的用于响应网站中数据更新的事件可同时供该网站和工资管理系统使用。
- 希望随要求更改而灵活更改具体化模型和实体数据的格式,或需要调整读取模型或公开数据的视图(与 CQRS 结合使用时)。
- 与 CQRS 结合使用且更新读取模型时最终一致性可接受或事件流中的解冻实体和数据的性能影响可接受。
此模式在以下情况中可能不起作用:
- **小型域或简单域、几乎或完全没有业务逻辑的**系统或者自然地适用于传统 CRUD 数据管理机制的非域系统。
- 要求一致性和数据视图实时更新的系统。
- 不需要审核线索、历史记录以及回滚和重播操作功能的系统。
- 基础数据更新冲突发生率低的系统。 例如,主要是添加数据而不是更新数据的系统。

7. 结论
事件溯源框架通过将业务逻辑显式化为事件流,为构建高可靠、可审计的分布式系统提供了全新范式。主流框架如Axon、EventStoreDB和Kafka在不同场景下展现出独特优势,但需权衡性能、复杂性与扩展性。未来,随着云原生技术与AI的融合,事件溯源将在实时分析、自动化决策等领域发挥更大价值。
参考文献
- https://learn.microsoft.com/zh-cn/azure/architecture/patterns/event-sourcing
- https://codeopinion.com/greg-young-answers-your-event-sourcing-questions/
相关文章:
架构设计基础系列:事件溯源模式浅析
图片来源网络,侵权删 1. 引言 1.1 研究背景 传统CRUD模型的局限性:状态覆盖导致审计困难、无法追溯历史。分布式系统复杂性的提升:微服务架构下数据一致性、回滚与调试的需求激增。监管合规性要求:金融、医疗等领域对数…...
ResNet系列和ViT系列预训练模型权重文件下载
一、简单介绍 OpenAI CLIP项目提供的预训练模型权重文件列表,主要包含两种架构系列和不同规模配置: ResNet系列 (RN) 基础版本:RN50(ResNet-50)扩展版本:RN50x4、RN50x16、RN50x64(宽度扩展&am…...
【力扣hot100题】(035)二叉树的中序遍历
正常方法递归很简单,于是又学了一种栈的方法。 原理如下:每次循环先尽量将目前节点入栈并左移,没有左节点时回到栈首节点将目前节点放入结果容器中并移出栈外,目前节点变为该节点的右节点,循环结束条件是目前节点为nu…...
《数字图像处理》教材寻找合作者
Rafael Gonzalez和Richard Woods所著的《数字图像处理》关于滤波器的部分几乎全错,完全从零开始写,困难重重。关于他的问题已经描述在《数字图像处理(面向新工科的电工电子信息基础课程系列教材)》。 现寻找能够共同讨论、切磋、…...
批量删除 txt/html/json/xml/csv 等文本文件中的重复行
在文本文件中,可能会存在一些重复的数据行,这可能不是我们期望的,因此我们会碰到需要删除文本文件中重复行的情况。如果是人工删除,当文件较大或者数量较多的时候,处理的难度会较大。今天就给大家介绍一下批量删除文本…...
LangChain 使用向量数据库介绍与使用
LangChain 是一个用于构建大语言模型(LLM)应用的框架,而向量数据库在 LangChain 中主要用于实现检索增强生成(RAG, Retrieval-Augmented Generation),即通过向量搜索从外部知识库中快速检索相关信息,辅助大模型生成更准确的回答。以下是具体的使用方法: 1. 核心流程 L…...
基于微信小程序的智慧乡村旅游服务平台【附源码】
基于微信小程序的智慧乡村旅游服务平台(源码L文说明文档) 目录 4系统设计 4.1系统功能设计 4.2系统结构 4.3.数据库设计 4.3.1数据库实体 4.3.2数据库设计表 5系统详细实现 5.1 管理员模块的实现 5.1.1旅游景点管理…...
d202542
一、142.环形链表I 142. 环形链表 II - 力扣(LeetCode) 用set统计一下 如果再次出现那么就环的第一个return返回就行 public ListNode detectCycle(ListNode head) {Set<ListNode> set new HashSet<>();ListNode cur head;while(cur ! …...
NodeTextFileCollectorScrapeError 报警原因及解决方法
现象 prometheus 经常有告警 NodeTextFileCollectorScrapeError 查看 node-exporter 日志出现如下报错 time2025-04-01T06:43:18.266Z levelERROR sourcetextfile.go:248 msg"failed to collect textfile data" collectortextfile fileipmitool.prom err"fail…...
RapidJSON 处理 JSON(高性能 C++ 库)(四)
第四部分:RapidJSON 处理 JSON(高性能 C++ 库) 📢 快速掌握 JSON!文章 + 视频双管齐下 🚀 如果你觉得阅读文章太慢,或者更喜欢 边看边学 的方式,不妨直接观看我录制的 RapidJSON 课程视频!🎬 视频里会用更直观的方式讲解 RapidJSON 的核心概念、实战技巧,并配有…...
80. Linux内核定时器实验
一、Linux内核定时器原理 1.1、内核时间管理 1、Cortex-M内核使用systick作为系统定时器。 2、硬件定时器、软件定时器,原理是依靠系统定时器来驱动。 3、linux内核频率可以配置,图形化界面配置。 4、重点,HZ表示系统节拍率, 1.…...
Java 可变参数全解析:动态参数传递的实践指南
Java 可变参数全解析:动态参数传递的实践指南 一、可变参数:Java 方法的灵活扩展 在狂神说 Java 第 49 集课程中,我们系统学习了 Java 可变参数的核心原理。作为 Java SE 5 引入的重要特性,可变参数允许方法接受动态数量的输入&…...
C++类与对象(上):从入门到实践
目录 一、引言 二、面向过程和面向对象初步认识 2.1 面向过程编程 2.2 面向对象编程 三、类的引入 四、类的定义 4.1 定义格式 4.2 定义方式 4.3 成员变量命名规则建议 五、类的访问限定符及封装 5.1 访问限定符 5.2 封装 六、类的作用域 七、类的实例化 7.1 概念…...
Lumerical ------ Edge coupler design
Lumerical ------ Edge coupler design 引言正文无 Si Substrate 的仿真步骤有 Si Substrate 的仿真步骤引言 本文,我们将使用官方提供的 Edge coupler 设计教程,但是中间会带有作者本人的设计的感悟。 正文 无 Si Substrate 的仿真步骤 打开 Edge_Coupler_No_Substrate.l…...
大语言模型本质上还是自动化,而不是智能化
大语言模型本质上仍然是自动化或高级自动化,而非真正的智能化,原因可以从以下几个方面进行分析:1、自动化与智能化的本质区别自动化:大语言模型通过预训练和微调,基于大量数据和规则生成输出。它的行为是基于输入数据的…...
cmake 中的命令
命令描述示例capabilities报告 CMake 内置的功能,以 JSON 格式输出cmake -E capabilitiescat连接文件并将其内容打印到标准输出cmake -E cat file1.txt file2.txtchdir在指定目录中运行命令cmake -E chdir /path/to/dir commandcompare_files比较两个文件是否相同cm…...
python数据结构——链表、栈、队列
一、思维梳理: 二、双向循环链表: class Node:def __init__(self,data):self.data dataself.next Noneself.prev Noneclass DoubleLink:def __init__(self):self.size 0self.head Nonedef is_empty(self):return self.size 0def add_end(self,dat…...
centos操作系统如何更换yum镜像源
CentOS Linux 是一个免费提供的、社区支持的Linux发行版,由CentOS项目社区贡献者开发、分发和维护。2020年CentOS项目宣布将把全部投资转移到CentOS Stream,作为即将发布的 Red Hat Enterprise Linux版本的上游开发平台。因此,CentOS Linux更新和发布将在2021年至2024年期间…...
【Linux篇】自主Shell命令行解释器
📌 个人主页: 孙同学_ 🔧 文章专栏:Liunx 💡 关注我,分享经验,助你少走弯路! 文章目录 1. 获取用户名的接口2. 等待用户输入接口3. 将上述代码进行面向对象式的封装4. 命令行解析5.…...
Tomcat 部署 Jenkins.war 详细教程(含常见问题解决)
在Tomcat中部署Jenkins.war文件是一个相对简单的过程,以下是详细步骤: 1. 准备工作 确保已安装JDK:Jenkins需要Java环境,建议安装JDK 8或更高版本。 下载Jenkins.war:https://pan.quark.cn/s/c4fd7711a1b3 下载Tomc…...
我的创作纪念日-一周年
目录 机缘 收获 日常 成就 憧憬 机缘 时光荏苒,转行计算机已经是第5个年头了。从Python入门,到C入土,兜兜转转,发现自己也只是初窥门径,习得皮毛。我从6年前开始潜水CSDN,学习各路大佬的技术经验&…...
多线程代码案例 - 1
目录 单例模式 1. 饿汉模式 2. 懒汉模式 单例模式与多线程 问题1 问题2 问题3 完! 单例模式 单例模式是一种设计模式。 设计模式,是我们在编写代码时候的一种软性的规定,也就是说,我们遵守设计模式,代码的下限…...
display:none与visibility的区别
1. 是否占据空间 display: none:元素完全从文档流中移除,不占据任何布局空间。后续元素会“填补”它的位置。visibility:hidden:元素仍占据布局空间,但内容不可见(类似透明占位符)。 2.渲染与性能 displ…...
算法 | 基于蜣螂优化算法求解带时间窗的车辆路径问题(VRPTW)研究(附matlab代码)
基于蜣螂优化算法求解带时间窗的车辆路径问题(VRPTW)研究 🍏🍏🍏🍏🍏🍏🍏🍏🍏🍏🍏🍏🍏🍏🍏 摘要 带时间窗的车辆路径问题(VRPTW)是物流配送中的核心优化难题。本文提出一种基于蜣螂优化算法(Dung Beetle Optimizer, DBO)的求解方法,通过…...
开发体育赛事直播系统主播认证功能技术实现方案
该体育直播系统系统由东莞梦幻网络科技开发,使用 ThinkPHP 作为后端,Vue.js 作为 PC/H5 端框架,Java 和 Objective-C 分别用于安卓和 iOS 开发。 1、前端实现 (Vue.js) <template><div class"anchor-certification">…...
国产三维CAD「皇冠CAD」在汽车零部件领域建模教程:刹车片
本教程深度融合三维皇冠CAD(CrownCAD)的MBD(Model-Based Definition)设计理念,通过参数化建模、智能约束管理、动态装配验证等功能,实现数据驱动设计,精准解决了汽车制动系统中精密制动组件的设…...
基于指针的线程池
使用原线程池 当 push 和 pop的对象过大时,消耗时延过高,需优化线程池 采用 std::move() unique_ptr的方法,能极大的减少时延, 实际就是避免了多次拷贝,直接使用指针。 代码实现 ThreadPool…...
GitHub与Gitee各是什么?它们的区别与联系是什么?
李升伟 整理 GitHub 介绍 GitHub 是一个基于 Git 的代码托管平台,主要用于版本控制和协作开发。它支持多人协作,提供代码托管、问题跟踪、代码审查、项目管理等功能。GitHub 是全球最大的开源社区,许多知名开源项目都在此托管。 主要功能&…...
SpringMvc获取请求数据
基本参数 RequestMapping("save5") ResponseBody public User save5(String name, int age) {User user new User();user.setName(name);user.setAge(age);return user; } 在url中将name与age进行编写,通过框架可以提取url中的name与age,这…...
简述竞赛经历在考研复试中的作用
文章目录 前言拟录取情况baichuicxyAshy佬齐总结回首展望 前言 随着就业形式的不断变化,竞赛奖项在就业中能起到的作用在逐步减弱。想拿到头部大厂实习面试资格,最低要区域赛银牌起步,当然这也仅仅是面试资格。 那么,竞赛经历在…...
