← 返回文章列表
March 27, 2026
5 分鐘閱讀

ZigBolt:為什麼我們用 Zig 從零打造了自己的 Aeron,實現了每條訊息 20 納秒延遲

#zigbolt
#zig
#高頻交易
#低延遲
#訊息系統
#aeron
#ipc
#開源
🛰️
Part 3 of 4 · Collection
Low-Latency Trading Infrastructure

ZigBolt — 超低延遲訊息系統 無鎖環形緩衝區、零複製編解碼器、Raft 叢集——全部用純 Zig 編寫,全部開源。

如果你從事演算法交易或做市,就知道每一微秒的價值。多一次上下文切換,你的訂單就會晚到一步。JVM 的一次垃圾回收暫停,對面的做市商就已經更新了報價。在一個以納秒衡量利潤的世界裡,訊息傳遞基礎設施不是服務之間的無聊管道,而是核心競爭優勢。

我們用 Zig 語言從零構建了 ZigBolt——一套面向高頻交易的訊息系統。沒有 JVM,沒有垃圾回收器,沒有 Media Driver,沒有 XML 配置檔案。我們實現了 SPSC 環形緩衝區 20 納秒 p50 延遲,以及共享記憶體 IPC 30 納秒延遲。

這篇文章講述的是:為什麼需要做這件事,內部是如何實現的,以及為什麼選擇了 Zig。


概要

  • ZigBolt — 開源(MIT 協議)的純 Zig 高頻交易訊息系統
  • 20 納秒 p50(SPSC),30 納秒 p50(IPC)——優於 Aeron 公佈的效能指標
  • 零複製編解碼器耗時 0 納秒(編譯期程式碼生成,執行時僅為指標型別轉換)
  • 無 GC、無 JVM、無 Media Driver ——庫直接嵌入應用程式
  • Raft 叢集、歸檔、排序器 ——全部內建
  • FFI 繫結支援 Rust、Python、Go、TypeScript、C——用你習慣的語言即可

問題:Aeron 很好,但還不夠

Aeron 是 Real Logic 開發的產品,堪稱資本市場低延遲訊息傳遞的事實標準。數十家高頻交易公司在使用它,久經實戰檢驗,架構設計優秀。但 Aeron 有一個根本性問題,那就是 JVM。

JVM safepoints:隱形的敵人

即使你小心地把所有資料放到了堆外記憶體,即使你關閉了 GC 自適應調節並設定了 GuaranteedSafepointInterval=300000,JVM 仍然會不時地在 safepoint 處暫停所有執行緒。這不是 bug,而是架構設計:JVM 需要 safepoints 來執行去最佳化、偏向鎖、棧遍歷等操作。

實際表現是這樣的:你的執行緒以 p50 = 200 納秒的速度傳送訊息,突然 p99.9 飆升到 50 微秒。沒有任何明顯原因。因為 JVM 的某個執行緒認為該執行 safepoint 了。

Media Driver:多餘的一跳

Aeron 通過 Media Driver 工作——一個獨立的程序(或嵌入式 JVM),通過共享記憶體在 publisher 和 subscriber 之間路由訊息。這提供了漂亮的隔離性,但至少增加了一次額外的跳轉:

Aeron:    App → shm → Media Driver → shm → socket → NIC
ZigBolt:  App → ring buffer → io_uring → NIC

每多一跳,就多幾納秒延遲、多幾次快取未命中、多一份不可預測性。

SBE:額外的構建步驟

Simple Binary Encoding 是金融訊息的標準 FIX 編解碼器。在 Aeron 生態中,它是一個獨立的 Java 工具,從 XML 模式生成程式碼。獨立的依賴、獨立的構建步驟、獨立的問題集。


解決方案:ZigBolt

我們問了自己一個問題:如果把 Aeron 最好的設計理念——三重緩衝日誌、無鎖環形緩衝區、Raft 叢集——用一種這樣的語言重新實現會怎樣:

  1. 沒有執行時開銷(無 GC、無 safepoints)
  2. 能在編譯期生成程式碼(comptime)
  3. 與 C 庫(DPDK、io_uring)整合極其簡單
  4. 編譯出的二進位制檔案僅約 100 KB

