Блог инженера

History is written by its contributors

Redis как Message Broker: когда он лучше RabbitMQ

2025-07-23 время чтения 6 мин Redis Ilya Brin

Redis часто воспринимают только как кеш. Но Redis - это полноценный message broker, который в некоторых сценариях работает лучше RabbitMQ, Kafka и других специализированных решений.

Разберём, когда Redis - правильный выбор, а когда лучше взять что-то другое.

Проблема выбора

У вас есть задача: нужно передавать сообщения между сервисами. Первая мысль - RabbitMQ или Kafka. Но у вас уже есть Redis для кеша. Зачем добавлять ещё одну систему?

Вопрос не в том, “можно ли использовать Redis”. Вопрос в том, “когда Redis - лучший выбор”.

Redis как Message Broker: возможности

1. Pub/Sub - простейший вариант

Классический publish-subscribe паттерн. Один публикует, многие подписываются.

// Publisher
func PublishEvent(client *redis.Client, channel string, message string) error {
    return client.Publish(context.Background(), channel, message).Err()
}

// Subscriber
func Subscribe(client *redis.Client, channel string) {
    pubsub := client.Subscribe(context.Background(), channel)
    defer pubsub.Close()
    
    ch := pubsub.Channel()
    for msg := range ch {
        fmt.Printf("Received: %s\n", msg.Payload)
    }
}

Плюсы:

  • Мгновенная доставка
  • Простота реализации
  • Нулевая задержка

Минусы:

  • Нет гарантии доставки
  • Если подписчик офлайн - сообщение потеряно
  • Нет персистентности

Когда использовать:

  • Уведомления в реальном времени
  • Инвалидация кеша
  • Live обновления UI
  • Координация между инстансами

2. Lists - простая очередь

Redis Lists работают как FIFO очередь. LPUSH добавляет, BRPOP забирает.

// Producer
func Enqueue(client *redis.Client, queue string, message string) error {
    return client.LPush(context.Background(), queue, message).Err()
}

// Consumer
func Consume(client *redis.Client, queue string) {
    for {
        result, err := client.BRPop(context.Background(), 0, queue).Result()
        if err != nil {
            continue
        }
        
        message := result[1]
        processMessage(message)
    }
}

Плюсы:

  • Гарантия доставки (пока Redis жив)
  • Порядок сохраняется
  • Блокирующее чтение (BRPOP)
  • Персистентность (если включена)

Минусы:

  • Нет подтверждения обработки
  • Если consumer упал - сообщение потеряно
  • Один consumer на очередь

Когда использовать:

  • Фоновые задачи
  • Email рассылки
  • Обработка изображений
  • Простые job queues

3. Streams - продвинутая очередь

Redis Streams - это как Kafka, но проще. Появились в Redis 5.0.

// Producer
func AddToStream(client *redis.Client, stream string, data map[string]interface{}) error {
    return client.XAdd(context.Background(), &redis.XAddArgs{
        Stream: stream,
        Values: data,
    }).Err()
}

// Consumer Group
func ConsumeStream(client *redis.Client, stream, group, consumer string) {
    for {
        streams, err := client.XReadGroup(context.Background(), &redis.XReadGroupArgs{
            Group:    group,
            Consumer: consumer,
            Streams:  []string{stream, ">"},
            Count:    10,
            Block:    0,
        }).Result()
        
        if err != nil {
            continue
        }
        
        for _, stream := range streams {
            for _, message := range stream.Messages {
                processMessage(message.Values)
                
                // Подтверждение обработки
                client.XAck(context.Background(), stream.Stream, group, message.ID)
            }
        }
    }
}

Плюсы:

  • Consumer groups (как в Kafka)
  • Подтверждение обработки (ACK)
  • Персистентность
  • Чтение с любой позиции
  • Несколько consumers
  • Pending messages tracking

Минусы:

  • Сложнее, чем Lists
  • Нужно управлять consumer groups
  • Больше памяти

Когда использовать:

  • Event sourcing
  • Audit logs
  • Activity streams
  • Когда нужна надёжность
  • Когда нужно несколько consumers

Redis vs RabbitMQ: сравнение

Производительность

Redis:

  • 100,000+ сообщений в секунду на одном инстансе
  • Латентность < 1ms
  • In-memory операции

RabbitMQ:

  • 20,000-50,000 сообщений в секунду
  • Латентность 1-5ms
  • Disk + memory

Вывод: Redis быстрее в 2-5 раз для простых сценариев.

Надёжность

Redis:

  • Персистентность опциональна (RDB/AOF)
  • При падении можно потерять последние секунды
  • Репликация асинхронная

RabbitMQ:

  • Персистентность по умолчанию
  • Подтверждения на каждом этапе
  • Кластеризация с синхронной репликацией

Вывод: RabbitMQ надёжнее для критичных данных.

Сложность

Redis:

  • Простая установка
  • Минимальная конфигурация
  • Понятный API

RabbitMQ:

  • Сложная установка
  • Много настроек
  • Erlang под капотом
  • Exchanges, queues, bindings

Вывод: Redis проще в разы.

Функциональность

Redis:

  • Pub/Sub
  • Lists
  • Streams
  • Sorted Sets для приоритетов

RabbitMQ:

  • Routing по паттернам
  • Dead letter queues
  • Message TTL
  • Priority queues
  • Delayed messages
  • Transactions

Вывод: RabbitMQ функциональнее.

Когда Redis лучше

1. Высокая скорость важнее надёжности

Уведомления в реальном времени, live обновления, координация между сервисами.

Пример: Инвалидация кеша при обновлении данных.

