第四章:增量更新概念:增、删、改操作,事件驱动模型

好,咱们进入正题。上一章我们聊了快照的生成与存储,说白了就是给订单簿拍了一张「全家福」。但金融市场每秒都在变,你不可能每毫秒都拍一张新照片——那带宽和存储都扛不住。

所以就有了增量更新。我个人习惯把它叫做「打补丁」——只告诉系统:哪里变了,怎么变的。这样既省带宽,又省时间。

4.1 什么是增量更新?

增量更新的核心思想很简单:只传递变化的部分。你想想看,一个订单簿里可能有几万个价位,但真正在变化的,可能只有几个价位上的订单。

我在做高频交易系统时遇到过这种情况:交易所每秒推送10万笔订单,但其中95%的订单都是「已存在订单的微调」。如果每次都传全量快照,FPGA的DDR带宽早就被撑爆了。

增量更新通常包含三种操作:

  • 新增(Add):在某个价位上插入一个新订单
  • 删除(Delete):移除一个已存在的订单
  • 修改(Modify):改变某个订单的数量或价格

嗯,这里要注意:修改操作其实可以拆成「删除+新增」。但为什么还要单独保留修改?因为有些场景下,修改只涉及数量变化,价格不变。如果拆成两步,FPGA内部的状态机就要多跑一轮,延迟就上去了。

核心原则:增量更新的目标是「最小化传输数据量」和「最小化处理延迟」。两者往往需要权衡。

4.2 增、删、改的具体实现

咱们用FPGA的视角来看这三种操作。说白了,就是三个状态机的事。

4.2.1 新增操作

新增操作是最简单的。FPGA收到一个「新增订单」的消息,里面包含:

  • 订单ID(唯一标识)
  • 价格
  • 数量
  • 买卖方向

FPGA要做的事:在对应的价格链表里插入一个新节点。我一般用双链表结构,这样插入和删除都很快。

// 伪代码:新增订单
if (price_level_exists) {
    insert_node_into_list(order_id, price, quantity);
} else {
    create_new_price_level(price);
    insert_node_into_list(order_id, price, quantity);
}

我在项目中踩过一个坑:新增操作如果遇到价格不存在的情况,需要先创建价格层级。这个创建过程会占用一个时钟周期,如果处理不好,后面的流水线就会断掉。我的解决办法是:预分配一个空闲的价格层级池,需要时直接取用。

4.2.2 删除操作

删除操作更直接。FPGA收到一个「删除订单ID」的消息,然后:

  1. 根据订单ID找到对应的节点
  2. 从链表中摘除该节点
  3. 回收节点内存

这里有个细节:如果删除的是某个价格层级上的最后一个订单,这个价格层级也要被删除。否则空的价格层级会越积越多,浪费BRAM资源。

我曾经犯过的错:删除操作没有及时回收空的价格层级,导致FPGA内部的BRAM被占满,系统直接死机。后来我加了一个「空层级回收状态机」,每删除一个订单就检查一次。

4.2.3 修改操作

修改操作稍微复杂一点。它有两种情况:

修改类型 处理方式 延迟
仅修改数量 直接更新节点中的数量字段 1个时钟周期
修改价格 先删除原节点,再新增到新价格层级 2-3个时钟周期

你可能会问:为什么价格修改要拆成两步?因为价格变了,订单在链表中的位置就变了。FPGA不像CPU有复杂的指针操作,它只能老老实实地先删后加。

我个人习惯在修改操作中加一个「价格是否变化」的预判断。如果价格没变,直接走快速路径;如果价格变了,再走慢速路径。这样能省下不少延迟。

4.3 事件驱动模型

增量更新的本质是事件驱动。什么意思?就是FPGA不主动去「轮询」订单簿有没有变化,而是等外部告诉它:「嘿,有个新订单来了」。

事件驱动模型在FPGA里通常用消息队列来实现。我画了一张图,你看看就明白了:

事件驱动模型架构图 交易所 事件源 事件消息队列 FIFO / 环形缓冲区 事件处理器 状态机 + 仲裁器 订单簿存储 BRAM / DDR 事件类型 • 新增事件 (ADD) • 删除事件 (DEL) • 修改事件 (MOD) • 快照请求 (SNAP) • 心跳事件 (HB)

这张图展示了事件驱动模型的核心流程:

  1. 事件源(交易所)产生各种事件
  2. 消息队列(FIFO或环形缓冲区)暂存事件,防止丢失
  3. 事件处理器(状态机+仲裁器)从队列中取出事件,解析类型
  4. 订单簿存储根据事件类型执行对应的增、删、改操作

我的经验之谈:消息队列的深度一定要留够余量。我曾经因为队列深度不够,在高频行情爆发时丢了几笔增量更新,导致订单簿状态和交易所不一致。后来我改成双缓冲队列,一个队列在写,另一个队列在读,彻底解决了丢数据的问题。

4.4 事件优先级与仲裁

事件驱动模型里有个容易被忽略的问题:事件优先级。比如,一个「删除」事件和一个「修改」事件同时到达,先处理哪个?

我一般用时间戳仲裁:谁的时间戳小,谁先处理。但这里有个坑:网络传输延迟会导致时间戳乱序。比如A事件先发出,但B事件后发出却先到达。

解决办法有两种:

  • 硬件时间戳:在FPGA入口处打上本地时间戳,保证顺序
  • 序列号机制:交易所给每个事件分配递增的序列号,FPGA按序列号处理

我个人更推荐序列号机制。因为硬件时间戳需要高精度时钟同步,而序列号是交易所保证的,FPGA只管按顺序处理就行。

4.5 增量更新的性能指标

最后,咱们聊聊怎么衡量增量更新的好坏。我常用的指标有三个:

指标 定义 典型值
吞吐量 每秒能处理的事件数 1000万+ 事件/秒
延迟 从事件到达FPGA到更新完成的时间 < 100 纳秒
丢事件率 因队列溢出等原因丢失的事件比例 0%(必须保证)

嗯,这里要特别强调:丢事件率必须是0%。因为一旦丢了一个增量更新,订单簿的状态就永远对不上了。我见过有人用「丢事件后重新请求快照」的方案,但那个恢复时间太长了,在高频交易里根本不可接受。

好了,增量更新的概念就讲到这里。说白了就是三件事:增、删、改,再加上一个事件驱动的框架。下一章咱们会深入FPGA的具体实现,看看这些操作在硬件里是怎么跑起来的。

本章核心要点

  • 增量更新只传递变化部分,节省带宽和存储
  • 三种操作:新增、删除、修改(修改可拆为删+加)
  • 事件驱动模型:消息队列 + 状态机 + 仲裁器
  • 事件优先级处理:推荐序列号机制
  • 性能指标:吞吐量、延迟、丢事件率(必须0%)

专注资料整理