← Quay lại danh sách bài viết
March 27, 2026
5 phút đọc

ZigBolt: Tại Sao Chúng Tôi Tự Xây Aeron Bằng Zig Và Đạt Được 20 Nanosecond Mỗi Tin Nhắn

#zigbolt
#zig
#hft
#độ-trễ-thấp
#nhắn-tin
#aeron
#ipc
#mã-nguồn-mở

ZigBolt — nhắn tin độ trễ cực thấp Bộ đệm vòng không khóa, codec zero-copy, cụm Raft — tất cả trong Zig thuần túy, tất cả mã nguồn mở.

Nếu bạn làm việc trong giao dịch thuật toán hoặc tạo lập thị trường, bạn biết giá trị của từng microsecond. Một lần chuyển ngữ cảnh thêm — và lệnh của bạn đến thứ hai. Một lần tạm dừng GC của JVM — và nhà tạo lập thị trường ở phía kia đã cập nhật báo giá. Trong một thế giới mà tiền được đo bằng nanosecond, cơ sở hạ tầng nhắn tin không phải là đường ống nhàm chán giữa các dịch vụ — đó là lợi thế cạnh tranh.

Chúng tôi đã xây dựng ZigBolt — một hệ thống nhắn tin cho giao dịch tần số cao được viết bằng Zig. Từ đầu. Không JVM, không bộ thu gom rác, không Media Driver, không cấu hình XML. Và chúng tôi đạt được độ trễ p50 20 nanosecond trên bộ đệm vòng SPSC và 30 nanosecond trên IPC qua bộ nhớ dùng chung.

Bài viết này đề cập đến lý do tại sao chúng tôi cần nó, cách thức hoạt động bên trong, và tại sao Zig.


TL;DR

  • ZigBolt — hệ thống nhắn tin mã nguồn mở (MIT) cho HFT bằng Zig thuần túy
  • p50 20 ns trên SPSC, p50 30 ns trên IPC — nhanh hơn các con số đã công bố của Aeron
  • Codec zero-copy chạy ở 0 ns (tạo ra tại compile-time, runtime chỉ là ép kiểu con trỏ)
  • Không GC, không JVM, không Media Driver — thư viện nhúng trực tiếp vào ứng dụng của bạn
  • Cụm Raft, archive, sequencer — tất cả đã bao gồm
  • FFI bindings cho Rust, Python, Go, TypeScript, C — làm việc bằng bất kỳ ngôn ngữ nào bạn thích

Vấn Đề: Tại Sao Aeron Tuyệt Vời Nhưng Chưa Đủ

Aeron từ Real Logic là tiêu chuẩn thực tế cho nhắn tin độ trễ thấp trong thị trường vốn. Hàng chục công ty HFT sử dụng nó, đã được thử nghiệm thực chiến, và có kiến trúc xuất sắc. Nhưng Aeron có một vấn đề cơ bản, và tên của nó là JVM.

Safepoints JVM: Kẻ Thù Vô Hình

Ngay cả khi bạn cẩn thận đặt tất cả dữ liệu vào bộ nhớ off-heap, ngay cả khi bạn đã vô hiệu hóa GC ergonomics và đặt GuaranteedSafepointInterval=300000 — JVM vẫn thỉnh thoảng dừng tất cả các luồng tại một safepoint. Đây không phải là bug, đó là quyết định kiến trúc: JVM cần safepoints để deoptimization, biased locking, và stack walking.

Trong thực tế nó trông như thế này: luồng của bạn gửi tin nhắn ở p50 = 200 ns, và đột nhiên p99.9 tăng vọt lên 50 us. Không có lý do rõ ràng. Vì một trong các luồng JVM đã quyết định đến lúc rồi.

Media Driver: Một Hop Thêm

Aeron hoạt động thông qua một Media Driver — một tiến trình riêng (hoặc JVM nhúng) định tuyến tin nhắn giữa publisher và subscriber qua bộ nhớ dùng chung. Điều này mang lại sự cô lập tốt nhưng thêm ít nhất một hop thêm:

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

Mỗi hop có nghĩa là thêm nanosecond, thêm cache miss, thêm tính không thể đoán trước.

SBE: Một Bước Build Riêng Biệt

Simple Binary Encoding — codec FIX tiêu chuẩn cho tin nhắn tài chính. Trong hệ sinh thái Aeron, đó là một tiện ích Java riêng biệt tạo ra code từ các schema XML. Một phụ thuộc riêng biệt, một bước build riêng biệt, một tập hợp vấn đề riêng biệt.


