Очереди / Брокеры сообщений
Термины
- Гарантия доставки - дошло ли сообщение до получателя
- Надежность - сохранность данных/конфига после падения
- Обычно подразумевается сохранение на диск и репликация
- Сообщение - байты (строка/json/что угодно) + метадата (заголовки/приоритет)
Модели Брокеров
- Push-модель - сообщения отправляются получателю
- Pull-модель - получатель сам ходит за сообщениями
Гарантии доставки
- At-most-once delivery - 0-1
- Отравили и забыли - нет гарантии успешной обработки
- Быстрее, больше пропускная способность
- At-least-once delivery - 1+
- Отправили сообщение, но подтверждение не получили, значит отправляем еще раз
- Важно обрабатывать такой кейс, напр. с помощью идемпотентности
- Exactly-once delivery - 1
- Аналог транзакций
Rabbit MQ
Архитектура
- Паблишер > Exchange > Очередь > Консумер
- Паблишер - отравляет/создает сообщения
- Exchange - роутер сообщений от паблишеров в очереди
- Очередь - хранит сообщения + отправляет Консумерам
- Консумер - обрабатывает сообщение
- Очередность - FIFO - для 1 паблишера - 1 консумера
Exchange
- Сущности роутинга
- Routing Key - ключик (просто строка), на основе которой роутим
- Binding - связь между Exchange и Exchange/Queue
- Виды Exchange роутинга
- Fanout - во все Очереди и Exchangeи по биндингу
- Механизм Pub/Sub
- Routing Key игнорируется
- Direct - полное совпадение Routing Key
- Topic - direct + wildcard
- Header - по заголовкам
- Consistent Hashing - хеширует ключ и отправляет только в одну очередь
- Fanout - во все Очереди и Exchangeи по биндингу
- Dead letter exchange - Exchange с недоставленными сообщениями
- Если сообщение не доставлено / очередь переполнена, то можно вернуть сообщение из очереди обратно в спец эксченж
Доставка / гарантии / надежность
- At-least-once, At-most-once
- Паблишер отправляет сообщение > rabbit что-то поделал с сообщением > отправляет паблишеру команду
ack
- Если паблишер не получил
ack
(кейс 500), сообщение отправляется еще раз, предварительно возвращая сообщение в очередь - Очередь >-сообщение-> Консумер ⇒
- Консумер >-ack-> Очередь ⇒
- Сообщение удаляется из очереди
- Иначе повторная доставка / доставка другому консумеру
- Консумер >-ack-> Очередь ⇒
- Можно из паблишера сразу в очередь - дефолтный эксченж
- Надежность: Можно включить флаг durable, тогда конфиг будет сохраняться
Масштабирование
- 1 очередь - несколько консумеров ⇒
- 1 сообщение - 1 консумеру
- несколько сообщений равномерно распределяется по всем консумерам
- При добавлении новых консумеров никаких пауз нет
- Можно ждать ack, тогда будет медленее, можно не ждать, тогда будет быстро
- QoS - лимит на кол-во неподтвержденных сообщений
Kafka
Архитектура
- Распределенный реплицируемый лог
- Распределенный - на разных машинах - партиции
- Реплицируемый - копирует сообщения - реплики
- Лог - упорядоченный журнал событий = файл со строками
-
Продусер > Топик / Партиция <-Оффсет-< Consumer (Group)
-
Продусер всегда пишет в конец партишена
Топик / Партиция
- Топик - набор событий одного или нескольких типов
- Важно не делать много топиков
- Топик - набор партиций
- Важно писать равномерно по всем партициям
- Партиционирование - выбор партиции
- key > hash > %len(partitions) > partition
- Это происходит на стороне producer ⇒ можно свой алго написать
- Важно определить сколько партиций надо
Consumer / Offset
- Consumer Group = логический Consumer
- Консумер читает партицию с последнего места чтения
- Можно читать, то что уже прочитано - тайм-тревел
- Оффсет может храниться у консумера и у Кафки (если консумер не хранит оффсет)
- key - аналог routing-key в rabbit
- 1 партиция = 1 логический консумер
- Т.е можно 2 партиции - 1 консумер, нельзя - 1 партиция - 2 консумера
- Важно что логический - если консумеры будут разными приложениями, то чтение из одной парции это ок
- Group Coordinator - kafka-брокер, который следит за группой
- Group Leader - консумер, который распределяет консумеров по партициям
- Можно управлять алго
- commit offset - команда фиксации нового оффсета
- Консумер > Kafka >
_consumer_offsets
Topic - Нужно для уведомления kafka
- Иначе, если консумер сдох, то kafka будет отдавать сообщения с послндего сохраненного оффсета
- Консумер > Kafka >
Масштабирование / Перебалансировка
- При добавлении нового консумера чтение сообщений приостанавливается до конца обработки текущих сообщений, а затем партиции распределяются заново
Репликация
- Тупа копирование партиций на несколько нод
- Параметр - factor - желательно 3+
Доставка и Гарантии
- Последовательность: Если сообщение B пришло после А, значит Offset B > Offset A
- Сообщение закоммичено, когда доставлено во все реплики = sync replica
- Можно отключить
- Сообщения не будут потеряны если жива хотя бы одна реплика
- Параметр acks
- acks = 0 - “выстрелил и забыл”
- acks = 1 - хотя бы одна реплика (лидер) сохранила на диск
- acks = all - все реплики получили сообщение
Pull-модель
- консумеры сами запрашивают сообщения у Kafka
- Kafka отдает сообщения батчами (пачками)
- latency хуже, потому что Kafka ждет мб еще сообщения придут
- Добавление новых партиций ломает очередность
Хранение
- Если хранить вечно не надо (место на диске не резиновое), то можно настроить время жизни / макс. размер
Обработка ошибок
- Ждем
- Но это плохо
- Таймауты / Ретраи / Алертинг
- Заглушка
- Сохранение частичной инфы в бд, обработаем потом, в другом событии
- Переместиться во времени
- Переложить сообщение
- Идемпотентность
Что выбирать?
Kafka
- Высокая пропускная способность, но хуже latency
- Хранение сообщений + возможность перечитать сообщения
- Репликация, но и сложнее настройка
Rabbit
- Лучше latency
- Роутинг
Redis
- Скорость, простота
- Нет гарантий доставки
- Нет роутинга