亚毫秒级订单撮合解析
1. 起点
我们在 2022 年上架的撮合引擎是教科书式實现:用单線程事件循环對以嵌套 B-tree map 表示的价格-時间优先订单簿處理订单。从订单接收到执行报告的中位時延约為 4.2 ms,p99 约 11 ms。對零售流量来说还行,對任何試图做市的人来说则相当尴尬。
2. 時延预算
撮合本身很便宜 — 對热订单簿的典型订单只需 50–200 纳秒。剩下的 4 ms 分布在:内核網絡栈、JSON 解析、校验、账目写入、複制以及报告。我们在负载下測量了每一段,发现最昂贵的部分是校验(~1.4 ms,30 多次白名单查询)和账目写入(~1.7 ms,需写入 Postgres)。
3. 我们做了哪些改变
校验改為一次写入快照
白名单、费率层级和按帳戶的限额过去是针對 Postgres 的緩存未命中。我们在每次变更時将它们快照到带 16 字节版本头的内存映射页中。撮合循环以无锁方式读取;更新原子地切换快照。校验時延降至约 80 µs。
账目改為日志方式
我们不在热路径上将成交直接写入 Postgres,而是把事件追加到按分片的环形缓冲并 ack 订单。一个獨立的日志器在 5 ms 内做持久化 — 足以满足我们的一致性故事,而不会阻塞撮合。热路径上的账目時延降至约 30 µs。
網絡栈:io_uring + 零拷贝
我们将订单入口从基于 epoll 的阻塞 I/O 迁移到带批量提交的 io_uring — 仅此一项就削减了约 400 µs 的系统调用开销。在網絡入口侧,XDP 程序将热門交易對的數據包直接導入撮合引擎队列,无需穿越内核網絡栈的其余部分,再节省约 300 µs。合计:往返時延减少约 700 µs。
4. 我们没有改变的部分
订单簿數據結構保持不变。价格-時间优先语義保持不变。执行报告格式保持不变。所有变更都发生在边缘 — 校验快照、异步日志器、網絡栈。實際撮合代碼没有任何变动。这让我们能够按分片逐步推出变更,并进行 bit-for-bit 的回放比較。
5. Results
- 中位時延: 4.2 ms → 580 µs (7×)
- p99 latency: 11 ms → 1.4 ms (8×)
- 每分片吞吐: 18,000 ops/s → 92,000 ops/s
- 账目持久性:同步 → 5 毫秒内异步(我们明确公開 SLA)
做市商在一周内注意到了变化。随着库存周转率跟上新的成交率,主流币種的價差收窄了 6–18 个基点。
6. 我们接下来要做的事
下一个瓶颈是複制 — 我们在三个区域间运行两阶段提交,跨区域确认现在是系统中最长的一跳。我们正在原型一个 Raft 变体,使撮合分片可在单区域多数派下完成订单,并异步複制,同時對故障切换時保留的内容设置明确的不变式。上架后会有跟进文章。
bitexasia 的技术文章由實際交付变更的工程师撰写。相关文章: 近期一份提现通道的事故複盘 — 同样以精确為先,只是應用于故障模式而非優化。请将您的 API 工单 with engine 標记 engine,如果您发现回归 — 工单将直接進入该团队的看板。