共计 1940 个字符,预计需要花费 5 分钟才能阅读完成。
背景痛点
在传统 CRUD 模式下,Skill 的状态管理常常面临以下问题:

- 数据竞争:高并发场景下多个操作同时修改同一 Skill 状态,导致最终结果不符合预期
- 版本冲突:乐观锁机制虽然能解决部分问题,但在分布式系统中版本号同步本身可能成为瓶颈
- 历史追溯困难:传统方案只保存当前状态,无法还原任意时间点的完整业务上下文
技术选型
在解决状态同步问题时,我们主要对比了三种方案:
- 状态快照:定期保存完整状态,恢复快但存储开销大
- Saga 模式:适合长事务但无法解决查询时的状态一致性问题
- 事件溯源(Event Sourcing):通过持久化事件流实现状态重建,天然支持历史追溯
选择事件溯源的核心依据:
- 业务需要完整的操作审计追踪
- Skill 状态变更路径比最终状态更重要
- 需要支持时间旅行调试(Time Travel Debugging)
实现细节
DDD 事件建模
以用户技能升级为例,定义聚合根和事件:
@Aggregate
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class UserSkill {
@AggregateIdentifier
private String userId;
private int level;
private LocalDateTime lastUpgradedAt;
@CommandHandler
public UserSkill(UpgradeSkillCommand command) {apply(new SkillUpgradedEvent(command.getUserId(),
command.getSkillId(), 1));
}
@EventSourcingHandler
public void on(SkillUpgradedEvent event) {this.userId = event.getUserId();
this.level += event.getIncrement();
this.lastUpgradedAt = LocalDateTime.now();}
}
事件存储实现
Spring Data 持久化配置(使用 MongoDB 作为事件存储):
@Repository
public interface EventStoreRepository extends MongoRepository<DomainEvent, String> {@Lock(LockModeType.OPTIMISTIC)
List<DomainEvent> findByAggregateIdOrderByVersionAsc(String aggregateId);
}
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "event_type")
public abstract class DomainEvent {
@Id private String eventId;
private String aggregateId;
private long version;
private Instant timestamp;
// getters/setters...
}
快照策略
快照间隔计算公式:
$$
N = \sqrt{M}
$$
其中:
– $N$:快照间隔(事件数量)
– $M$:预估事件总量
实际实现时建议动态调整,当事件回放时间超过阈值(如 200ms)时触发快照。
性能考量
并发控制方案
事件回放时采用多版本并发控制(MVCC):
- 为每个聚合根维护一个版本号
- 加载事件时检查版本连续性
- 保存事件时验证版本号一致性
查询优化
使用 CQRS 分离读写模型后,通过 Projection 提升查询性能:
| 方案 | QPS | 平均延迟 | 内存占用 |
|---|---|---|---|
| 直接回放 | 120 | 45ms | 低 |
| 内存投影 | 8500 | 2ms | 高 |
| 持久化投影 | 3200 | 5ms | 中 |
避坑指南
事件版本兼容
- Upcasters 模式:在事件加载时动态转换旧版本格式
- 双写迁移:同时处理新旧版本事件直到完全迁移
- 事件包装:将旧事件包装为新版本事件的属性
幂等性保障
分布式环境下建议:
- 每个事件包含唯一的 eventId
- 消费者维护已处理事件的 ID 集合
- 采用幂等写入(如 MongoDB 的 upsert)
思考题互动
如何设计跨 Skill 的事件因果关系追踪?
提示线索:
1. 考虑引入因果事件 ID(causeEventId)字段
2. 使用向量时钟 (Vector Clock) 技术
3. 参考 CRDT(Conflict-Free Replicated Data Types)的解决思路
总结
事件溯源为 Skill 系统带来的核心价值:
- 完整保存业务变更历史
- 天然避免数据竞争问题
- 支持复杂的业务状态重建
实际落地时需要注意事件版本管理和查询性能优化,建议从小型业务场景开始试点,逐步积累经验。
正文完
