Когда следует использовать Redis в качестве брокера сообщений?
Узнайте об идеальных сценариях использования Redis в качестве брокера сообщений с помощью двух его основных функций: Pub/Sub и Streams. Это подробное руководство описывает преимущества производительности, низкую задержку и инфраструктурные выгоды обмена сообщениями в Redis. Изучите ключевые различия между эфемерным Pub/Sub и долговечными Streams, поймите их ограничения по сравнению с выделенными брокерами, такими как Kafka, и найдите практические примеры использования — от простой инвалидации кэша до надежных легковесных очередей задач — чтобы помочь вам выбрать правильный инструмент для ваших потребностей в асинхронной коммуникации.
Когда следует использовать Redis в качестве брокера сообщений?
Redis может быть хорошим брокером сообщений, когда задача небольшая, быстрая и близка к данным, которые вы уже храните в Redis. Он также может оказаться неподходящим инструментом, если вам нужны строгие гарантии доставки, длительное хранение, функции маршрутизации или брокер, который продолжает стабильно работать, когда история сообщений значительно превышает объем памяти.
Решение становится проще, если перестать спрашивать «Может ли Redis работать с сообщениями?» и задать вопрос «Какая примитивная модель обмена сообщениями Redis соответствует допустимому для меня режиму отказа?»
Redis предоставляет несколько шаблонов, но для этого вопроса важны два:
- Pub/Sub для прямых трансляций.
- Streams для долговечной, похожей на журнал обработки сообщений с группами потребителей.
Издалека они кажутся похожими. Операционно они не похожи.
Используйте Pub/Sub, когда потеря сообщения допустима
Redis Pub/Sub — это прямая трансляция. Издатель отправляет сообщение в канал. Подключенные подписчики получают сообщение. Redis не хранит это сообщение для отключенных подписчиков, и нет встроенного подтверждения.
Это идеально для некоторых задач:
SUBSCRIBE cache:invalidations
PUBLISH cache:invalidations 'product:123'
Если один экземпляр приложения пропустит эту инвалидацию из-за перезапуска в неподходящий момент, мир не должен рухнуть. Локальный кэш должен иметь TTL, проверки версий или другой способ восстановления. Pub/Sub — это путь уведомлений, а не источник истины.
Хорошие случаи использования Pub/Sub:
- Инвалидация кэша между экземплярами приложения.
- Обновления живого интерфейса, где клиенты заботятся только о текущем состоянии.
- Сигналы присутствия, такие как «пользователь печатает» или «изменение пульса рабочего».
- Легковесные уведомления о развертывании или изменении конфигурации.
- События типа «fan-out», где потеря сообщений допустима.
Плохие случаи использования Pub/Sub:
- Обработка платежей.
- Задачи электронной почты, которые должны быть отправлены в конечном итоге.
- Обновления инвентаря, которые нельзя пропустить.
- Журналы аудита.
- Все, где отключенный потребитель должен наверстать упущенное позже.
Pub/Sub быстр, потому что делает меньше. Это компромисс.
Используйте Streams, когда потребителям нужно наверстывать упущенное
Redis Streams хранят записи в структуре данных потока:
XADD orders:events * order_id 42 status paid
Потребители могут читать с определенной позиции:
XREAD COUNT 10 STREAMS orders:events 0
Группы потребителей позволяют нескольким рабочим делить работу:
XGROUP CREATE orders:events order-workers 0 MKSTREAM
XREADGROUP GROUP order-workers worker-1 COUNT 10 STREAMS orders:events >
XACK orders:events order-workers 1740000000000-0
С группами потребителей сообщение, доставленное рабочему, остается в списке ожидающих записей до подтверждения с помощью XACK. Если рабочий умирает после чтения, но до подтверждения, другой рабочий может проверить и забрать ожидающую работу. Это дает вам обработку «как минимум один раз» при правильной настройке потребителя.
«Как минимум один раз» означает возможные дубликаты. Ваш рабочий должен быть идемпотентным. Например, рабочий электронной почты должен записать, что email_job_id=abc123 было отправлено, прежде чем пытаться отправить снова. Рабочий заказов должен избегать двойного списания, если он видит одну и ту же запись потока дважды.
Хорошие случаи использования Streams:
- Легковесные фоновые задачи.
- Внутренние события сервиса, которые нужно воспроизвести после коротких сбоев.
- Небольшие и средние журналы событий.
- Пулы рабочих, где каждая задача должна быть обработана одним рабочим в группе.
- Ленты активности или журналы изменений состояний с ограниченным сроком хранения.
Streams не бесплатны. Записи живут в памяти Redis, если не обрезаются или не истекают по вашему дизайну. Если вы никогда не обрезаете активный поток, он станет вашим следующим инцидентом с памятью.
Используйте обрезку:
XADD orders:events MAXLEN ~ 100000 * order_id 42 status paid
XTRIM orders:events MAXLEN ~ 100000
Приблизительная обрезка с ~ обычно дешевле точной. Выбирайте срок хранения исходя из потребностей восстановления, а не надежды.
Списки Redis все еще полезны для простых очередей
До появления Streams многие очереди Redis использовали списки:
LPUSH jobs:email '{"to":"[email protected]"}'
BRPOP jobs:email 5
Списки все еще подходят для очень простых очередей, особенно когда нужно блокирующее поведение pop и не нужны группы потребителей или история. Ограничение — восстановление. Если рабочий извлекает задачу и падает до завершения, задача теряется, если не добавить дополнительный учет.
Существуют шаблоны с использованием BRPOPLPUSH или BLMOVE для перемещения задач в список обработки, а затем удаления после успеха. Эти шаблоны могут работать, но как только вам понадобится отслеживание ожидающих, повторные попытки и несколько потребителей, Streams обычно дают более четкую отправную точку.
Выбирайте Redis, когда простота важнее функций брокера
Обмен сообщениями в Redis привлекателен, когда Redis уже часть вашего стека, а нагрузка скромная. Вы избегаете эксплуатации другой распределенной системы. Разработчики уже понимают клиенты Redis, мониторинг, учетные данные и пути развертывания.
Это веская причина. Операционная простота имеет реальную ценность.
Redis также имеет очень низкую задержку. Если ваше приложение и Redis находятся в одном регионе или частной сети, публикация небольшого уведомления обычно дешева и быстра. Для инвалидации кэша или обновлений статуса в реальном времени более тяжелый брокер может быть излишним.
Redis также позволяет аккуратно комбинировать изменения состояния и сообщения. Скрипт Lua или транзакция могут обновить ключ и добавить запись в поток одной операцией на стороне Redis. Это может быть полезно для небольших систем, где Redis является центральным хранилищем состояния.
Загвоздка в том, что Redis не должен стать вашим случайным брокером для всего. Если каждый сервис начнет добавлять высокообъемные потоки без плана хранения, «простой» выбор превратится в перегруженное хранилище журналов в памяти.
Выбирайте выделенный брокер, когда обработка отказов является продуктом
Kafka, RabbitMQ, Pulsar, NATS JetStream и облачные очереди существуют, потому что обмен сообщениями быстро усложняется.
Используйте выделенный брокер, когда вам нужны такие функции, как:
- Длительное хранение, измеряемое неделями, месяцами или годами.
- История сообщений, значительно превышающая объем памяти.
- Очереди недоставленных сообщений и политики повторных попыток, встроенные в брокер.
- Отложенная доставка, приоритеты, ключи маршрутизации, обменники или разделение тем.
- Шаблоны кросс-региональной репликации, разработанные для обмена сообщениями.
- Множество независимых групп потребителей, воспроизводящих одну и ту же историю событий.
- Более мощные инструменты для отслеживания задержек, смещений, ребалансировки и аудита.
Kafka обычно лучше подходит для высокообъемных конвейеров событий и воспроизводимых журналов. RabbitMQ часто лучше подходит для сложной маршрутизации, подтверждений и очередей задач. Облачные очереди часто лучше, когда вам нужно управляемое хранение и простые операционные границы.
Redis Streams могут справляться с полезными производственными нагрузками, но это все еще Redis. Его данные ориентированы на память, его настройки персистентности должны быть поняты, а его функции брокера намеренно меньше, чем у выделенных систем.
Конкретный способ принятия решения
Задайте эти вопросы перед выбором Redis:
- Может ли потребитель пропустить сообщение без потери данных?
- Нужно ли отключенному потребителю наверстывать упущенное?
- Как долго должны храниться сообщения?
- Могут ли хранимые данные сообщений комфортно поместиться в памяти Redis?
- Обрабатывают ли рабочие дублирующиеся сообщения безопасно?
- Нужны ли вам очереди недоставленных сообщений, отложенные повторные попытки, приоритеты или правила маршрутизации?
- Будет ли этот трафик мешать кэшированию или сессиям Redis?
Если потеря сообщений допустима, Pub/Sub может быть достаточно.
Если потребителям нужно наверстывать упущенное, а срок хранения ограничен, Streams может быть достаточно.
Если данные сообщений требуют длительного хранения, воспроизведения многими командами, сложного поведения брокера или строгого операционного разделения, используйте выделенный брокер.
Пример: инвалидация кэша
Приложение хранит страницы продуктов в локальной памяти процесса и Redis. Когда продукт изменяется, сервис администрирования публикует:
PUBLISH cache:invalidate product:123
Каждый экземпляр веб-сервера, подписанный на cache:invalidate, удаляет свою локальную копию. Если один экземпляр пропустит сообщение, его локальная запись все еще имеет пятиминутный TTL, и он также проверяет поле версии продукта при следующем запросе. Pub/Sub подходит, потому что есть путь восстановления.
Использование Kafka здесь, вероятно, добавило бы больше операционной нагрузки, чем пользы.
Пример: фоновые задачи электронной почты
Пользователь регистрируется, и вам нужно отправить приветственное письмо. Если рабочий недоступен в течение минуты, задача все равно должна быть отправлена позже. Pub/Sub плохо подходит.
Redis Stream может работать:
XADD email:jobs MAXLEN ~ 100000 * job_id abc123 type welcome user_id 42
Рабочие читают через группу потребителей, отправляют письмо, записывают job_id как выполненный и вызывают XACK. Монитор проверяет ожидающие задачи и забирает старые. Это разумно для скромной внутренней очереди.
Если доставка электронной почты становится масштабной, требуются отложенные повторные попытки, обработка недоставленных сообщений, ограничения скорости для каждого клиента и богатые операционные панели, выделенная очередь начинает выглядеть лучше.
Пример: события аудита
События аудита обычно требуют долговечности, поиска, хранения, а иногда и юридической обработки или соответствия требованиям. Redis Streams могут помочь как короткий буфер, но Redis не должен быть конечным хранилищем аудита. Используйте долговечный журнал, базу данных, конвейер объектного хранения или управляемый сервис событий, предназначенный для хранения и просмотра.
Операционные заметки, если вы выбираете Redis
Для Pub/Sub:
- Настройте
client-output-buffer-limit pubsub. - Используйте выделенные соединения подписчиков.
- Реализуйте поведение переподключения и повторной подписки.
- Относитесь к сообщениям как к подсказкам, а не к долговечным фактам.
Для Streams:
- Установите политику хранения с помощью
MAXLEN,MINIDили явной обрезки. - Мониторьте ожидающие записи.
- Сделайте потребителей идемпотентными.
- Используйте
XACKтолько после успешного выполнения работы. - Спланируйте, как застрявшие сообщения будут забираться и повторяться.
- Следите за памятью, персистентностью и задержкой репликации.
Redis — хороший брокер сообщений, когда вы выбираете ту часть Redis, которая соответствует задаче. Pub/Sub — это живой сигнал. Streams — это ограниченный долговечный журнал. Ни то, ни другое не следует выбирать только потому, что Redis уже работает, но оба могут быть самым простым правильным ответом, когда их модель отказа соответствует вашему приложению.
Неудобная середина
Многие команды оказываются посередине: Pub/Sub слишком ненадежен, Kafka кажется слишком большой, а RabbitMQ — еще одной системой для эксплуатации. Redis Streams могут быть хорошим ответом, но только если относиться к ним как к реальной очереди, а не как к волшебному списку.
Здоровый дизайн Streams включает ответственность за следующие детали:
- Кто создает поток и группу потребителей?
- Сколько потребителей ожидается?
- Каков максимальный возраст ожидающих записей до того, как сообщение будет забрано?
- Что происходит после повторного сбоя?
- Сколько истории потока сохраняется?
- Какая панель или оповещение показывает растущую задержку?
Без этих ответов Streams могут тихо выйти из строя. Рабочий может читать сообщения и падать до XACK, оставляя записи в ожидании навсегда. Другой рабочий может никогда их не забрать. Длина потока может продолжать расти, потому что никто не настроил обрезку. Память Redis растет, но команда приложения думает «очередь долговечна», поэтому они не замечают, пока экземпляр не окажется под давлением.
Простой рабочий обычно должен выполнять такой цикл:
читать небольшую партию
обрабатывать каждое сообщение идемпотентно
подтверждать только успешные сообщения
периодически проверять ожидающие сообщения
забирать устаревшие ожидающие сообщения
обрезать в соответствии с политикой хранения
Это больше работы, чем Pub/Sub, и в этом суть. Долговечность всегда перемещает сложность куда-то. Redis Streams сохраняет сторону брокера довольно маленькой, но приложение все равно отвечает за повторные попытки, обработку недоставленных сообщений и идемпотентность.
Если никто в команде не хочет отвечать за эти детали, управляемая очередь может быть дешевле в долгосрочной перспективе, даже если в первый день она кажется тяжелее. Лучший брокер — не самый быстрый в бенчмарке. Это тот, чье поведение при сбое ваша команда может эксплуатировать в 3 часа ночи без догадок.