這種語言就是 Zig


架構

┌─────────────────────────────────────────────────────────┐
│  Publisher/Subscriber API(类型化封装)                    │
├─────────────────────────────────────────────────────────┤
│  Transport Layer(通道工厂、生命周期管理)                  │
├─────────────────────────────────────────────────────────┤
│  IPC Channel(共享内存)     │ UDP Channel(网络)         │
├─────────────────────────────────────────────────────────┤
│  WireCodec(comptime 零拷贝)│ SBE Encoder/Decoder       │
├─────────────────────────────────────────────────────────┤
│  Ring Buffers (SPSC/MPSC) │ LogBuffer(三重缓冲)         │
├─────────────────────────────────────────────────────────┤
│  Archive(回放)│ Sequencer(全局有序)│ Raft(高可用)      │
└─────────────────────────────────────────────────────────┘

七層架構,每一層都可以獨立使用。只需要一個 SPSC 環形緩衝區在兩個程序間做 IPC?直接用。需要完整的 Raft 共識叢集加歸檔?也有。


基準測試:用資料說話

ZigBolt 基準測試結果

以下是在 1000 萬次迭代(Apple Silicon / macOS)下的實測結果:

SPSC 環形緩衝區

訊息大小 p50 p99 p99.9 吞吐量
8 位元組 20 納秒 30 納秒 120 納秒 42.8M msg/s
32 位元組 30 納秒 50 納秒 150 納秒 28.5M msg/s
64 位元組 50 納秒 60 納秒 320 納秒 17.6M msg/s
256 位元組 30 納秒 50 納秒 50 納秒 29.5M msg/s

IPC Channel(共享記憶體)

訊息大小 p50 p99 p99.9 吞吐量
64 位元組 30 納秒 40 納秒 40 納秒 35.7M msg/s
256 位元組 40 納秒 40 納秒 170 納秒 27.4M msg/s
1024 位元組 90 納秒 260 納秒 900 納秒 9.9M msg/s

LogBuffer(Aeron 風格三重緩衝)

訊息大小 p50 p99 p99.9 吞吐量
32 位元組 30 納秒 40 納秒 320 納秒 33.6M msg/s
64 位元組 30 納秒 30 納秒 160 納秒 38.0M msg/s
256 位元組 30 納秒 40 納秒 60 納秒 31.1M msg/s

WireCodec(comptime 零複製)

操作 延遲 吞吐量
編碼(32 位元組) 0 納秒 ∞(內聯 memcpy)
解碼(32 位元組) ~0.4 納秒 27 億 msg/s

沒錯,你沒看錯:編碼耗時為零納秒。因為 WireCodec(T) 在編譯期驗證結構體,並將 encode/decode 轉化為普通的 @memcpy 或指標型別轉換。執行時開銷 = 零。

作為對比:Aeron 公佈的 IPC RTT(往返延遲)約 250 納秒。我們的單程延遲為 30 納秒。即使按往返計算,我們也快了 4 倍。


內部原理

無鎖 SPSC:以簡潔為美

SPSC 環形緩衝區

單生產者單消費者環形緩衝區是最簡單、最快的無鎖資料結構。寫入端移動 head,讀取端移動 tail,無需 CAS——acquire/release 原子操作就夠了。

關鍵技巧是快取行填充。如果 headtail 位於同一快取行,那麼每次更新其中一個計數器都會使另一個核心的快取失效(偽共享)。解決方案:

// Head(写入位置)——独占一个缓存行
head: std.atomic.Value(usize) align(128) = .init(0),

// 128 字节填充——确保隔离
_pad0: [128 - @sizeOf(std.atomic.Value(usize))]u8 = .{0} ** ...,

// Tail(读取位置)——独占一个缓存行
tail: std.atomic.Value(usize) align(128) = .init(0),

128 位元組而非 64 位元組——因為在 Apple Silicon(及許多 ARM 架構)上,硬體預取器可能以快取行對的方式工作。我們選擇了更保守的方案。

WireCodec:用 comptime 取代程式碼生成

