数据持久化策略:全量快照 vs 增量日志,如何选择
好,咱们来聊聊这个核心问题。订单簿的数据恢复,说白了就两个路子:要么定期拍一张「全家福」,要么把每笔操作都记下来。我做了这么多年交易系统,这两种方案都踩过坑,今天把经验摊开来讲。
先搞清楚:我们到底在恢复什么?
订单簿的数据,本质上是一个内存中的状态机。每一笔订单的进入、撮合、撤单,都在改变这个状态。系统崩溃后,我们要重建的,就是崩溃前最后一刻的那个状态。
你想想看,如果每秒有10万笔订单进来,内存里维护着几十万个限价单。突然断电了,怎么办?
嗯,这就是持久化要解决的问题。
方案一:全量快照
全量快照,就是每隔一段时间,把整个订单簿的状态完整地写下来。比如每5分钟、每10分钟,把当前所有的订单数据序列化到磁盘。
优点很明显:
- 恢复速度极快。加载一个文件,内存里就重建好了
- 逻辑简单,不容易出错
- 没有依赖关系,随便哪个快照都能独立使用
缺点也扎心:
- 数据量大。一个订单簿快照可能几十MB甚至上百MB
- 写入频繁时,磁盘I/O扛不住
- 快照间隔内发生崩溃,会丢失这段时间的数据
我记得有一次,在某个交易所做优化。他们每1分钟打一次快照,结果磁盘直接被打满了。后来改成5分钟一次,配合增量日志,才把问题解决。
方案二:增量日志
增量日志,就是只记录「变化」。每来一笔订单,就写一条日志:ADD order_id=12345, price=100.5, qty=100。每撮合一笔,也写一条:TRADE order_id=12345, filled_qty=50。
优点:
- 写入量小。每条日志可能就几十字节
- 可以做到几乎不丢数据(配合WAL机制)
- 方便审计和回放
缺点:
- 恢复慢。如果日志积累了几小时甚至几天,回放时间会很长
- 日志文件管理复杂,需要做日志滚动和清理
- 如果日志本身损坏,恢复就麻烦了
我曾经遇到过一个极端案例:某系统连续运行了72小时没打快照,日志文件堆了200GB。结果系统崩溃后,光回放日志就花了40分钟。你说这能忍吗?
那到底怎么选?
别急,我画了一张图,帮你理清思路。
我的建议:别二选一,混合才是王道
说实话,在真正的生产环境里,很少有人只用一种方案。我参与过的几个大型交易系统,无一例外都是全量快照 + 增量日志的混合模式。
具体怎么做?
- 定期打快照:比如每5分钟或每10分钟,把订单簿状态完整写下来
- 持续写增量日志:快照之间的所有操作,都记录到日志里
- 恢复时先加载快照,再回放日志:这样既快又不丢数据
代码示例:快照与日志的混合实现
下面是一个简化版的实现思路,用伪代码展示核心逻辑:
// 快照生成
void take_snapshot(OrderBook* book) {
// 1. 暂停增量日志写入(短暂锁住)
lock(log_mutex);
// 2. 序列化当前订单簿
serialize(book, "snapshot_20240101_120000.dat");
// 3. 记录当前日志位置
uint64_t log_pos = get_log_position();
save_checkpoint(log_pos);
// 4. 释放锁,继续处理订单
unlock(log_mutex);
// 5. 清理旧的日志文件(保留快照之后的即可)
clean_old_logs(log_pos);
}
// 数据恢复
void recover(OrderBook* book) {
// 1. 加载最近一次快照
load_snapshot(book, "snapshot_20240101_120000.dat");
// 2. 获取快照时的日志位置
uint64_t checkpoint = load_checkpoint();
// 3. 从该位置开始回放日志
replay_logs(book, checkpoint);
// 4. 恢复完成,开始正常处理
printf("恢复完成,共回放 %d 条日志\n", log_count);
}
避坑指南
- 快照写入时没有加锁,导致快照和日志不一致。恢复出来的订单簿少了几个订单
- 日志文件没有做CRC校验,磁盘坏道导致日志损坏,恢复失败
- 快照频率太高,磁盘I/O成为瓶颈,影响了正常订单处理
嗯,这里要注意一点:快照的间隔不是越短越好。我一般建议根据订单量来动态调整。比如每秒10万笔订单时,5分钟一次快照就够了。如果每秒只有1000笔,那30分钟一次也行。
表格对比:一目了然
| 维度 | 全量快照 | 增量日志 | 混合方案 |
|---|---|---|---|
| 恢复速度 | 快(秒级) | 慢(分钟级) | 较快(秒级+少量回放) |
| 数据完整性 | 可能丢失快照间隔内的数据 | 几乎不丢数据 | 几乎不丢数据 |
| 磁盘占用 | 大 | 小 | 中等 |
| 实现复杂度 | 低 | 中 | 高 |
| 适用场景 | 低吞吐、可接受少量丢数据 | 高吞吐、不能丢数据 | 高吞吐、高可用要求 |
最后说两句
其实选择哪种策略,最终取决于你的业务容忍度。如果丢了1秒的订单数据,客户会骂娘吗?如果恢复需要10分钟,交易员能接受吗?
我个人习惯是:宁可多花点磁盘空间,也要保证恢复速度和数据完整性。毕竟在金融系统里,数据就是钱,时间就是命。
好了,关于全量快照和增量日志的选择,就聊到这里。记住,没有银弹,只有最适合你场景的方案。