Giải Pháp: ZigBolt

Chúng tôi tự hỏi: nếu chúng tôi lấy những ý tưởng tốt nhất của Aeron — triple-buffered log, bộ đệm vòng không khóa, cụm Raft — và thực hiện chúng trong một ngôn ngữ:

  1. Không có overhead runtime (không GC, không safepoints)
  2. Cho phép tạo code tại compile time (comptime)
  3. Tích hợp dễ dàng với thư viện C (DPDK, io_uring)
  4. Biên dịch thành binary ~100 KB

Ngôn ngữ đó là Zig.


Kiến Trúc

┌─────────────────────────────────────────────────────────┐
│  Publisher/Subscriber API (typed generic wrappers)       │
├─────────────────────────────────────────────────────────┤
│  Transport Layer (channel factory & lifecycle)            │
├─────────────────────────────────────────────────────────┤
│  IPC Channel (shared memory)  │ UDP Channel (network)    │
├─────────────────────────────────────────────────────────┤
│  WireCodec (comptime, zero-copy) │ SBE Encoder/Decoder   │
├─────────────────────────────────────────────────────────┤
│  Ring Buffers (SPSC/MPSC) │ LogBuffer (triple-buffered)  │
├─────────────────────────────────────────────────────────┤
│  Archive (replay) │ Sequencer (total order) │ Raft (HA)  │
└─────────────────────────────────────────────────────────┘

Bảy lớp, mỗi lớp có thể sử dụng độc lập. Chỉ cần một bộ đệm vòng SPSC cho IPC giữa hai tiến trình? Lấy nó đi. Cần một cụm đầy đủ với đồng thuận Raft và lưu trữ? Cũng có ở đó.


Benchmarks: Con Số, Không Phải Lời Nói

Kết quả Benchmark ZigBolt

Kết quả benchmark thực tế trên 10 triệu lần lặp (Apple Silicon / macOS):

Bộ Đệm Vòng SPSC

Kích Thước Tin Nhắn p50 p99 p99.9 Thông Lượng
8 bytes 20 ns 30 ns 120 ns 42.8M msg/s
32 bytes 30 ns 50 ns 150 ns 28.5M msg/s
64 bytes 50 ns 60 ns 320 ns 17.6M msg/s
256 bytes 30 ns 50 ns 50 ns 29.5M msg/s

Kênh IPC (bộ nhớ dùng chung)

Kích Thước Tin Nhắn p50 p99 p99.9 Thông Lượng
64 bytes 30 ns 40 ns 40 ns 35.7M msg/s
256 bytes 40 ns 40 ns 170 ns 27.4M msg/s
1024 bytes 90 ns 260 ns 900 ns 9.9M msg/s

LogBuffer (triple-buffered theo phong cách Aeron)

Kích Thước Tin Nhắn p50 p99 p99.9 Thông Lượng
32 bytes 30 ns 40 ns 320 ns 33.6M msg/s
64 bytes 30 ns 30 ns 160 ns 38.0M msg/s
256 bytes 30 ns 40 ns 60 ns 31.1M msg/s

WireCodec (comptime zero-copy)

Thao Tác Độ Trễ Thông Lượng
Encode (32 bytes) 0 ns memcpy nội tuyến
Decode (32 bytes) ~0.4 ns 2.7 tỷ msg/s

Đúng vậy, bạn đọc đúng: encoding mất không nanosecond. Vì WireCodec(T) xác thực struct tại compile time và biến encode/decode thành một @memcpy hoặc ép kiểu con trỏ thông thường. Overhead runtime = không.

Để so sánh: Aeron khẳng định IPC RTT (round-trip) ~250 ns. Độ trễ một chiều của chúng tôi là 30 ns. Ngay cả tính round-trip, chúng tôi nhanh hơn 4 lần.


Cách Hoạt Động Bên Trong

SPSC Không Khóa: Sự Đơn Giản Là Đức Hạnh

Bộ Đệm Vòng SPSC

Bộ đệm vòng single-producer single-consumer là cấu trúc dữ liệu không khóa đơn giản và nhanh nhất. Người ghi di chuyển head, người đọc di chuyển tail, không cần CAS — acquire/release atomics là đủ.

Mẹo chính là đệm cache-line. Nếu headtail ngồi trong cùng một cache line, mọi cập nhật cho một bộ đếm sẽ vô hiệu hóa cache cho core kia (false sharing). Cách sửa:

// Head (vị trí ghi) — trên cache line riêng của nó
head: std.atomic.Value(usize) align(128) = .init(0),

