Очереди и журналы
Что это такое
Под брокерами, несущими асинхронную работу, стоят две разные модели. Очередь отдаёт каждое сообщение одному потребителю и считает дело сделанным, едва пришло подтверждение, — после этого сообщение исчезает. Журнал только дописывает сообщения в упорядоченную долговечную последовательность, а потребители читают её каждый со своей позиции; поэтому сообщение остаётся доступным и для повторного чтения — тем же потребителем или другим.
Почему это важно
От этого выбора зависит, что система сможет делать позже. Очередь — более простая мысленная модель, и она годится для работы, которая должна произойти ровно один раз на элемент: отправь письмо, спиши деньги с карты. Журнал хранит историю и позволяет нескольким независимым потребителям читать одни и те же события — именно поэтому потребителя можно добавить позже и воспроизвести для него пропущенное. Однако выбрать журнал для одноразовой задачи значит управлять позициями без всякой выгоды, а взять очередь там, где важна история, — потерять события.
Ключевые идеи
Очередь — это работа, которую нужно сделать; журнал — запись о том, что произошло
Сообщение очереди — инструкция, адресованная тому, кто свободен, и жизнь его кончается подтверждением. Запись журнала — факт, дописанный в последовательность, и живёт она до конца срока хранения. Чтобы понять, какая модель подходит, надёжнее прочитать предполагаемый смысл сообщения, чем сравнивать производительность.
очередь: [задача] -> один потребитель -> подтверждено -> исчезла
журнал: [событие0][событие1][событие2] ... хранится, у каждого потребителя своя позиция
Гарантии порядка — на раздел, а не глобальные
Очередь с несколькими потребителями доставляет каждое сообщение один раз, но ничего не обещает о порядке, который видят разные потребители. Журнал гарантирует порядок внутри раздела: сообщения с одним ключом упорядочены друг относительно друга, а с разными — нет. Значит, там, где порядок важен, ключ надо выбирать так, чтобы связанные сообщения попадали в один раздел.
Позиция потребителя — состояние, которым нужно управлять
В журнале каждая группа потребителей отслеживает, докуда она дочитала. Если зафиксировать позицию слишком рано, при падении потребителя сообщения потеряются; если слишком поздно — они будут обработаны заново. Обычно позицию фиксируют после того, как работа сделана; значит, доставка дважды возможна, и обработчик обязан быть идемпотентным.
На практике доставка at-least-once, поэтому обработчики должны быть повторяемыми
Если брокер гарантирует отсутствие потерь, он доставит сообщение заново, когда подтверждение не пришло, а потерянное подтверждение ничем не отличается от потерянного сообщения. Следствие: обработчик может увидеть одно и то же сообщение дважды и всё равно должен дать одинаковый результат. Поэтому, прежде чем что-то менять — или вместе с изменением, — запишите, что работа сделана, и возьмите ключом идентификатор сообщения. Доставка exactly-once — свойство всего конвейера, а не настройка брокера.
Веерная рассылка естественна в журнале и требует механизма в очереди
В журнале несколько независимых потребителей читают одни и те же события по умолчанию: у каждого своя позиция. В очереди, чтобы доставить одно сообщение нескольким потребителям, нужен топик с подписками или явно настроенный обмен, и семантика при этом другая. Там, где на одно событие должны реагировать несколько систем, именно это и решает дело.
Срок хранения — параметр проектирования, а не деталь уборки
От того, сколько сообщения хранятся, зависит, сможет ли новый потребитель воспроизвести историю и удастся ли восстановиться после сбоя, отмотав позицию назад. Если срок хранения короче, чем время до обнаружения ошибки, данных для её исправления уже не будет. Решение это осознанное — и в той, и в другой модели.
Практика
Выберите модель для двух нагрузок
Возьмите задачу, которая должна выполняться один раз на заказ, и событие, на которое должны отреагировать три системы, — и выберите для каждой очередь или журнал, объяснив почему.
Готово, когда: для каждого выбора названо, требуется ли повторное чтение того же элемента другим потребителем.
Прочитайте одно событие дважды
Опубликуйте сообщение, обработайте его, а затем обработайте снова с сохранённой позиции, ничего не публикуя заново.
Готово, когда: одно и то же сообщение обработано дважды, и вы можете назвать запись, которая сделала вторую обработку безвредной.
Частые ошибки
- Очередь там, где будущему потребителю понадобится история — события потеряны; исправить: используйте журнал, когда запись сама по себе имеет ценность.
- Ожидание глобального порядка при нескольких потребителях — гарантирован только порядок внутри раздела; исправить: выбирайте ключ так, чтобы связанные сообщения попадали в один раздел.
- Фиксация позиции до того, как работа сделана — сбой теряет сообщения; исправить: фиксируйте после обработки и сделайте обработчик идемпотентным.
- Ожидание доставки exactly-once от брокера — повторная доставка нормальна; исправить: сделайте обработчик безопасным для повторного запуска.
- Срок хранения без учёта восстановления — улики, нужные для исправления ошибки, истекли; исправить: выбирайте срок хранения из окна восстановления.
Что читать дальше
- Очереди сообщений (Kafka / RabbitMQ) (05-backend-data/17-message-queues-kafka-rabbitmq) — брокеры, которыми реализованы эти две модели, поэтому читайте эту запись первой.
- Повторные попытки, порядок и обратное давление (05-backend-data/17-message-queues-kafka-rabbitmq/02-retries-ordering-and-back-pressure) — механика, которая вытекает из этого выбора.
- Микросервисы (05-backend-data/12-microservices) — архитектура, которой эта асинхронность обычно служит.