WireCodec — 編譯期編解碼器

在 Java/C++ 的世界裡,要使用二進位制編解碼器,你需要一個單獨的步驟:編寫模式 -> 執行程式碼生成器 -> 得到程式碼 -> 編譯。在 Zig 裡,這一切都在編譯期完成:

const TickMsg = packed struct {
    symbol_id: u32,
    price: i64,
    quantity: u32,
    side: u8,
    _reserved: [3]u8,
    timestamp: u64,
};

const Codec = WireCodec(TickMsg);

// 编码——只是 32 字节的 memcpy。内联为 1-2 条指令。
Codec.encode(&msg, buf[0..Codec.wire_size]);

// 解码——指针类型转换。零拷贝。
const tick = Codec.decode(buf[0..Codec.wire_size]);

Zig 編譯器在 comptime 階段檢查:

  • 結構體是 packed 的(沒有填充空洞)
  • 大小是 8 位元組的倍數(為 SIMD 對齊)
  • 所有欄位都是基本型別

如果有問題——編譯錯誤,而不是凌晨三點生產環境的執行時異常。

基於共享記憶體的 IPC

兩個程序對映 /dev/shm 中的同一個檔案。Publisher 寫入環形緩衝區,subscriber 讀取。熱路徑上沒有套接字、沒有系統呼叫:

// Publisher
const channel = try IpcChannel.create("/market-data", .{
    .term_length = 1024 * 1024, // 1 MB
});
channel.publish(&msg_bytes, msg_type_id);

// Subscriber(另一个进程)
const channel = try IpcChannel.open("/market-data", .{
    .term_length = 1024 * 1024,
});
const count = channel.poll(handler_fn, 10);

publish() 到 subscriber 中 handler_fn 被呼叫的完整路徑——64 位元組訊息僅需 30 納秒。

基於 NAK 的 UDP 可靠傳輸

對於網路傳輸,ZigBolt 使用接收端驅動的重傳機制。接收端通過點陣圖追蹤 sequence number 中的間隙,並向傳送端傳送 NAK(否定確認)。加上 AIMD 擁塞控制——類似 TCP 的慢啟動和擁塞避免——以防止網路過載。

Raft 叢集:當一致性不可妥協

對於不能丟失訊息的場景(例如撮合引擎),ZigBolt 內建了完整的 Raft 共識:

  • Leader 選舉,可配置超時時間(150-300 毫秒)
  • 日誌複製 ——leader 將每條訊息複製到 followers
  • 預寫式日誌(WAL),帶 CRC32 校驗和崩潰恢復
  • 快照 ——防止 WAL 無限增長

歸檔:錄製與回放

所有訊息可以錄製到磁碟上的分段歸檔中。之後可以按時間或 sequence number 從任意位置回放。內建 LZ4 風格壓縮,無外部依賴。稀疏索引用於在段內快速查詢。

全域性有序排序器

對於在多個交易場所做市的場景,所有事件必須有全域性順序。排序器接收 N 個輸入流併合併為一個,分配單調遞增的 sequence number。每個參與者看到的是同一個事件序列。


為什麼選 Zig 而不是 Rust/C/C++?

我們在四個候選語言中做了比較。以下是客觀的對比:

指標 Zig C/C++ Rust Java (Aeron)
GC / 執行時開銷 JVM safepoints, GC
Comptime 程式碼生成 原生支援 宏/模板 proc macros
C 互操作(DPDK, io_uring) 簡單的 @cImport 原生 FFI/bindgen JNI 開銷
SIMD @Vector,內建 Intrinsics packed_simd(不穩定) 向量化提示
交叉編譯 內建 CMake 地獄 cargo target 不適用
構建時間 秒級 分鐘級(C++) 分鐘級 秒級 + JVM 啟動
隱式控制流 異常、隱式型別轉換 unwrap 中的 panic 異常

Zig 提供了獨一無二的組合: C 級效能 + 開發安全性 + comptime 超程式設計(編解碼器、查詢表、協議狀態機——全在編譯期生成)+ 通過 @cImport 與 DPDK、liburing、ef_vi 輕鬆整合。

