Redis как Message Broker: когда он лучше RabbitMQ
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.