ZigBolt:為什麼我們用 Zig 從零打造了自己的 Aeron,實現了每條訊息 20 納秒延遲
無鎖環形緩衝區、零複製編解碼器、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 叢集——用一種這樣的語言重新實現會怎樣:
- 沒有執行時開銷(無 GC、無 safepoints)
- 能在編譯期生成程式碼(comptime)
- 與 C 庫(DPDK、io_uring)整合極其簡單
- 編譯出的二進位制檔案僅約 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 共識叢集加歸檔?也有。
基準測試:用資料說話

以下是在 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:以簡潔為美

單生產者單消費者環形緩衝區是最簡單、最快的無鎖資料結構。寫入端移動 head,讀取端移動 tail,無需 CAS——acquire/release 原子操作就夠了。
關鍵技巧是快取行填充。如果 head 和 tail 位於同一快取行,那麼每次更新其中一個計數器都會使另一個核心的快取失效(偽共享)。解決方案:
// 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 取代程式碼生成

在 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 缺失
立即體驗
- 官網:zigbolt-landing.vercel.app
- 文件:zigbolt-landing.vercel.app/getting-started/introduction/
- 原始碼:github.com/suenot/zigbolt
- 許可證:MIT
從原始碼構建(zig build),執行基準測試(zig build bench),通過 FFI 從任何語言接入。如果你有 Zig 0.15.1 和幾分鐘時間——試試 ping-pong 基準測試,和你目前的方案比較一下。
連結:
- ZigBolt 官網:zigbolt-landing.vercel.app
- GitHub:github.com/suenot/zigbolt
- Aeron(供對比):github.com/real-logic/aeron | 我們的 Aeron 概述
- Zig 語言:ziglang.org
- Marketmaker.cc:marketmaker.cc
引用
@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
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.