// Đệm 128 bytes — đảm bảo cô lập
_pad0: [128 - @sizeOf(std.atomic.Value(usize))]u8 = .{0} ** ...,

// Tail (vị trí đọc) — trên cache line riêng của nó
tail: std.atomic.Value(usize) align(128) = .init(0),

128 bytes, không phải 64 — vì trên Apple Silicon (và nhiều chip ARM) hardware prefetcher có thể làm việc với các cặp cache line. Chúng tôi chơi an toàn.

WireCodec: Comptime Thay Vì Tạo Code

WireCodec — codec compile-time

Trong thế giới Java/C++, binary codec yêu cầu một bước riêng biệt: viết schema, chạy code generator, lấy code, biên dịch. Trong Zig, tất cả điều này xảy ra tại compile time:

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

const Codec = WireCodec(TickMsg);

// Encode — chỉ là memcpy 32 byte. Nội tuyến thành 1-2 lệnh.
Codec.encode(&msg, buf[0..Codec.wire_size]);

// Decode — ép kiểu con trỏ. Không có copy.
const tick = Codec.decode(buf[0..Codec.wire_size]);

Trình biên dịch Zig xác thực tại comptime:

  • Struct được packed (không có lỗ padding)
  • Kích thước là bội số của 8 bytes (căn chỉnh cho SIMD)
  • Tất cả các trường là kiểu primitive

Nếu có gì đó sai — lỗi biên dịch, không phải exception runtime lúc 3 giờ sáng trong production.

IPC qua Bộ Nhớ Dùng Chung

Hai tiến trình ánh xạ cùng một file trong /dev/shm. Publisher ghi vào bộ đệm vòng, subscriber đọc. Không có socket, không có system call trên hot path:

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

// Subscriber (tiến trình khác)
const channel = try IpcChannel.open("/market-data", .{
    .term_length = 1024 * 1024,
});
const count = channel.poll(handler_fn, 10);

Toàn bộ đường đi từ publish() đến lời gọi handler_fn trong subscriber — 30 nanosecond cho tin nhắn 64 byte.

Độ Tin Cậy Dựa Trên NAK cho UDP

Đối với vận chuyển mạng, ZigBolt sử dụng retransmission do receiver điều khiển. Receiver theo dõi các khoảng trống trong số thứ tự qua bitmap và gửi NAK (negative acknowledgement) đến sender. Thêm vào đó là điều khiển tắc nghẽn AIMD — slow start và congestion avoidance giống TCP — để tránh làm ngập mạng.

Cụm Raft: Khi Bạn Cần Tính Nhất Quán

Đối với các trường hợp mà mất một tin nhắn là không thể chấp nhận (ví dụ: matching engine), ZigBolt bao gồm đồng thuận Raft đầy đủ:

  • Bầu chọn leader với timeout có thể cấu hình (150-300 ms)
  • Sao chép log — leader sao chép mỗi tin nhắn đến followers
  • Write-ahead log với xác thực CRC32 và phục hồi sau crash
  • Snapshots — để WAL không tăng trưởng mãi mãi

Archive: Ghi và Phát Lại

Tất cả tin nhắn có thể được ghi vào archive trên đĩa được phân đoạn. Sau đó — phát lại từ bất kỳ vị trí nào theo thời gian hoặc số thứ tự. Nén tích hợp kiểu LZ4 không có phụ thuộc bên ngoài. Chỉ mục thưa thớt để tra cứu nhanh trong các phân đoạn.

Sequencer Thứ Tự Toàn Cục

Đối với tạo lập thị trường trên nhiều sàn giao dịch, điều quan trọng là tất cả các sự kiện có thứ tự toàn cục. Sequencer lấy N luồng đầu vào và hợp nhất chúng thành một, gán số thứ tự tăng đơn điệu. Mỗi người tham gia thấy cùng một chuỗi sự kiện.


Tại Sao Zig, Không Phải Rust/C/C++?

Chúng tôi chọn giữa bốn ứng viên. Đây là so sánh trung thực:

Tiêu Chí Zig C/C++ Rust Java (Aeron)
GC / runtime overhead Không Không Không JVM safepoints, GC
Tạo code comptime Có sẵn Macros/templates proc macros Không
C interop (DPDK, io_uring) @cImport đơn giản Có sẵn FFI/bindgen Overhead JNI
SIMD @Vector, tích hợp Intrinsics packed_simd (không ổn định) Gợi ý vector hóa
Cross-compilation Có sẵn CMake địa ngục cargo target N/A
Thời gian build Giây Phút (C++) Phút Giây + khởi động JVM
Luồng điều khiển ẩn Không Exceptions, ép kiểu ngầm Panics trong unwrap Exceptions

