2、快照机制原理:全量快照 vs 增量快照、快照生成策略、快照存储格式
聊到订单簿的快照机制,我得先坦白一件事——快照这东西,看着简单,坑是真不少。我刚入行那会儿,觉得快照不就是把订单簿拍个照存起来嘛,有啥难的?结果第一次上线就被打脸了。嗯,今天咱们就把这块掰开揉碎了讲清楚。
2.1 全量快照 vs 增量快照
先说说最基础的概念。快照分两种:全量和增量。说白了就是——你是每次都重新拍一张完整的照片,还是只记录变化的部分。
全量快照
全量快照,就是把当前订单簿的所有挂单数据完整地存一份。比如某个时刻,买一价、买一量、买二价、买二量……一直到买N,卖侧同理,全部记录下来。
优点:恢复速度快。拿到一个全量快照,直接加载就能用,不需要任何额外计算。
缺点:存储开销大。尤其是深度大的市场,一个快照可能几百KB甚至几MB。如果每秒拍一次,一天下来数据量惊人。
我在项目中遇到过这样的情况:某个币种深度特别深,挂了上千档。全量快照一次就1.2MB,高频拍下来,磁盘直接报警。后来我们不得不调整策略——全量快照不能拍太勤。
增量快照
增量快照,只记录从上一次快照到当前时刻的变化部分。比如新增了哪些订单,撤销了哪些,价格变动了哪些。
优点:数据量小。通常只有全量快照的十分之一甚至更少。
缺点:恢复时需要「回放」。你得先拿到一个全量快照,然后一条一条应用增量,才能还原出完整的订单簿。
你想想看,如果系统崩溃了,你手里只有一堆增量日志,但没有最近的全量快照——那恢复起来可就痛苦了。我曾经吃过这个亏,后面才明白:增量快照不能单独用,必须搭配全量快照。
| 对比维度 | 全量快照 | 增量快照 |
|---|---|---|
| 存储大小 | 大(完整订单簿) | 小(仅变化部分) |
| 恢复速度 | 快(直接加载) | 慢(需回放) |
| 生成开销 | 高(遍历所有挂单) | 低(仅记录变化) |
| 适用场景 | 低频快照、系统启动 | 高频快照、实时同步 |
2.2 快照生成策略
讲完了两种快照的区别,咱们聊聊实际中怎么用。我个人习惯的做法是——全量+增量混合策略。
策略一:定时全量 + 实时增量
这是最常用的方案。比如:
- 每5分钟生成一次全量快照
- 每次订单簿变化时,实时记录增量快照
- 恢复时:加载最近的全量快照,然后回放之后的增量
这样做的好处很明显:全量快照控制存储成本,增量快照保证数据不丢。我建议全量快照的间隔不要超过10分钟,否则增量日志太多,恢复时回放会很慢。
小技巧:全量快照的生成时机,最好选在交易相对冷清的时候。比如凌晨3-4点,或者市场波动小的时候。这样可以减少快照生成对系统性能的影响。
策略二:基于水位线的触发式快照
另一种思路是——不按时间,按增量日志的大小来触发全量快照。比如:
- 设置一个阈值,比如增量日志达到100MB
- 一旦超过阈值,自动生成一次全量快照
- 然后清空增量日志
这样做的好处是:恢复时间可控。不管市场多活跃,增量日志都不会太大,恢复时回放的时间基本固定。
我曾经在一个高频交易系统里用过这个策略。当时市场波动特别大,每秒都有几百笔成交。如果用定时策略,5分钟的全量间隔根本扛不住,增量日志涨得飞快。换成水位线策略后,问题就解决了。
策略三:快照链式存储
这个稍微高级一点。不是只保留一个全量快照,而是保留多个历史快照,形成一个链条。比如:
- 快照A(时间T1)
- 增量A→B
- 快照B(时间T2)
- 增量B→C
- 快照C(时间T3)
恢复时,你可以选择任意一个快照作为起点。这样做的好处是:容错性强。如果某个快照损坏了,还可以用前一个快照加上增量来恢复。
注意:链式存储会占用更多磁盘空间。建议只保留最近3-5个全量快照,更早的可以删除或归档。
2.3 快照存储格式
格式这块,我踩过的坑最多。选错了格式,后面读写性能、存储成本都会出问题。
格式一:JSON
最直观的格式。可读性好,调试方便。但——性能差,体积大。
{
"timestamp": 1700000000000,
"bids": [
{"price": 100.5, "size": 10.0},
{"price": 100.4, "size": 5.0}
],
"asks": [
{"price": 100.6, "size": 8.0},
{"price": 100.7, "size": 3.0}
]
}
我建议:只在开发和调试阶段用JSON。生产环境千万别用,否则你会被序列化/反序列化的性能坑死。
格式二:Protocol Buffers(Protobuf)
这是我在生产环境最常用的格式。体积小、解析快、跨语言支持好。
message OrderBookSnapshot {
int64 timestamp = 1;
repeated Level bids = 2;
repeated Level asks = 3;
}
message Level {
double price = 1;
double size = 2;
}
用Protobuf,同样的数据量,体积只有JSON的1/3到1/5。解析速度更是快一个数量级。我在一个项目中,把快照存储从JSON换成Protobuf后,磁盘IO下降了70%。
格式三:自定义二进制格式
如果对性能有极致要求,可以自己定义二进制格式。比如:
- 前8字节:时间戳
- 接下来4字节:买盘档位数
- 然后每档:8字节价格 + 8字节数量
- 卖盘同理
这样做的好处是:解析几乎零开销。直接内存映射(mmap)就能读。但缺点也很明显——可读性差,扩展困难。一旦格式定了,后面想加字段就麻烦了。
我的建议:除非你是做极低延迟的交易系统(纳秒级),否则用Protobuf就够了。自定义格式的维护成本太高,不划算。
2.4 核心逻辑流程图
下面这张图,把全量快照和增量快照的生成、存储、恢复流程串起来了。你看完应该能对整个机制有个直观的认识。
从图上你能看到:数据源进来后,先经过生成策略的判断。是走全量还是走增量,取决于你的策略配置。然后各自按对应的格式存储。恢复时,引擎会先加载最近的全量快照,再回放增量,最终还原出完整的订单簿。
嗯,这就是快照机制的核心。说白了就是——用空间换时间,还是用时间换空间,你得根据业务场景来权衡。没有银弹,只有最适合你的方案。
公众号:蓝海资料掘金营,微信deep3321