Pub/Sub with tokio::sync::broadcast
Before the code: messaging and replication, in plain English
Picture a group chat. One person publishes a message. The chat app knows who is in the room and forwards the message to each of them. That is publish and subscribe, and it is the simplest piece of Redis that is not a key-value store. Replication is the same idea pointed at a backup machine: the leader sends every write to a follower, who quietly replays them. Same plumbing, different audience. In Rust we wire this with tokio::sync::broadcast channels.
Fan-out and follow-the-leader
Quiz: Quiz
Loading practice…
tokio::sync::broadcast is multi-producer multi-consumer. Every published message reaches every active subscriber. We lazily create a Sender per channel name and clone receivers on SUBSCRIBE.
impl StoreInner {
fn sender(&mut self, channel: &str) -> broadcast::Sender<String> {
if let Some(tx) = self.channels.get(channel) { return tx.clone(); }
let (tx, _rx) = broadcast::channel::<String>(1024);
self.channels.insert(channel.to_string(), tx.clone());
tx
}
}Lazy channel creation. The throwaway _rx keeps the Sender alive even with no subscribers.
tokio::select! {
read_res = socket.read(&mut tmp) => {
// dispatch any complete RESP frames in the buffer
}
Some(payload) = msg_rx.recv() => {
socket.write_all(&payload).await?;
}
}The connection task interleaves: read commands from the socket OR forward messages from the mpsc into the socket. select! cancels the loser each iteration.
Why mpsc as a bridge? Each subscription task takes the broadcast Receiver and forwards messages to a per-connection mpsc. The connection task selects on the mpsc, not on every broadcast Receiver. One unified place to wait. Cleaner than juggling N broadcast receivers in one select!.
Quiz: Quiz
Loading practice…