0

0

c# 事件溯源(Event Sourcing)模式下的并发和快照策略

畫卷琴夢

畫卷琴夢

发布时间:2026-01-19 12:13:02

|

428人浏览过

|

来源于php中文网

原创

并发冲突典型表现为OptimisticConcurrencyException,应对方式是版本校验+重载重放+幂等重试;快照按需触发(事件数阈值+耗时判断),加载时三段式合并快照与后续事件,序列化需确保无参构造、类型兼容与语义一致。

c# 事件溯源(event sourcing)模式下的并发和快照策略

事件溯源中并发冲突的典型表现和应对方式

在 C# 事件溯源系统中,并发写入最常触发 OptimisticConcurrencyException(或自定义的类似异常),本质是多个线程/请求对同一聚合根尝试基于不同版本号追加事件。EF Core 的 DbUpdateConcurrencyException、自研事件存储中校验 ExpectedVersion 失败,都属于这一类。

关键不是“避免并发”,而是“让并发失败可预测、可重试、不丢数据”:

  • 聚合根必须维护 Version(整数递增)字段,每次成功应用事件后 +1
  • 保存时严格校验:数据库 WHERE 子句含 WHERE AggregateId = @id AND Version = @expectedVersion,或事件存储要求写入时传入 expectedVersion
  • 捕获冲突后,**不直接抛出上层异常**,而是重新加载最新状态(含所有已存事件)、重放业务逻辑、生成新事件序列,再重试 —— 这个过程需幂等且无副作用
  • 重试次数建议限制(如 3–5 次),超限应降级为人工介入或异步补偿,避免雪崩

快照(Snapshot)该在什么时机触发

快照不是“定期执行”,而是“按需截断事件链”。核心判断依据只有一个:EventCount 超过阈值(如 100 或 500) 重建聚合耗时明显升高(实测 >50ms)。

不要用固定时间(如“每天凌晨”)或固定事件数(如“每 100 条”)硬编码触发,因为不同聚合生命周期差异极大 —— 订单聚合可能一天产生 20 个事件,而用户配置聚合可能三年才变 3 次。

推荐做法:

文心快码
文心快码

文心快码(Comate)是百度推出的一款AI辅助编程工具

下载
  • 在聚合加载逻辑中,统计从快照点开始读取的事件数量,若 ≥ SnapshotThreshold(例如 200),则在本次保存后主动创建快照
  • 快照本身只存聚合当前状态(State 对象),不含事件;其版本号必须与所覆盖的最后一个事件的 Version 一致
  • 快照存储需支持唯一键约束:(AggregateType, AggregateId, SnapshotVersion),防止重复写入

快照与事件如何协同加载聚合

加载聚合时,不能假设“有快照就一定用快照”,也不能“无视快照全量重放”。正确流程是三段式:

var snapshot = _snapshotStore.GetLatest(aggregateId);
if (snapshot != null)
{
    aggregate = new Order(snapshot.State); // 从快照重建
    var events = _eventStore.GetEvents(aggregateId, afterVersion: snapshot.Version);
    foreach (var e in events) aggregate.Apply(e); // 仅重放快照之后的事件
}
else
{
    aggregate = new Order(); // 空构造
    var events = _eventStore.GetEvents(aggregateId);
    foreach (var e in events) aggregate.Apply(e);
}

注意两个细节:

  • GetEvents(aggregateId, afterVersion: snapshot.Version)afterVersion 是排他性参数(即从 snapshot.Version + 1 开始),否则会重复应用快照已包含的事件
  • 快照中的 State 必须是“可序列化纯净对象”,不能含引用、委托、DbContext 实例等运行时依赖,否则反序列化后无法安全调用 Apply()
  • 若快照损坏或版本错乱,要能 fallback 到全量事件重放 —— 所以快照永远是优化手段,不是状态唯一来源

C# 实现快照时最容易被忽略的序列化陷阱

System.Text.JsonNewtonsoft.Json 序列化快照时,90% 的问题出在类型丢失和构造器约束上:

  • 聚合状态类必须有 public parameterless constructor,否则反序列化失败(即使你写了 [JsonConstructor],也要确保默认构造器存在)
  • 避免使用 record 类型直接作为快照 State —— 它的不可变性和私有字段会让 JSON 序列化器无法写入,除非显式配置 IncludeFields = truePropertyNamingPolicy = null
  • 若状态中含 DateTimeOffsetdecimal 或枚举,确认序列化器未做格式转换(如把 decimal 转成 double 导致精度丢失)
  • 快照表的数据库字段类型必须匹配序列化结果长度:JSON 字段建议用 NVARCHAR(MAX)(SQL Server)或 JSONB(PostgreSQL),别用 VARCHAR(200) 截断

真正的难点不在“怎么存快照”,而在于“怎么保证快照和事件语义严格一致”——只要有一次 Apply() 方法修改了非状态字段(比如缓存计数器、临时标记),快照就会脱离事件源事实。

相关专题

更多
数据分析工具有哪些
数据分析工具有哪些

数据分析工具有Excel、SQL、Python、R、Tableau、Power BI、SAS、SPSS和MATLAB等。详细介绍:1、Excel,具有强大的计算和数据处理功能;2、SQL,可以进行数据查询、过滤、排序、聚合等操作;3、Python,拥有丰富的数据分析库;4、R,拥有丰富的统计分析库和图形库;5、Tableau,提供了直观易用的用户界面等等。

683

2023.10.12

SQL中distinct的用法
SQL中distinct的用法

SQL中distinct的语法是“SELECT DISTINCT column1, column2,...,FROM table_name;”。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

320

2023.10.27

SQL中months_between使用方法
SQL中months_between使用方法

在SQL中,MONTHS_BETWEEN 是一个常见的函数,用于计算两个日期之间的月份差。想了解更多SQL的相关内容,可以阅读本专题下面的文章。

347

2024.02.23

SQL出现5120错误解决方法
SQL出现5120错误解决方法

SQL Server错误5120是由于没有足够的权限来访问或操作指定的数据库或文件引起的。想了解更多sql错误的相关内容,可以阅读本专题下面的文章。

1095

2024.03.06

sql procedure语法错误解决方法
sql procedure语法错误解决方法

sql procedure语法错误解决办法:1、仔细检查错误消息;2、检查语法规则;3、检查括号和引号;4、检查变量和参数;5、检查关键字和函数;6、逐步调试;7、参考文档和示例。想了解更多语法错误的相关内容,可以阅读本专题下面的文章。

357

2024.03.06

oracle数据库运行sql方法
oracle数据库运行sql方法

运行sql步骤包括:打开sql plus工具并连接到数据库。在提示符下输入sql语句。按enter键运行该语句。查看结果,错误消息或退出sql plus。想了解更多oracle数据库的相关内容,可以阅读本专题下面的文章。

676

2024.04.07

sql中where的含义
sql中where的含义

sql中where子句用于从表中过滤数据,它基于指定条件选择特定的行。想了解更多where的相关内容,可以阅读本专题下面的文章。

575

2024.04.29

sql中删除表的语句是什么
sql中删除表的语句是什么

sql中用于删除表的语句是drop table。语法为drop table table_name;该语句将永久删除指定表的表和数据。想了解更多sql的相关内容,可以阅读本专题下面的文章。

417

2024.04.29

PS使用蒙版相关教程
PS使用蒙版相关教程

本专题整合了ps使用蒙版相关教程,阅读专题下面的文章了解更多详细内容。

23

2026.01.19

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 8.4万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 3.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号