Building a Reliable Event-Driven System with Golang and Redis Streams

Event-driven systems are widely used in modern software architecture, allowing components to communicate asynchronously. While traditional implementations often use message brokers like Kafka, Google Pub/Sub, or Amazon SQS, sometimes we want to build a lightweight yet reliable alternative for learning or custom requirements. In this post, we'll explore how to create a resilient event-driven system using Golang and Redis Streams. Why Reliability Matters in Event-Driven Systems In many cases, losing an event is unacceptable. Consider an alert monitoring system that notifies users of critical issues. Missing a single event could lead to severe consequences, such as downtime, security breaches, or failed transactions. Thus, we need a mechanism that ensures: Durability: Events should persist until processed. Acknowledgment & Retry: Events should not be lost if processing fails. Scalability: The system should handle multiple producers and consumers efficiently. Why Use Redis Streams Instead of Pub/Sub? While Redis Pub/Sub enables real-time event streaming, it lacks durability—messages are lost if no subscriber is active. Redis Streams, on the other hand, offers: Message persistence Consumer groups for parallel processing Message acknowledgment and replay Efficient handling of large-scale event processing Architecture Overview Our system consists of three main components: Event Producers: Services that generate and push events into Redis Streams. Redis Streams: The central event storage and message broker. Event Consumers: Workers that read, process, and acknowledge events. Implementing the System in Golang 1. Setting Up Redis Make sure you have Redis installed and running: redis-server 2. Installing Redis Client for Golang We'll use the github.com/redis/go-redis/v9 package to interact with Redis Streams: go get github.com/redis/go-redis/v9 3. Implementing an Event Producer The producer sends events to a Redis stream. package main import ( "context" "fmt" "log" "github.com/redis/go-redis/v9" ) var ctx = context.Background() func main() { client := redis.NewClient(&redis.Options{ Addr: "localhost:6379", }) eventData := map[string]interface{}{ "message": "Critical alert! Server down.", } _, err := client.XAdd(ctx, &redis.XAddArgs{ Stream: "alerts", Values: eventData, }).Result() if err != nil { log.Fatalf("Failed to publish event: %v", err) } fmt.Println("Event published successfully") } 4. Implementing an Event Consumer The consumer reads and processes events from Redis Streams. package main import ( "context" "fmt" "log" "github.com/redis/go-redis/v9" ) var ctx = context.Background() func main() { client := redis.NewClient(&redis.Options{ Addr: "localhost:6379", }) for { res, err := client.XRead(ctx, &redis.XReadArgs{ Streams: []string{"alerts", "$"}, Count: 1, Block: 0, }).Result() if err != nil { log.Fatalf("Failed to read event: %v", err) } for _, stream := range res { for _, message := range stream.Messages { fmt.Printf("Processing event: %v\n", message.Values) } } } } Enhancements for Production While this implementation is a simple demonstration, a production-ready version should include: Error handling & retries: Implement exponential backoff for failures. Consumer groups: Distribute workload across multiple consumers. Monitoring & logging: Track event processing metrics. Persistence & backups: Use disk persistence to prevent data loss. Conclusion Using Redis Streams and Golang, we can build a reliable event-driven system with durability, acknowledgment, and scalability. This lightweight alternative is great for learning and small-scale applications where high availability is required. Have you built an event-driven system with Redis Streams? Let me know your thoughts and experiences!

Apr 14, 2025 - 18:50
 0
Building a Reliable Event-Driven System with Golang and Redis Streams

Event-driven systems are widely used in modern software architecture, allowing components to communicate asynchronously. While traditional implementations often use message brokers like Kafka, Google Pub/Sub, or Amazon SQS, sometimes we want to build a lightweight yet reliable alternative for learning or custom requirements. In this post, we'll explore how to create a resilient event-driven system using Golang and Redis Streams.

Why Reliability Matters in Event-Driven Systems

In many cases, losing an event is unacceptable. Consider an alert monitoring system that notifies users of critical issues. Missing a single event could lead to severe consequences, such as downtime, security breaches, or failed transactions. Thus, we need a mechanism that ensures:

Durability: Events should persist until processed.

Acknowledgment & Retry: Events should not be lost if processing fails.

Scalability: The system should handle multiple producers and consumers efficiently.

Why Use Redis Streams Instead of Pub/Sub?

While Redis Pub/Sub enables real-time event streaming, it lacks durability—messages are lost if no subscriber is active. Redis Streams, on the other hand, offers:

Message persistence

Consumer groups for parallel processing

Message acknowledgment and replay

Efficient handling of large-scale event processing

Architecture Overview

Our system consists of three main components:

Event Producers: Services that generate and push events into Redis Streams.

Redis Streams: The central event storage and message broker.

Event Consumers: Workers that read, process, and acknowledge events.

Implementing the System in Golang

1. Setting Up Redis

Make sure you have Redis installed and running:

redis-server

2. Installing Redis Client for Golang

We'll use the github.com/redis/go-redis/v9 package to interact with Redis Streams:

go get github.com/redis/go-redis/v9

3. Implementing an Event Producer

The producer sends events to a Redis stream.

package main

import (
    "context"
    "fmt"
    "log"
    "github.com/redis/go-redis/v9"
)

var ctx = context.Background()

func main() {
    client := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })

    eventData := map[string]interface{}{
        "message": "Critical alert! Server down.",
    }

    _, err := client.XAdd(ctx, &redis.XAddArgs{
        Stream: "alerts",
        Values: eventData,
    }).Result()

    if err != nil {
        log.Fatalf("Failed to publish event: %v", err)
    }

    fmt.Println("Event published successfully")
}

4. Implementing an Event Consumer

The consumer reads and processes events from Redis Streams.

package main

import (
    "context"
    "fmt"
    "log"
    "github.com/redis/go-redis/v9"
)

var ctx = context.Background()

func main() {
    client := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })

    for {
        res, err := client.XRead(ctx, &redis.XReadArgs{
            Streams: []string{"alerts", "$"},
            Count:   1,
            Block:   0,
        }).Result()

        if err != nil {
            log.Fatalf("Failed to read event: %v", err)
        }

        for _, stream := range res {
            for _, message := range stream.Messages {
                fmt.Printf("Processing event: %v\n", message.Values)
            }
        }
    }
}

Enhancements for Production

While this implementation is a simple demonstration, a production-ready version should include:

Error handling & retries: Implement exponential backoff for failures.

Consumer groups: Distribute workload across multiple consumers.

Monitoring & logging: Track event processing metrics.

Persistence & backups: Use disk persistence to prevent data loss.

Conclusion

Using Redis Streams and Golang, we can build a reliable event-driven system with durability, acknowledgment, and scalability. This lightweight alternative is great for learning and small-scale applications where high availability is required.

Have you built an event-driven system with Redis Streams? Let me know your thoughts and experiences!