I'm freakin elated to share something I've been hammering away at: rzmq – a pure Rust, async ZeroMQ that's aiming for (and achieving!) a new level of performance on Linux!
If you've been in the distributed systems or messaging game, ZeroMQ (ØMQ) is legendary. It's the swiss-army knife, powerful and flexible. But what if we could take that solid foundation and supercharge it with modern Rust and the raw power of Linux's io_uring? That's rzmq.
The "Why": Rust, Raw Speed, and a Modern Take
So, what's the big deal with yet another ZeroMQ implementation?
Unleashing Blazing Performance on Linux: This was the *primary* driver. libzmq is fast, but Linux has evolved. `rzmq` features an **`io_uring` backend** for its TCP transport, and when combined with **TCP Corking**, the results are astounding. **We've benchmarked `rzmq` achieving superior throughput and lower latency compared to other ZeroMQ implementations, including `libzmq`, in high-throughput scenarios.** This isn't just an aspiration; it's a demonstrated capability, making `rzmq` a serious contender for your most demanding Linux-based messaging workloads.
Rust's Fearless Concurrency & Memory Safety: Building a complex messaging library in Rust means we get incredible compile-time guarantees against data races and memory bugs. No more chasing segfaults from C FFI calls – just robust, concurrent code.
Modern Security with Noise_XX: While `libzmq` has CURVE, `rzmq` takes a different path for modern, robust authenticated encryption by implementing **Noise_XX**. This provides strong cryptographic guarantees for `rzmq`-to-`rzmq` communication. We also support standard PLAIN and NULL mechanisms for interoperability where appropriate.
Asynchronous from the Ground Up: Built on Tokio, `rzmq` is `async/await` native, fitting seamlessly into modern Rust applications.
Under the Hood: Actors and `io_uring` Magic
Internally, rzmq uses an actor model. Each socket is managed by dedicated, lightweight actors handling state, message routing, and protocol logic, communicating via asynchronous channels.
But the real magic for Linux users is the `io_uring` backend. This isn't just a wrapper; it's a deep integration designed to minimize syscalls and data copies. We're also exploring and implementing features like:
Experimental Zerocopy Send: Directly passing data to the kernel to avoid userspace-to-kernel copies.
Experimental Multishot Receive: Submitting multiple receive buffers at once to reduce syscall overhead.
The goal is to squeeze every last drop of performance out of the system for your messaging needs.
A Glimpse of the API
The API aims for familiarity for `libzmq` users, but with Rust's idiomatic async style:
// Example (ensure features like "inproc" are enabled in Cargo.toml for this snippet)
use rzmq::{Context, SocketType, Msg, ZmqError, socket::options::*};
use std::time::Duration;
#[tokio::main]
async fn run_my_service() -> Result<(), ZmqError> {
// On Linux, if using io_uring features:
// First, initialize the io_uring backend (typically once at app start)
// #[cfg(all(target_os = "linux", feature = "io-uring"))]
// rzmq::uring::initialize_uring_backend(Default::default())?;
// 1. Create a context
let ctx = rzmq::Context::new()?;
// 2. Create sockets
let publisher = ctx.socket(SocketType::Pub)?;
let subscriber = ctx.socket(SocketType::Sub)?;
// For io_uring on TCP, enable it per socket:
// #[cfg(all(target_os = "linux", feature = "io-uring"))] {
// publisher.set_option(IO_URING_SESSION_ENABLED, 1i32).await?;
// subscriber.set_option(IO_URING_SESSION_ENABLED, 1i32).await?;
// // Optionally enable TCP_CORK for optimal batching with io_uring
// publisher.set_option(TCP_CORK, 1i32).await?;
// }
// 3. Bind or connect
publisher.bind("inproc://my-topic").await?;
// Give a moment for inproc bind to register
tokio::time::sleep(Duration::from_millis(10)).await;
subscriber.connect("inproc://my-topic").await?;
// Allow time for connection and subscription to propagate (especially for TCP)
tokio::time::sleep(Duration::from_millis(50)).await;
// 4. Set options
subscriber.set_option(SUBSCRIBE, "NASDAQ:").await?; // Topic filter
publisher.set_option(SNDHWM, 1000i32).await?; // Send High-Water Mark
// 5. Send and receive asynchronously
tokio::spawn(async move {
let mut count = 0;
loop {
let payload = format!("NASDAQ:AAPL 150.0{}", count);
if let Err(e) = publisher.send(Msg::from_vec(payload.into_bytes())).await {
eprintln!("Publisher error: {}", e);
break;
}
tokio::time::sleep(Duration::from_secs(1)).await;
count += 1;
if count > 5 { break; } // Limit messages for example
}
println!("Publisher finished.");
// `publisher` is dropped here, closing its side.
Ok::<(), ZmqError>(()) // Explicit type for spawn block
});
let mut received_count = 0;
loop {
match subscriber.recv().await {
Ok(msg) => {
println!("Subscriber Got: {}", String::from_utf8_lossy(msg.data().unwrap_or_default()));
received_count += 1;
if received_count > 5 { break; } // Exit after receiving expected messages
}
Err(e) => {
eprintln!("Subscriber error: {}", e);
break;
}
}
}
println!("Subscriber finished.");
// 6. Graceful shutdown
ctx.term().await?;
println!("Context terminated.");
// If io_uring was initialized, shut it down
// #[cfg(all(target_os = "linux", feature = "io-uring"))]
// rzmq::uring::shutdown_uring_backend()?;
Ok(())
}
Familiar patterns, Rust's async ergonomics, and extreme performance where it counts!
Current Status: Beta – Fast, Functional, and Evolving!
rzmq
is now in **Beta**. Core socket types (PUB/SUB, REQ/REP, PUSH/PULL, DEALER/ROUTER) are functional over TCP, IPC (Unix-like systems), and Inproc transports. The `io_uring` backend for TCP is operational and delivering on its performance promises on Linux Kernel 6.x.
Implemented & Tested: Core patterns, ZMTP 3.1 basics, many common socket options (`SNDHWM`, `RCVHWM`, `SNDTIMEO`, `RCVTIMEO`, `RECONNECT_IVL`, etc.), TCP Corking, the `io_uring` backend with experimental Zerocopy Send and Multishot Receive, socket monitoring, and security mechanisms (NULL, PLAIN, Noise_XX).
Interoperability: We aim for wire-level compatibility with `libzmq` for NULL and PLAIN security. Noise_XX is an `rzmq`-specific modern security offering.
What's NOT in Scope: Full `libzmq` feature parity is a non-goal. Specifically, **CURVE security and ZAP (ZeroMQ Authentication Protocol) are not supported and not planned.** `rzmq` focuses on its chosen feature set and security mechanisms.
Performance & Robustness: While the performance with `io_uring` is a highlight, continued testing across diverse workloads and further hardening for edge cases are ongoing priorities as we move towards a 1.0 release.
What's Next? More Benchmarking and Real-World Usage!
We're focused on more extensive benchmarking to quantify the performance gains across various scenarios and further refining the `io_uring` optimizations. Integrating `rzmq` into real-world applications will be key to battle-testing its stability and identifying areas for improvement.
Join the Journey!
If a pure Rust, async ZeroMQ that pushes performance boundaries on Linux (and offers modern security like Noise_XX) excites you, I'd be thrilled for you to check out the rzmq GitHub repository.
Whether it's trying it out, providing feedback, reporting bugs, or contributing code, your involvement is highly welcome! Stars on GitHub also help gauge interest.
This project has been an incredible learning experience, blending the elegance of ZeroMQ's patterns with Rust's power and Tokio's async capabilities, topped with the cutting-edge potential of `io_uring`. The journey to build the fastest ZeroMQ (on Linux, at least!) is well underway.
Stay tuned for more updates!