亞毫秒級訂單撮合解析
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,如果您發現迴歸 — 工單將直接進入該團隊的看板。