Consumer Group
Load-balanced event delivery across a consumer group
Overview
A consumer group turns Events pub/sub from a broadcast into a work queue. By default every subscriber on a channel gets every event — fine for notifications, but wasteful when you want a pool of workers to split a stream of tasks so each one is handled exactly once. Reach for a consumer group whenever you're scaling out event processing and duplicate work isn't just wasteful but actively wrong (double-charging a customer, double-sending an alert).
It works by naming a group when you subscribe: every subscriber that passes the same group argument to subscribe_to_events joins that group, and the broker round-robins each event to exactly one member instead of fanning it out to all of them. Passing an empty string reverts to normal fan-out, so the same call can flip between the two delivery models with one argument.
Gotchas: consumer groups are scoped per channel — subscribing to the same group on a different channel does not share load balancing across channels. A group with zero active subscribers behaves like no subscribers at all; events aren't queued for a group that's temporarily empty the way they are for durable queue messages. And because delivery is round-robin rather than content-aware, you can't route specific events to specific workers within a group — if you need that, partition by channel instead.
Prerequisites
- KubeMQ server running on
localhost:50000 - Rust SDK installed (
cargo add kubemq)
Code
use kubemq::prelude::*;
use kubemq::EventBuilder;
use std::time::Duration;
#[tokio::main]
async fn main() -> kubemq::Result<()> {
let client = KubemqClient::builder()
.host("localhost")
.port(50000)
.build()
.await?;
let channel = "rust-events.consumer-group";
let group = "my-consumer-group";
let sub1 = client
.subscribe_to_events(
channel,
group,
|event| {
Box::pin(async move {
println!(
"Consumer-1 received: id={}, body={}",
event.id,
String::from_utf8_lossy(&event.body)
);
})
},
None,
)
.await?;
let sub2 = client
.subscribe_to_events(
channel,
group,
|event| {
Box::pin(async move {
println!(
"Consumer-2 received: id={}, body={}",
event.id,
String::from_utf8_lossy(&event.body)
);
})
},
None,
)
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
for i in 0..10 {
let event = EventBuilder::new()
.channel(channel)
.body(format!("message-{}", i).into_bytes())
.build();
client.send_event(event).await?;
}
println!("Sent 10 events to group '{}'", group);
tokio::time::sleep(Duration::from_secs(2)).await;
sub1.unsubscribe().await;
sub2.unsubscribe().await;
client.close().await?;
Ok(())
}How It Works
- Both subscribers pass the same
groupname, so each event is delivered to exactly one consumer. - The server distributes events round-robin across group members.
- Review timeouts, channel names, and client IDs before running against shared environments.
- Run the program while the server from the prerequisites is available.
Related
Was this page helpful?