撮合引擎:交易所是怎么把买单卖单凑成一笔交易的 书架

第 11 章 / 共 12 章

第十一章 · 做快与做对:定序、重放与崩溃恢复

快和对,是同一个答案

上一章的五十行伪代码能成交,但离"能上线"还差三件事:它得扛住断电,它得快,它得能被证明是对的。这三件事看起来是三座山,其实山脚连着同一个决定——而这一章就是要让你相信这个决定:单线程定序 + 一切皆可重放。

为什么撮合引擎几乎都是单线程

直觉说多线程快。但撮合引擎的命门不是算力,是顺序:同一张簿子上,"先撤单后成交"和"先成交后撤单"是两个世界。第六章FOK的干跑、止损单的触发链,全都建立在"事件一件一件来,前一件彻底处理完才看下一件"之上。多线程的第一件事就是破坏这个保证,然后你得用锁把顺序再抢回来——抢回来的过程比省下的还贵,还引入死锁和竞态这些新的死法。

所以业界的主流架构是把系统切成三段流水线:

大白话:撮合引擎像一个只开一扇门的银行柜台——门口保安(网关)给所有人发号,柜台里一个人(内核)按号办事,后面一群人(外围)负责复印单据寄出去。多开几个柜台不会更快,只会让账对不上。

事件溯源:让断电变得不可怕

单线程内核是纯内存的,快,但断电就失忆。怎么办?答案叫事件溯源(event sourcing):在进入内核之前,把每个输入事件先追加写到磁盘日志里——"10:00:01 收到D的限价买单1200股"、"10:00:02 收到撤单X"……

现在关键的魔法出现了:撮合内核是确定性的——同样的输入序列,必然产生同样的输出和同样的簿子状态(这就是为什么不能用随机数、不能依赖系统时钟做撮合决策)。所以恢复的时候根本不需要恢复"簿子",只需要重放日志:从空簿子开始,把今天的事件按序号重新喂一遍,内核一单一单重新撮合,最终状态分毫不差。日志就是系统,内存里的簿子只是日志的缓存。

重放一天的日志太慢?定期拍快照:把某时刻的簿子状态整个存下来,记下它对应日志的哪个序号。恢复 = 加载最近快照 + 重放之后的日志。这套"日志+快照"正是第九章行情修复机制的同款——你看,同一个思想贯穿了整个系统。

高可用:主备两台机器,同一份日志

单机再稳也会坏。标准做法是主备:两台一模一样的内核,消费同一份定序日志。主机活着,备机跟着重放保持热同步;主机死了,备机接管——它的簿子和主机死前一字不差,因为它算过同样的题。切换时对外几乎无感。更讲究的交易所上三台做多数派,那已经进入分布式共识的领域,原理相同。

测试:怎么证明一台金融机器是对的

确定性给了我们一件别的系统梦寐以求的武器:可复现的测试。四层,一层都不能少:

  1. 规则单测:本书前五章的每个算例都该变成一个测试——限价单排队、市价单扫墙、部分成交保留队位、FOK干跑、STP三档、集合竞价最大成交量。这些是"法律条文"的判例集。
  2. 不变量检查:不管喂什么输入,某些事永远不许发生——簿子里买价不得 ≥ 卖价(交叉的簿子说明撮合漏了);任何订单剩余量不得为负;所有成交的成交量必须守恒(买方得的=卖方出的)。让引擎每处理完一个事件就自检一遍,违反就停机报警——宁可停,不可错。
  3. 随机对拍(fuzzing):写一个"笨但显然正确"的参考实现(用数组、暴力遍历,不管性能),再写一个随机订单生成器,两边喂同样的十万条随机事件,状态必须一模一样。你的快实现和笨实现任何一次分歧,都是一个真bug。
  4. 生产重放:上线后,把每天的生产日志拿到测试环境重放,结果和当天真实状态对账。这套设施反正为了崩溃恢复也得建,顺手就成了最强的回归测试。

走到这里,你的交易所内核已经能跑、能快、能恢复、能被证明正确。只剩最后一关:真实世界不总是按规则出牌——行情会疯,人会犯错,程序会有bug。最后一章,我们给这台机器装上护栏。

撮合引擎:交易所是怎么把买单卖单凑成一笔交易的 · 王建硕
费曼式写法 · 由多个 AI 代理撰写与互相审校