← 返回文章列表
March 27, 2026
5 分钟阅读

ZigBolt:为什么我们用 Zig 从零打造了自己的 Aeron,实现了每条消息 20 纳秒延迟

ZigBolt:为什么我们用 Zig 从零打造了自己的 Aeron,实现了每条消息 20 纳秒延迟
#zigbolt
#zig
#高频交易
#低延迟
#消息系统
#aeron
#ipc
#开源

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、无意外。}
}
免责声明:本文提供的信息仅用于教育和参考目的,不构成财务、投资或交易建议。加密货币交易涉及重大损失风险。

MarketMaker.cc Team

量化研究与策略

在 Telegram 中讨论
Newsletter

紧跟市场步伐

订阅我们的时事通讯,获取独家 AI 交易见解、市场分析和平台更新。

我们尊重您的隐私。您可以随时退订。