SKIP TO CONTENT
HOME/LEARN/BACKEND
WEBSOCKETREDIS

REAL-TIME COMMUNICATION

WebSocket patterns, event-driven architectures, and scaling real-time features.

WebSocket vs SSE vs Polling

WebSocket: bidirectional, persistent — use for chat, collaborative editing. SSE: server-to-client only — use for notifications. For the community forum, WebSocket was the clear choice.

WebSocket Hub Pattern in Go

A central hub manages all connections, rooms, and message broadcasting. Each connection runs in its own goroutine with channels for communication.

go
type Hub struct {
    rooms      map[string]map[*Client]bool  // roomID -> clients
    broadcast  chan *Message
    register   chan *Client
    unregister chan *Client
    mu         sync.RWMutex
}

func (h *Hub) Run() {
    for {
        select {
        case client := <-h.register:
            h.mu.Lock()
            if _, ok := h.rooms[client.RoomID]; !ok {
                h.rooms[client.RoomID] = make(map[*Client]bool)
            }
            h.rooms[client.RoomID][client] = true
            h.mu.Unlock()

        case client := <-h.unregister:
            h.mu.Lock()
            if clients, ok := h.rooms[client.RoomID]; ok {
                delete(clients, client)
                close(client.Send)
                if len(clients) == 0 { delete(h.rooms, client.RoomID) }
            }
            h.mu.Unlock()

        case msg := <-h.broadcast:
            h.mu.RLock()
            if clients, ok := h.rooms[msg.RoomID]; ok {
                for client := range clients {
                    select {
                    case client.Send <- msg.Data:
                    default:
                        close(client.Send)
                        delete(clients, client)
                    }
                }
            }
            h.mu.RUnlock()
        }
    }
}

Scaling with Redis Pub/Sub

Single-server WebSocket breaks at scale. Redis Pub/Sub bridges multiple instances — publish on one server, all servers receive and broadcast to their connected clients.

go
// Subscribe to Redis and broadcast to local WebSocket clients
func (h *Hub) SubscribeToRedis(rdb *redis.Client) {
    pubsub := rdb.Subscribe(ctx, "chat:*")
    ch := pubsub.Channel()

    for msg := range ch {
        // msg.Channel = "chat:room123"
        roomID := strings.TrimPrefix(msg.Channel, "chat:")
        h.broadcast <- &Message{
            RoomID: roomID,
            Data:   []byte(msg.Payload),
        }
    }
}

// Publish message — goes to all server instances
func (h *Hub) PublishMessage(rdb *redis.Client, roomID string, data []byte) {
    rdb.Publish(ctx, "chat:"+roomID, data)
}