第四章:增量更新概念:增、删、改操作,事件驱动模型
好,咱们进入正题。上一章我们聊了快照的生成与存储,说白了就是给订单簿拍了一张「全家福」。但金融市场每秒都在变,你不可能每毫秒都拍一张新照片——那带宽和存储都扛不住。
所以就有了增量更新。我个人习惯把它叫做「打补丁」——只告诉系统:哪里变了,怎么变的。这样既省带宽,又省时间。
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」的消息,然后:
- 根据订单ID找到对应的节点
- 从链表中摘除该节点
- 回收节点内存
这里有个细节:如果删除的是某个价格层级上的最后一个订单,这个价格层级也要被删除。否则空的价格层级会越积越多,浪费BRAM资源。
我曾经犯过的错:删除操作没有及时回收空的价格层级,导致FPGA内部的BRAM被占满,系统直接死机。后来我加了一个「空层级回收状态机」,每删除一个订单就检查一次。
4.2.3 修改操作
修改操作稍微复杂一点。它有两种情况:
| 修改类型 | 处理方式 | 延迟 |
|---|---|---|
| 仅修改数量 | 直接更新节点中的数量字段 | 1个时钟周期 |
| 修改价格 | 先删除原节点,再新增到新价格层级 | 2-3个时钟周期 |
你可能会问:为什么价格修改要拆成两步?因为价格变了,订单在链表中的位置就变了。FPGA不像CPU有复杂的指针操作,它只能老老实实地先删后加。
我个人习惯在修改操作中加一个「价格是否变化」的预判断。如果价格没变,直接走快速路径;如果价格变了,再走慢速路径。这样能省下不少延迟。
4.3 事件驱动模型
增量更新的本质是事件驱动。什么意思?就是FPGA不主动去「轮询」订单簿有没有变化,而是等外部告诉它:「嘿,有个新订单来了」。
事件驱动模型在FPGA里通常用消息队列来实现。我画了一张图,你看看就明白了:
这张图展示了事件驱动模型的核心流程:
- 事件源(交易所)产生各种事件
- 消息队列(FIFO或环形缓冲区)暂存事件,防止丢失
- 事件处理器(状态机+仲裁器)从队列中取出事件,解析类型
- 订单簿存储根据事件类型执行对应的增、删、改操作
我的经验之谈:消息队列的深度一定要留够余量。我曾经因为队列深度不够,在高频行情爆发时丢了几笔增量更新,导致订单簿状态和交易所不一致。后来我改成双缓冲队列,一个队列在写,另一个队列在读,彻底解决了丢数据的问题。
4.4 事件优先级与仲裁
事件驱动模型里有个容易被忽略的问题:事件优先级。比如,一个「删除」事件和一个「修改」事件同时到达,先处理哪个?
我一般用时间戳仲裁:谁的时间戳小,谁先处理。但这里有个坑:网络传输延迟会导致时间戳乱序。比如A事件先发出,但B事件后发出却先到达。
解决办法有两种:
- 硬件时间戳:在FPGA入口处打上本地时间戳,保证顺序
- 序列号机制:交易所给每个事件分配递增的序列号,FPGA按序列号处理
我个人更推荐序列号机制。因为硬件时间戳需要高精度时钟同步,而序列号是交易所保证的,FPGA只管按顺序处理就行。
4.5 增量更新的性能指标
最后,咱们聊聊怎么衡量增量更新的好坏。我常用的指标有三个:
| 指标 | 定义 | 典型值 |
|---|---|---|
| 吞吐量 | 每秒能处理的事件数 | 1000万+ 事件/秒 |
| 延迟 | 从事件到达FPGA到更新完成的时间 | < 100 纳秒 |
| 丢事件率 | 因队列溢出等原因丢失的事件比例 | 0%(必须保证) |
嗯,这里要特别强调:丢事件率必须是0%。因为一旦丢了一个增量更新,订单簿的状态就永远对不上了。我见过有人用「丢事件后重新请求快照」的方案,但那个恢复时间太长了,在高频交易里根本不可接受。
好了,增量更新的概念就讲到这里。说白了就是三件事:增、删、改,再加上一个事件驱动的框架。下一章咱们会深入FPGA的具体实现,看看这些操作在硬件里是怎么跑起来的。
本章核心要点:
- 增量更新只传递变化部分,节省带宽和存储
- 三种操作:新增、删除、修改(修改可拆为删+加)
- 事件驱动模型:消息队列 + 状态机 + 仲裁器
- 事件优先级处理:推荐序列号机制
- 性能指标:吞吐量、延迟、丢事件率(必须0%)