Redis Pub/Sub is useful for simple messaging, but it does not store messages. If a service is offline, messages can be lost. Redis
Streams solve this problem by providing persistent messaging with consumer groups, making it suitable for event-driven microservices.
This article explains how to build a simple event-driven architecture using Redis Streams in Spring Boot.
1. What Are Redis Streams
Redis Streams are a data structure designed for
append-only event logs.
Key features:
- Persistent messages
- Ordered event streams
- Consumer groups for scalable processing
- Message acknowledgments
Example stream structure:
orders-stream
├── message-1
├── message-2
└── message-3
Each message gets a unique ID generated by Redis.
2. Creating a Stream Producer
A producer writes events to a Redis stream.
Example service:
@Service
public class OrderEventProducer {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void publishOrderEvent(String orderId, String status) {
Map<String, String> event = new HashMap<>();
event.put("orderId", orderId);
event.put("status", status);
redisTemplate.opsForStream()
.add("orders-stream", event);
}
}
Example event data:
orderId: 123
status: CREATED
This event is stored permanently in the stream.
3. Creating a Consumer Group
Consumer groups allow multiple services to process events without duplication.
Create a group:
redisTemplate.opsForStream()
.createGroup("orders-stream", "order-processors");
Now multiple consumers can share the workload.
4. Implementing a Stream Consumer
Consumers read messages from the stream.
@Service
public class OrderEventConsumer {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void consumeEvents() {
List<MapRecord<String, Object, Object>> messages =
redisTemplate.opsForStream().read(
Consumer.from("order-processors", "consumer-1"),
StreamReadOptions.empty().count(10),
StreamOffset.create("orders-stream", ReadOffset.lastConsumed())
);
if(messages != null) {
for (MapRecord<String, Object, Object> message : messages) {
System.out.println("Processing event: " + message.getValue());
redisTemplate.opsForStream()
.acknowledge("orders-stream",
"order-processors",
message.getId());
}
}
}
}
After processing, the consumer sends an acknowledgment.
5. Running Consumers Periodically
Use a scheduled job to continuously read events.
@Scheduled(fixedDelay = 2000)
public void pollStream() {
orderEventConsumer.consumeEvents();
}
This checks for new messages every two seconds.
6. Using Multiple Consumers
Consumer groups allow horizontal scaling.
Example:
orders-stream
↓
order-processors
├── consumer-1
├── consumer-2
└── consumer-3
Each consumer processes different events, improving throughput.
7. Handling Failed Messages
If a consumer crashes before acknowledging a message, Redis keeps the event in the
pending entries list (PEL).
Another consumer can reclaim and process it later.
This ensures reliable event processing.
8. Real Use Cases
Redis Streams are ideal for:
- Microservice communication
- Order processing systems
- Event sourcing
- Activity logging
- Real-time analytics pipelines
image quote pre code