Zig cho chúng tôi sự kết hợp độc đáo: hiệu suất cấp C + an toàn trong quá trình phát triển + metaprogramming comptime (codec, bảng tra cứu, máy trạng thái giao thức — tất cả được tạo ra tại compile time) + tích hợp dễ dàng với DPDK, liburing, ef_vi qua @cImport.

Và binary Zig nặng ~100 KB. So với 20+ MB cho giải pháp dựa trên JVM.


Bindings: Làm Việc Bằng Ngôn Ngữ Của Bạn

ZigBolt biên dịch thành thư viện dùng chung với C-ABI, và chúng tôi có sẵn bindings cho năm ngôn ngữ:

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();

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)

Ngoài ra còn có Go và C thuần túy. Cùng một kênh bộ nhớ dùng chung có thể truy cập từ tất cả các ngôn ngữ đồng thời — publisher trong Zig, subscriber trong Python, monitoring trong Go. Tất cả đều đọc cùng một vùng mmap.


Codec SBE: Tin Nhắn Tương Thích FIX

Đối với các giao thức tài chính, ZigBolt bao gồm codec SBE (Simple Binary Encoding) đầy đủ với schema compile-time. Các loại tin nhắn tích hợp:

  • NewOrderSingle — gửi lệnh
  • ExecutionReport — báo cáo thực hiện
  • MarketDataIncrementalRefresh — cập nhật dữ liệu thị trường gia tăng
  • MassQuote — báo giá hàng loạt
  • Heartbeat — kiểm tra kết nối
  • Logon — xác thực

Không có code generator bên ngoài, không có XML. Mọi thứ được mô tả bằng struct Zig và được xác thực tại compile time.


Giao Thức Wire: Tương Thích Aeron

ZigBolt thực hiện các flyweight giao thức wire tương thích Aeron:

  • DataHeaderFlyweight — khung dữ liệu
  • StatusMessage — điều khiển luồng
  • NAK — negative acknowledgement
  • Setup, RTT, Error — khung dịch vụ

Điều này có nghĩa là ZigBolt có thể cùng tồn tại với cơ sở hạ tầng Aeron hiện có. Việc di chuyển không phải là một big bang.


Tiếp Theo Là Gì

ZigBolt hiện đang ở phiên bản 0.2.1. Core ổn định, benchmarks có thể tái tạo, bindings hoạt động. Sắp ra mắt:

  • Backend io_uring — vận chuyển mạng zero-copy trên Linux 6.0+ (IORING_OP_SEND_ZC)
  • DPDK / AF_XDP — kernel bypass khi mỗi microsecond đều quan trọng
  • Multi-Raft — sharding theo công cụ/chiến lược
  • Archive dạng cột — tích hợp Apache Arrow/Parquet cho phân tích
  • Hỗ trợ Hugepage — hugepage 2MB/1GB pre-faulted để giảm thiểu TLB miss

Thử Ngay

Build từ nguồn (zig build), chạy benchmarks (zig build bench), kết nối qua FFI từ bất kỳ ngôn ngữ nào. Nếu bạn có Zig 0.15.1 và vài phút — hãy thử benchmark ping-pong và so sánh với giải pháp hiện tại của bạn.


Liên Kết:


Trích Dẫn

@software{soloviov2026zigbolt,
  author = {Soloviov, Eugen},
  title = {ZigBolt: Why We Built Our Own Aeron in Zig and Hit 20 Nanoseconds Per Message},
  year = {2026},
  url = {https://marketmaker.cc/vi/blog/post/zigbolt-zig-messaging-hft},
  version = {0.2.1},
  description = {Cách chúng tôi xây dựng hệ thống nhắn tin cực thấp độ trễ cho HFT từ đầu bằng Zig. Không JVM, không GC, không bất ngờ.}
}
Tuyên bố miễn trừ trách nhiệm: Thông tin được cung cấp trong bài viết này chỉ nhằm mục đích giáo dục và thông tin, không cấu thành lời khuyên về tài chính, đầu tư hoặc giao dịch. Giao dịch tiền mã hóa tiềm ẩn rủi ro thua lỗ đáng kể.

Tác Giả

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

Đi Trước Thị Trường

Đăng ký nhận bản tin của chúng tôi để có những thông tin chuyên sâu độc quyền về AI trading, phân tích thị trường và các cập nhật nền tảng.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất kỳ lúc nào.