Real-Time Backend

Redis Streams

Pub/Sub в Redis работает как радио: пока никто не слушает - сигнал теряется. Redis Streams - это уже магнитофон с перемоткой: каждое сообщение записано, его можно перечитать, и если обработчик упал - данные не потеряются.

  • **Платежный сервис:** заказ добавляется в стрим `orders`, три воркера-обработчика читают через Consumer Group - каждый платеж обрабатывается ровно один раз, даже если один воркер перезапустился.
  • **Аудит-лог действий пользователей:** все события пишутся в стрим, аналитический сервис читает их независимо от основного обработчика - оба получают полную картину без дублирования логики.
  • **IoT-телеметрия:** тысячи датчиков шлют показания, MAXLEN обрезает стрим до последних 24 часов, несколько потребителей параллельно агрегируют данные по разным метрикам.

Redis Streams

Redis Streams - это персистентный лог сообщений, встроенный прямо в Redis. Каждое сообщение получает уникальный ID вида `1699000000000-0` (timestamp-sequence), и лог никуда не исчезает после прочтения - в отличие от обычного Pub/Sub.

Команда `XADD` добавляет запись: поле `*` означает автогенерацию ID. `XREAD` читает с позиции - можно читать с начала, с конкретного ID или только новые (`>`). Это позволяет нескольким сервисам читать один и тот же поток независимо.

Redis Streams vs Kafka: Streams живут в памяти (опционально персистируются через RDB/AOF), не требуют отдельного кластера и управляются одной командой. Kafka рассчитана на терабайты логов и тысячи партиций. Streams выигрывают при объемах до ~50 млн сообщений в день и когда Redis уже есть в стеке.

  • `XADD key [MAXLEN ~ N] * field value ...` - добавить запись
  • `XREAD COUNT N BLOCK ms STREAMS key id` - читать записи начиная с id
  • `XRANGE key start end` - диапазон записей (- это начало, + конец)
  • `XLEN key` - количество записей в стриме
  • `XDEL key id` - удалить конкретную запись

Что происходит с сообщением в Redis Streams после того, как его прочитал один из подписчиков через XREAD?

Consumer Groups

Consumer Group - это именованная группа потребителей, которые делят один стрим между собой. Каждое сообщение достается ровно одному потребителю из группы - это позволяет горизонтально масштабировать обработку. Несколько групп могут читать один стрим независимо, каждая получает все сообщения.

Типичный паттерн - несколько воркеров в одной группе. Например, три инстанса payment-service запускают `XREADGROUP GROUP payment-processors consumer-{hostname}`. Redis автоматически распределяет сообщения между ними - нет нужды в Zookeeper или координаторе.

Разница от Kafka партиций: в Kafka нельзя иметь больше потребителей, чем партиций. В Redis Streams потребителей может быть сколько угодно - Redis сам балансирует. Но у Streams нет гарантии порядка между разными потребителями одной группы (в отличие от Kafka, где порядок гарантирован внутри партиции).

Три инстанса сервиса читают стрим `orders` через XREADGROUP из одной группы `processors`. Пришло 9 сообщений. Сколько сообщений получит каждый инстанс?

Pending Entries

Когда потребитель получает сообщение через XREADGROUP, Redis не считает его обработанным. Сообщение попадает в PEL - Pending Entry List. Там оно остается до явного `XACK`. Если воркер упал, не успев подтвердить, сообщение не потеряется - оно будет числиться как pending за этим потребителем.

Поле `delivery-count` в XPENDING показывает, сколько раз сообщение было доставлено. Если count > 3 - это poison message (обработчик падает на нем постоянно). Такие сообщения нужно перемещать в dead-letter stream и алертить, а не перепытываться бесконечно.

Воркер получил 10 сообщений через XREADGROUP, успешно обработал 7, потом упал. Что произойдет с оставшимися 3 сообщениями?

Stream Claim

XCLAIM и XAUTOCLAIM - команды для «перехвата» зависших сообщений. XCLAIM переназначает конкретный ID другому потребителю. XAUTOCLAIM (Redis 6.2+) сканирует весь PEL и забирает все сообщения старше указанного порога за один вызов - удобнее для автоматизации.

Полный production-цикл выглядит так: основные воркеры читают через `XREADGROUP ... >`, обрабатывают, делают `XACK`. Отдельный «janitor» процесс по расписанию вызывает `XAUTOCLAIM` с таймаутом в несколько минут, перехватывает зависшие сообщения и либо переобрабатывает их, либо отправляет в dead-letter стрим если `delivery-count` превысил лимит.

MAXLEN trimming - обязательная практика для production. Без ограничения стрим растет бесконечно и съедает память Redis. `XADD key MAXLEN ~ 100000 *` - тильда означает приближенную обрезку (быстрее, чем точная). Альтернатива - `XTRIM key MAXLEN ~ 100000` по расписанию, или хранить данные только за последние N часов через `XRANGE` + `XDEL`.

XACK автоматически удаляет сообщение из стрима

XACK только убирает сообщение из PEL (Pending Entry List) - подтверждает что потребитель обработал его. Само сообщение остается в стриме до XDEL или срабатывания MAXLEN.

PEL и стрим - две разные структуры. Стрим - это лог, PEL - список «взятых в работу» сообщений. XACK говорит «я закончил», но не «удали запись». Это позволяет другим группам или инструментам читать те же записи независимо.

Воркер `consumer-3` завис на одном сообщении уже 10 минут. XAUTOCLAIM вызван с min-idle-time 5 минут. Что произойдет с этим сообщением?

Итоги

  • **Redis Streams = персистентный лог:** XADD добавляет, XREAD читает - сообщения не исчезают после прочтения, в отличие от Pub/Sub.
  • **Consumer Groups = горизонтальное масштабирование:** каждое сообщение достается ровно одному потребителю группы; несколько групп получают все сообщения независимо.
  • **PEL + XACK = at-least-once гарантия:** сообщение числится pending до явного XACK; XAUTOCLAIM возвращает зависшие сообщения в работу при падении воркера.
  • **MAXLEN - обязателен в production:** без ограничения стрим растет бесконечно; `XADD key MAXLEN ~ N` держит только последние N записей без заметных потерь производительности.

Связанные темы

Redis Streams дополняют другие паттерны работы с очередями и логами:

  • Pub/Sub — Альтернатива без персистентности - сообщения теряются если нет подписчика; Streams решают эту проблему
  • BullMQ / Bull — Популярная Node.js очередь поверх Redis Lists; Streams дают больше контроля над группами и delivery-count
  • Kafka — Тот же паттерн лога + consumer groups, но для масштаба в терабайты; Streams - легкая альтернатива если Redis уже в стеке
  • Dead-letter Queue — Паттерн обработки poison messages из PEL - сообщения с delivery-count > N перемещаются в отдельный стрим для ручного разбора

Вопросы для размышления

  • Сервис читает стрим через XREADGROUP, но периодически падает посередине батча. Какой механизм гарантирует, что ни одно сообщение не будет потеряно, и что нужно реализовать для автоматического восстановления?
  • Два разных сервиса должны обрабатывать одни и те же события из стрима `events` - один строит аналитику, другой отправляет уведомления. Как организовать Consumer Groups чтобы каждый сервис получал все события независимо?
  • Стрим растет и через месяц занимает 8 GB памяти. Какие стратегии ограничения размера существуют, и как выбрать между MAXLEN по количеству записей и удалением по времени?

Связанные уроки

  • db-19-redis
Redis Streams

0

1

Войти