而且 Zig 二進位制檔案僅約 100 KB,相比 JVM 方案的 20+ MB。對於邊緣部署和容器化,這很重要。


多語言繫結:用你熟悉的語言

ZigBolt 編譯為帶 C-ABI 的共享庫,我們提供了五種語言的現成繫結:

TypeScript / Node.js

import { IpcChannel } from "@zigbolt/node";

const channel = IpcChannel.create({
  name: "/my-market-data",
  termLength: 1024 * 1024,
});

const msg = Buffer.from("BTC/USDT 42000.50", "utf-8");
channel.publish(msg, 1);

Rust

use zigbolt::IpcChannel;

let ch = IpcChannel::create("/my-channel", 64 * 1024).unwrap();
ch.publish(b"hello", 1).unwrap();

// Subscriber
let sub = IpcChannel::open("/my-channel", 64 * 1024).unwrap();
sub.poll(|data, msg_type_id| {
    println!("got {} bytes, type={}", data.len(), msg_type_id);
}, 10);

Python

from zigbolt import IpcChannel

ch = IpcChannel.create("/market-data", term_length=1024*1024)
ch.publish(b"tick data here", msg_type_id=1)

還有 Go 和純 C 繫結。同一個共享記憶體通道可以從所有語言同時訪問——publisher 用 Zig,subscriber 用 Python,監控用 Go。所有人讀取同一塊 mmap 區域。


SBE 編解碼器:FIX 相容訊息

對於金融協議,ZigBolt 內建了完整的 SBE(Simple Binary Encoding)編解碼器,支援編譯期模式。內建訊息型別包括:

  • NewOrderSingle — 提交訂單
  • ExecutionReport — 成交回報
  • MarketDataIncrementalRefresh — 增量行情更新
  • MassQuote — 批次報價
  • Heartbeat — 心跳檢測
  • Logon — 身份認證

不需要外部程式碼生成器,不需要 XML。一切通過 Zig 結構體描述,在編譯期完成驗證。


Wire Protocol:相容 Aeron

ZigBolt 實現了與 Aeron 相容的 wire protocol flyweights:

  • DataHeaderFlyweight — 資料幀
  • StatusMessage — 流量控制
  • NAK — 否定確認
  • Setup、RTT、Error — 控制幀

這意味著 ZigBolt 可以與現有的 Aeron 基礎設施共存。遷移不必一步到位。


未來規劃

ZigBolt 目前版本為 0.2.1。核心穩定,基準測試可復現,繫結可用。近期計劃包括:

  • io_uring 後端 — Linux 6.0+ 上的零複製網路傳輸(IORING_OP_SEND_ZC)
  • DPDK / AF_XDP — 核心旁路,適用於每微秒都至關重要的場景
  • Multi-Raft — 按交易品種/策略分片
  • 列式歸檔 — 整合 Apache Arrow/Parquet 以支援分析
  • 大頁支援 — 預分配的 2MB/1GB 大頁以減少 TLB 缺失

立即體驗

從原始碼構建(zig build),執行基準測試(zig build bench),通過 FFI 從任何語言接入。如果你有 Zig 0.15.1 和幾分鐘時間——試試 ping-pong 基準測試,和你目前的方案比較一下。


連結:


引用

@software{soloviov2026zigbolt,
  author = {Soloviov, Eugen},
  title = {ZigBolt: 为什么我们用 Zig 从零打造了自己的 Aeron,实现了每条消息 20 纳秒延迟},
  year = {2026},
  url = {https://marketmaker.cc/zh/blog/post/zigbolt-zig-messaging-hft},
  version = {0.2.1},
  description = {详解我们如何用 Zig 语言从零构建了一套面向高频交易的超低延迟消息系统。无 JVM、无 GC、无意外。}
}
免責宣告:本文提供的資訊僅用於教育和參考目的,不構成財務、投資或交易建議。加密貨幣交易涉及重大損失風險。

Authors

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

緊跟市場步伐

訂閱我們的時事通訊,獲取獨家 AI 交易見解、市場分析和平台更新。

我們尊重您的隱私。您可以隨時退訂。