快照
快照是事件溯源架构中的重要优化机制,通过保存聚合根状态检查点来提升性能,减少事件重放次数。
快照机制
在事件溯源中,聚合根的状态通过重放所有历史事件来重建。随着事件数量的增加,重放所有事件变得越来越慢。快照机制通过定期保存聚合根的当前状态来解决此问题。
kotlin
interface Snapshot<S : Any> : ReadOnlyStateAggregate<S>, SnapshotTimeCapable
data class SimpleSnapshot<S : Any>(
override val delegate: ReadOnlyStateAggregate<S>,
override val snapshotTime: Long = System.currentTimeMillis()
) : Snapshot<S>快照加载流程
加载聚合时,首先查询快照存储。如果存在快照,则只需重放快照版本之后的事件。
快照策略
快照策略决定何时创建快照。Wow 框架提供多种内置策略:
版本偏移策略 (VersionOffset)
当聚合根版本与上次快照版本的差值达到指定阈值时创建快照。
kotlin
class VersionOffsetSnapshotStrategy(
private val snapshotRepository: SnapshotRepository,
private val versionOffset: Int = 5
) : SnapshotStrategy全量策略 (All)
为每个状态事件创建快照。
kotlin
class SimpleSnapshotStrategy(
private val snapshotRepository: SnapshotRepository
) : SnapshotStrategy无操作策略 (NoOp)
不创建任何快照。
kotlin
object NoOp : SnapshotStrategy {
override fun <S : Any> shouldSnapshot(stateEvent: StateEvent<S>): Boolean = false
}快照生命周期
快照仓库
快照仓库负责存储和检索快照。
kotlin
interface SnapshotRepository : Named, AggregateIdScanner {
fun <S : Any> load(aggregateId: AggregateId): Mono<Snapshot<S>>
fun <S : Any> save(snapshot: Snapshot<S>): Mono<Void>
fun getVersion(aggregateId: AggregateId): Mono<Int>
}内存实现
kotlin
class InMemorySnapshotRepository : SnapshotRepository {
private val aggregateIdMapSnapshot = ConcurrentHashMap<AggregateId, String>()
override fun <S : Any> load(aggregateId: AggregateId): Mono<Snapshot<S>> =
Mono.fromCallable {
aggregateIdMapSnapshot[aggregateId]?.toObject<Snapshot<S>>()
}
override fun <S : Any> save(snapshot: Snapshot<S>): Mono<Void> =
Mono.fromRunnable {
aggregateIdMapSnapshot[snapshot.aggregateId] = snapshot.toJsonString()
}
}支持的后端
| 后端 | 模块 | 状态 |
|---|---|---|
| MongoDB | wow-mongo | 生产就绪 |
| Redis | wow-redis | 生产就绪 |
| R2DBC | wow-r2dbc | 生产就绪 |
快照处理流程
- 状态事件发布:当聚合根状态变化时,发布状态事件
- 策略评估:快照策略评估是否需要创建快照
- 快照创建:如需要,创建当前状态的快照
- 快照存储:将快照保存到快照仓库
配置
yaml
wow:
eventsourcing:
snapshot:
enabled: true # 是否启用快照
strategy: all # 快照策略 (all, version_offset)
storage: mongo # 快照存储 (mongo, redis, r2dbc, elasticsearch, in_memory, delay)
version-offset: 5 # 版本偏移(仅对 version_offset 策略有效)| 属性 | 默认值 | 描述 |
|---|---|---|
wow.snapshot.enabled | false | 启用快照存储 |
wow.snapshot.interval | 100 | 创建新快照前的事件数 |
wow.snapshot.store.type | 事件存储后端 | 快照存储后端 |
聚合加载优化
快照极大地优化了聚合根的加载性能:
kotlin
class EventSourcingOrderRepository(
private val eventStore: EventStore,
private val snapshotRepository: SnapshotRepository
) : OrderRepository {
override fun load(orderId: String): Mono<OrderState> {
val aggregateId = AggregateId("order", orderId)
return snapshotRepository.load<OrderState>(aggregateId)
.flatMap { snapshot ->
// 只重放快照版本之后的事件
eventStore.load(aggregateId, snapshot.version + 1)
.collectList()
.map { eventStreams ->
val state = snapshot.state
eventStreams.forEach { stream ->
stream.events.forEach { event ->
state.apply(event)
}
}
state
}
}
.switchIfEmpty(
// 无快照,加载所有事件
eventStore.load(aggregateId)
.collectList()
.map { eventStreams ->
val state = OrderState(orderId)
eventStreams.forEach { stream ->
stream.events.forEach { event ->
state.apply(event)
}
}
state
}
)
}
}性能影响
- 启用快照:聚合加载时间与快照间隔成正比,而非总事件数
- 禁用快照:每次加载都需要重放所有历史事件
- 存储成本:需要额外的存储空间来保存快照数据
当快照间隔为 50 时,拥有 1000 个事件的聚合最多重放 49 个事件,而非全部 1000 个 -- 减少约 95%。
最佳实践
- 选择合适的快照策略:根据业务场景选择合适的快照频率
- 监控快照效果:定期检查快照是否显著改善了加载性能
- 快照清理:定期清理过期的快照以节省存储空间
- 快照一致性:确保快照版本与事件流的一致性