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 核心逻辑流程图

下面这张图,把全量快照和增量快照的生成、存储、恢复流程串起来了。你看完应该能对整个机制有个直观的认识。

订单簿快照机制核心流程 订单簿数据源 生成策略 全量快照 增量快照 Protobuf/二进制 Protobuf/二进制 快照恢复引擎 完整订单簿

从图上你能看到:数据源进来后,先经过生成策略的判断。是走全量还是走增量,取决于你的策略配置。然后各自按对应的格式存储。恢复时,引擎会先加载最近的全量快照,再回放增量,最终还原出完整的订单簿。

嗯,这就是快照机制的核心。说白了就是——用空间换时间,还是用时间换空间,你得根据业务场景来权衡。没有银弹,只有最适合你的方案。


公众号:蓝海资料掘金营,微信deep3321