func InvalidateCache(client *redis.Client, key string) {
    // Удаляем из локального кеша
    localCache.Delete(key)
    
    // Уведомляем другие инстансы
    client.Publish(context.Background(), "cache:invalidate", key)
}

2. У вас уже есть Redis

Зачем добавлять RabbitMQ, если Redis уже работает? Меньше систем - меньше проблем.

Пример: Фоновые задачи в небольшом приложении.

3. Простые сценарии

Нет сложного роутинга, нет приоритетов, нет delayed messages.

Пример: Email очередь.

func SendEmailAsync(client *redis.Client, to, subject, body string) {
    email := map[string]interface{}{
        "to":      to,
        "subject": subject,
        "body":    body,
    }
    
    data, _ := json.Marshal(email)
    client.LPush(context.Background(), "emails", data)
}

4. Низкая латентность критична

Когда каждая миллисекунда важна.

Пример: Real-time аналитика, live dashboards.

5. Небольшой объём сообщений

До 100,000 сообщений в секунду Redis справляется отлично.

Когда RabbitMQ лучше

1. Критичные данные

Финансовые транзакции, заказы, платежи - нельзя потерять.

2. Сложный роутинг

Нужно отправлять сообщения в разные очереди по условиям.

3. Гарантии доставки

Нужны подтверждения на каждом этапе: publisher → broker → consumer.

4. Большой объём

Миллионы сообщений в день, нужна персистентность на диск.

5. Enterprise требования

Мониторинг, управление, плагины, интеграции.

Гибридный подход

Часто лучшее решение - использовать оба.

Redis для:

  • Быстрые уведомления
  • Инвалидация кеша
  • Real-time координация

RabbitMQ для:

  • Критичные задачи
  • Сложный роутинг
  • Долгоживущие очереди

Пример архитектуры:

User Action → API
         Redis Pub/Sub (инвалидация кеша)
         RabbitMQ (обработка заказа)
         Workers

Практические паттерны

1. Task Queue с Redis Lists

type Task struct {
    ID   string
    Type string
    Data map[string]interface{}
}

func EnqueueTask(client *redis.Client, task Task) error {
    data, _ := json.Marshal(task)
    return client.LPush(context.Background(), "tasks", data).Err()
}

func ProcessTasks(client *redis.Client) {
    for {
        result, err := client.BRPop(context.Background(), 0, "tasks").Result()
        if err != nil {
            continue
        }
        
        var task Task
        json.Unmarshal([]byte(result[1]), &task)
        
        switch task.Type {
        case "email":
            sendEmail(task.Data)
        case "image":
            processImage(task.Data)
        }
    }
}

2. Event Bus с Redis Pub/Sub

type EventBus struct {
    client *redis.Client
}

func (e *EventBus) Publish(event string, data interface{}) error {
    payload, _ := json.Marshal(data)
    return e.client.Publish(context.Background(), event, payload).Err()
}

func (e *EventBus) Subscribe(event string, handler func(data []byte)) {
    pubsub := e.client.Subscribe(context.Background(), event)
    defer pubsub.Close()
    
    ch := pubsub.Channel()
    for msg := range ch {
        handler([]byte(msg.Payload))
    }
}

3. Reliable Queue с Redis Streams

func ReliableQueue(client *redis.Client, stream, group string) {
    // Создать consumer group
    client.XGroupCreate(context.Background(), stream, group, "0")
    
    for {
        // Читать новые сообщения
        streams, _ := client.XReadGroup(context.Background(), &redis.XReadGroupArgs{
            Group:    group,
            Consumer: "worker-1",
            Streams:  []string{stream, ">"},
            Count:    10,
            Block:    time.Second,
        }).Result()
        
        for _, s := range streams {
            for _, msg := range s.Messages {
                if processMessage(msg.Values) {
                    // Подтвердить обработку
                    client.XAck(context.Background(), stream, group, msg.ID)
                }
            }
        }
        
        // Обработать pending messages (не подтверждённые)
        pending, _ := client.XPendingExt(context.Background(), &redis.XPendingExtArgs{
            Stream: stream,
            Group:  group,
            Start:  "-",
            End:    "+",
            Count:  10,
        }).Result()
        
        for _, p := range pending {
            if p.RetryCount > 3 {
                // Переместить в dead letter queue
                client.XDel(context.Background(), stream, p.ID)
            }
        }
    }
}

Мониторинг и метрики

Ключевые метрики для Redis

func GetQueueMetrics(client *redis.Client, queue string) map[string]int64 {
    return map[string]int64{
        "length":    client.LLen(context.Background(), queue).Val(),
        "consumers": client.PubSubNumSub(context.Background(), queue).Val()[queue],
    }
}

func GetStreamMetrics(client *redis.Client, stream, group string) map[string]interface{} {
    info, _ := client.XInfoGroups(context.Background(), stream).Result()
    
    metrics := make(map[string]interface{})
    for _, g := range info {
        if g.Name == group {
            metrics["pending"] = g.Pending
            metrics["consumers"] = g.Consumers
            metrics["lag"] = g.Lag
        }
    }
    
    return metrics
}

Заключение

Используйте Redis когда:

  • Скорость важнее надёжности
  • Простые сценарии
  • У вас уже есть Redis
  • Низкая латентность критична
  • Небольшой объём сообщений

Используйте RabbitMQ когда:

  • Критичные данные
  • Сложный роутинг
  • Нужны гарантии доставки
  • Большой объём
  • Enterprise требования

Лучшее решение: Часто это комбинация. Redis для быстрых операций, RabbitMQ для критичных.

Redis - это не замена специализированным message brokers. Это инструмент, который в правильных руках решает 80% задач проще и быстрее.

Не усложняйте архитектуру без необходимости. Если Redis справляется - используйте Redis.

comments powered by Disqus