что такое брокер сообщений
Понимание брокеров сообщений. Изучение механики обмена сообщениями посредством ActiveMQ и Kafka. Глава 1
Начал перевод небольшой книги:
«Understanding Message Brokers«,
автор: Jakub Korab, издательство: O’Reilly Media, Inc., дата издания: June 2017, ISBN: 9781492049296.
Буду выкладывать законченные главы по мере перевода.
ГЛАВА 1
Введение
Межсистемный обмен сообщениями — это одна из наименее понимаемых областей ИТ. Как разработчик или архитектор, вы можете быть хорошо знакомы с различными фреймфорками и базами данных. Однако, вполне вероятно, что у вас есть только мимолетное знакомство с тем, как работают технологии обмена сообщениями, основанные на брокере. Если вы так себя и чувствуете, не волнуйтесь, вы в хорошей компании.
Люди обычно контактируют с инфраструктурой обмена сообщениями очень ограниченно. Нередко подключаются к системе, созданной давным-давно, или загружают дистрибутив из интернета, устанавливают его в ПРОМ и начинают писать под него код. После запуска инфраструктуры в ПРОМ, результаты могут быть неоднозначными: потеря сообщений при сбоях, отправка не работает так, как вы ожидали, или брокеры «подвешивают» ваших продьюсеров или не отправляют сообщения вашим потребителям.
Распространенный сценарий, когда ваш код обмена сообщениями работает отлично, до поры до времени. Пока не перестает работать. Этот период усыпляет бдительность и дает ложное чувство безопасности, что приводит к еще большему коду, основанному на ложных представлениях о фундаментальном поведении технологии. Когда что-то начинает идти не так, вы сталкиваетесь с неудобной истиной: что вы действительно не поняли базовое поведение продукта или компромиссы, выбранные авторами, такие как, производительность против надежности, или транзакционность против горизонтальной масштабируемости.
Без глубокого понимания того, как работают брокеры, люди делают, казалось бы, разумные утверждения об их системах обмена сообщениями, такие как:
Эта книга научит вас рассуждать о системах обмена сообщениями, основанных на брокерах, сравнивая и противопоставляя две популярные технологии брокеров: Apache ActiveMQ и Apache Kafka. Здесь будут изложены примеры использования и стимулы разработки, которые привели к тому, что их разработчики использовали совершенно разные подходы к одной и той же области — обмену сообщениями между системами с промежуточным брокером. Мы рассмотрим эти технологии с нуля и выделим влияние различных вариантов дизайна на этом пути. Вы получите глубокое понимание обоих продуктов, понимание того, как их следует и не следует использовать, и понимание того, на что следует обращать внимание при рассмотрении других технологий обмена сообщениями в будущем.
Прежде чем мы начнем, давайте пройдемся по основам.
Что такое Система обмена сообщениями и зачем она нужна
Чтобы два приложения могли общаться друг с другом, они должны сначала определить интерфейс. Определение этого интерфейса включает выбор транспорта или протокола, такого как HTTP, MQTT или SMTP и согласование форматов сообщений, которыми будут обмениваться системы. Это может быть строгий процесс, такой как определение схемы XML с требованиями к затратам на полезную нагрузку (payload) сообщения, или это может быть гораздо менее формально, например, соглашение между двумя разработчиками о том, что некоторая часть HTTP-запроса будет содержать идентификатор клиента.
Пока формат сообщений и порядок их отправки между системами согласованы, они смогут взаимодействовать друг с другом, не заботясь о реализации другой системы. Внутренности этих систем, такие как язык программирования или использованный фреймфорк, могут со временем меняться. До тех пор, пока поддерживается сам контракт, взаимодействие может продолжаться без изменений с другой стороны. Эти две системы эффективно расцеплены (разделены) этим интерфейсом.
Системы обмена сообщениями, как правило, предусматривают участие посредника между двумя системами, которые взаимодействуют для дальнейшего расцепления (разделения) отправителя от получателя или получателей. При этом система обмена сообщениями позволяет отправителю отправить сообщение, не зная, где находится получатель, активен ли он или сколько их экземпляров.
Рассмотрим пару аналогий разновидностей проблем, которые решает система обмена сообщениями, и введем некоторые основные термины.
Point-to-Point
Александра идет на почту, чтобы отправить Адаму посылку. Она подходит к окошку и вручает сотруднику посылку. Сотрудник забирает посылку и выдает Александре квитанцию. Адаму не нужно быть дома в момент отправки посылки. Александра уверена, что посылка будет доставлена Адаму в какой-то момент в будущем и может продолжать заниматься своими делами. Позже в какой-то момент Адам получает посылку.
Это пример модели обмена сообщениями точка-точка. Почтовое отделение здесь действует как механизм распределения посылок, гарантируя, что каждая посылка будет доставлена один раз. Использование почтового отделения отделяет акт отправки посылки от доставки посылки.
В классических системах обмена сообщениями модель «точка-точка» реализуется через очереди. Очередь действует, как буфер FIFO (первый вошел, первый вышел), на который может подписаться один или несколько потребителей. Каждое сообщение доставляется только одному из подписанных потребителей. Очереди обычно пытаются справедливо распределять сообщения между потребителями. Только один потребитель получит данное сообщение.
К очередям применяется термин «надежные» («durable»). Надежность — это свойство сервиса, которое гарантирует, что система обмена сообщениями будет сохранять сообщения при отсутствии активных подписчиков до тех пор, пока потребитель не подпишется на очередь для доставки сообщений.
Надежность часто путают с персистентностью и, хотя эти два термина взаимозаменяемы, они выполняют разные функции. Персистентность определяет, записывает ли сообщение система обмена сообщениями в какого-либо рода хранилище между получением и отправкой его потребителю. Сообщения, отправляемые в очередь, могут быть или не быть персистентными.
Обмен сообщениями типа «Точка-точка» используется, когда вариант использования требует однократного действия с сообщением. В качестве примера можно привести внесение средств на счет или выполнение заказа на доставку. Мы обсудим позже, почему система обмена сообщениями сама по себе неспособна обеспечить однократную доставку и почему очереди могут в лучшем случае обеспечить гарантию доставки хотя бы один раз.
Издатель-Подписчик
Габриэлла набирает номер конференции. Пока она подключена к конференции, она слышит все, что говорит спикер, вместе с остальными участниками вызова. Когда она отключается, она пропускает то, что сказано. При повторном подключении она продолжает слышать, что говорят.
Это пример модели обмена сообщениями публикация-подписка. Конференц-связь выступает, как широковещательный механизм. Говорящий человек не заботится о том, сколько людей в настоящее время присоединились к звонку — система гарантирует, что любой подключившийся в настоящий момент услышит, что говорится.
В классических системах обмена сообщениями модель обмена сообщениями «публикация-подписка» реализуется через топики. Топик предоставляет такой же способ широковещания, как и механизм конференц-связи. Когда сообщение отправляется в топик, оно распределяется по всем подписанным пользователям.
Топики обычно ненадежные (nondurable). Как и слушатель, который не слышит, что говорится на конференц-звонке, когда слушатель отключается, подписчики топика пропускают любые сообщения, которые отправляются в тот момент, когда они находятся в автономном режиме. По этой причине можно сказать, что топики предоставляют гарантию доставки не более одного раза для каждого потребителя.
Обмен сообщениями типа «публикация-подписка» обычно используется, когда сообщения носят информационный характер, и потеря одного сообщения — не особо значима. Например, топик может передавать показания температуры от группы датчиков один раз в секунду. Система, которая интересуется текущей температурой и которая подписывается на топик, не будет переживать, если она пропустит сообщение — другое поступит в ближайшее время.
Гибридные модели
Веб-сайт магазина помещает сообщения о заказах в «очередь сообщений». Основным потребителем этих сообщений является исполнительная система. Кроме того, система аудита должна иметь копии этих сообщений о заказах для последующего отслеживания. Обе системы не могут пропускать сообщения, даже если сами системы в течение некоторого времени недоступны. Веб-сайт не должен знать о других системах.
Сценарии использования часто требуют совмещения моделей обмена сообщениями «публикация-подписка» и «точка-точка», например, когда нескольким системам требуется копия сообщения, и для предотвращения потери сообщения требуется как надежность, так и персистентность.
В этих случаях требуется адресат (destination) (общий термин для очередей и топиков), который распределяет сообщения в основном как топик, так, что каждое сообщение отправляется в отдельную систему, заинтересованную в этих сообщениях, но и также в которой каждая система может определить несколько потребителей, которые получают входящие сообщения, что больше похоже на очередь. Тип чтения в этом случае — один раз для каждой заинтересованной стороны. Эти гибридные адресаты часто требуют надежности (durability), так что, если потребитель отключается, сообщения, которые отправляются в это время, принимаются после повторного подключения потребителя.
Гибридные модели не новы и могут применяться в большинстве систем обмена сообщениями, включая как ActiveMQ (через виртуальные или составные адресаты, которые объединяют топики и очереди), так и Kafka (неявно, как фундаментальное свойство дизайна её адресата).
Теперь, когда у нас есть некоторая базовая терминология и понимание того, для чего нам могла бы пригодиться система обмена сообщениями, давайте перейдем к деталям.
Асинхронное взаимодействие. Брокеры сообщений. Apache Kafka
Данная публикация предназначена для тех, кто интересуется устройством распределенных систем, брокерами сообщений и Apache Kafka. Здесь вы не найдете эксклюзивного материала или лайфхаков, задача этой статьи – заложить фундамент и рассказать о внутреннем устройстве упомянутого брокера. Таким образом, в следующих публикациях мы сможем делать ссылки на данную статью, рассказывая о более узкоспециализированных темах.
Привет! Меня зовут Дмитрий Шеламов и я работаю в Vivid.Money на должности backend-разработчика в отделе Customer Care. Наша компания – европейский стартап, который создает и развивает сервис интернет-банкинга для стран Европы. Это амбициозная задача, а значит и ее техническая реализация требует продуманной инфраструктуры, способной выдерживать высокие нагрузки и масштабироваться согласно требованиям бизнеса.
В основе проекта лежит микросервисная архитектура, которая включает в себя десятки сервисов на разных языках. В их числе Scala, Java, Kotlin, Python и Go. На последнем я пишу код, поэтому практические примеры, приведенные в этой серии статей, будут задействовать по большей части Go (и немного docker-compose).
Работа с микросервисами имеет свои особенности, одна из которых – организация коммуникаций между сервисами. Модель взаимодействия в этих коммуникациях бывает синхронной или асинхронной и может оказать существенное влияние на производительность и отказоустойчивость системы в целом.
Асинхронное взаимодействие
Итак, представим что у нас есть два микросервиса (А и Б). Будем считать, что коммуникация между ними осуществляется через API и они ничего не знают о внутренней реализации друг друга, как и предписывает микросервисный подход. Формат передаваемых между ними данных заранее оговорен.
Задача перед нами стоит следующая: нам нужно организовать передачу данных от одного приложения к другому и, желательно, с минимальными задержками.
В самом простом случае поставленная задача достигается синхронным взаимодействием, когда А отправляет приложению Б запрос, после чего сервис Б его обрабатывает и, в зависимости от того, успешно или не успешно был обработан запрос, отправляет некоторый ответ сервису А, который этот ответ ожидает.
Если же ответ на запрос так и не был получен (например, Б рвет соединение до отправки ответа или А отваливается по таймауту), сервис А может повторить свой запрос к Б.
С одной стороны, такая модель взаимодействия дает определенность статуса доставки данных для каждого запроса, когда отправитель точно знает, были ли получены данные получателем и какие дальнейшие действия ему необходимо делать в зависимости от ответа.
С другой стороны, плата за это – ожидание. После отправки запроса сервис А (или поток, в котором выполняется запрос) блокируется до того момента, пока не получит ответ или не сочтет запрос неудавшимся согласно своей внутренней логике, после чего примет дальнейшие действия.
Проблема не только в том, что ожидание и простой имеют место быть, – задержки в сетевом взаимодействии неизбежны. Основная проблема заключается в непредсказуемости этой задержки. Участники коммуникации в микросервисном подходе не знают подробностей реализации друг друга, поэтому для запрашивающей стороны не всегда очевидно, обрабатывается ли ее запрос штатно или нужно переотправить данные.
Все, что остается А при такой модели взаимодействия – это просто ждать. Может быть наносекунду, а может быть час. И эта цифра вполне реальна в том случае, если Б в процессе обработки данных выполняет какие-либо тяжеловесные операции, вроде обработки видео.
Возможно, вам проблема не показалась существенной – одна железка ждет пока другая ответит, велика ли потеря?
Чтобы сделать эту проблему более личной, представим, что сервис А – это приложение, запущенное на вашем телефоне, и пока оно ожидает ответ от Б, вы видите на экране анимацию загрузки. Вы не можете продолжить пользоваться приложением до тех пор, пока сервис Б не ответит, и вынуждены ждать. Неизвестное количество времени. При том, что ваше время гораздо ценнее, чем время работы куска кода.
Подобные шероховатости решаются следующим образом – вы разделяете участников взаимодействия на два “лагеря”: одни не могут работать быстрее, как бы вы их ни оптимизировали (обработка видео), а другие не могут ждать дольше определенного времени (интерфейс приложения на вашем телефоне).
Затем вы заменяете cинхронное взаимодействие между ними (когда одна часть вынуждена ждать другую, чтобы удостовериться, что данные были доставлены и обработаны сервисом-получателем) на асинхронное, то есть модель работы по принципу отправил и забыл – в этом случае сервис А продолжит свою работу, не дожидаясь ответа от Б.
Но как в этом случае гарантировать то, что передача прошла успешно? Вы же не можете, допустим, загрузив видео на видеохостинг, вывести пользователю сообщение: «ваше видео может быть обрабатывается, а может быть и нет», потому что сервис, занимающийся загрузкой видео, не получил от сервиса-обработчика подтверждение, что видео дошло до него без происшествий.
В качестве одного из решений данной проблемы мы можем добавить между сервисами А и Б прослойку, которая будет выступать временным хранилищем и гарантом доставки данных в удобном для отправителя и получателя темпе. Таким образом мы сможем расцепить сервисы, синхронное взаимодействие которых потенциально может быть проблемным:
Однако выбор СУБД в качестве инструмента для обмена данными может привести к проблемам с производительностью с ростом нагрузки. Причина в том, что большинство баз данных не предназначены для такого сценария использования. Также во многих СУБД отсутствует возможность разделения подключенных клиентов на получателей и отправителей (Pub/Sub) – в этом случае, логика доставки данных должна быть реализована на клиентской стороне.
Вероятно, нам нужно нечто более узкоспециализированное, чем база данных.
Брокеры сообщений
Брокер сообщений (очередь сообщений) – это отдельный сервис, который отвечает за хранение и доставку данных от сервисов-отправителей к сервисам-получателям с помощью модели Pub/Sub.
Эта модель предполагает, что асинхронное взаимодействие осуществляется согласно следующей логике двух ролей:
Очередь можно представить как канал связи, натянутый между писателем и читателем. Писатели кладут сообщения в очередь, после чего они “проталкиваются” (push) читателям, которые подписаны на эту очередь. Один читатель получает одно сообщение за раз, после чего оно становится недоступно другим читателям.
Под сообщением же подразумевается единица данных, обычно состоящая из тела сообщения и метаданных брокера.
В общем случае, тело представляет из себя набор байт определенного формата.
Получатель обязательно должен знать этот формат, чтобы после получения сообщения иметь возможность десериализовать его тело для дальнейшей обработки.
Использовать можно любой удобный формат, однако, важно помнить об обратной совместимости, которую поддерживают, например, бинарный Protobuf и фреймворк Apache Avro.
По такому принципу работает большинство брокеров сообщений, построенных на AMQP (Advanced Message Queuing Protocol) – протоколе, который описывает стандарт отказоустойчивого обмена сообщениями посредством очередей.
Данный подход обеспечивает нам несколько важных преимуществ:
At least once, напротив, гарантирует получение сообщения получателем, однако при этом есть вероятность повторной обработки одних и тех же сообщений.
Зачастую эта гарантия достигается с помощью механизма Ack/Nack (acknowledgement/negative acknowledgement), который предписывает совершать переотправку сообщения, если получатель по какой-то причине не смог его обработать.
Таким образом, для каждого отправленного брокером (но еще не обработанного) сообщения существует три итоговых состояния — получатель вернул Ack (успешная обработка), вернул Nack (неуспешная обработка) или разорвал соединение. Последние два сценария приводят в переотправке сообщения и повторной обработке.
Однако брокер может произвести повторную отправку и при успешной обработке сообщения получателем. Например, если получатель обработал сообщение, но завершил свою работу, не отправив сигнал Ack брокеру.
В этом случае брокер снова положит сообщение в очередь, после чего оно будет обработано повторно, что может привести к ошибкам и порче данных, если разработчик не предусмотрел механизм устранения дублей на стороне получателя.
Стоит отметить, что существует еще одна гарантия доставки, которая называется “exactly once”. Ее трудно достичь в распределенных системах, но при этом она же является наиболее желаемой.
В этом плане, Apache Kafka, о которой мы будем говорить далее, выгодно выделяется на фоне многих доступных на рынке решений. Начиная с версии 0.11, Kafka предоставляет гарантию доставки exactly once в пределах кластера и транзакций, в то время как AMQP-брокеры таких гарантий предоставить не могут. Транзакции в Кафке – тема для отдельной публикации, сегодня же мы начнем со знакомства с Apache Kafka.
Apache Kafka
Мне кажется, что будет полезно для понимания начать рассказ о Кафке со схематичного изображения устройства кластера.
Отдельный сервер Кафки именуется брокером. Брокеры образуют собой кластер, в котором один из этих брокеров выступает контроллером, берущим на себя некоторые административные операции (помечен фиолетовым).
За выбор брокера-контроллера, в свою очередь, отвечает отдельный сервис – ZooKeeper, который также осуществляет service discovery брокеров, хранит конфигурации и принимает участие в распределении новых читателей по брокерам и в большинстве случаев хранит информацию о последнем прочитанном сообщении для каждого из читателей. Это важный момент, изучение которого требует опуститься на уровень ниже и рассмотреть, как отдельный брокер устроен внутри.
Commit log
Структура данных, лежащая в основе Kafka, называется commit log или журнал фиксации изменений.
Новые элементы, добавляемые в commit log, помещаются строго в конец, и их порядок после этого не меняется, благодаря чему в каждом отдельном журнале элементы всегда расположены в порядке их добавления.
Свойство упорядоченности журнала фиксаций позволяет использовать его, например, для репликации по принципу eventual consistency между репликами БД: в них хранят журнал изменений, производимых над данными в мастер-ноде, последовательное применение которых на слейв-нодах позволяет привести данные в них к согласованному с мастером виду.
В Кафке эти журналы называются партициями, а данные, хранимые в них, называются сообщениями.
Что такое сообщение? Это основная единица данных в Kafka, представляющая из себя просто набор байт, в котором вы можете передавать произвольную информацию – ее содержимое и структура не имеют значения для Kafka. Сообщение может содержать в себе ключ, так же представляющий из себя набор байт. Ключ позволяет получить больше контроля над механизмом распределения сообщений по партициям.
Партиции и топики
Почему это может быть важно? Дело в том, что партиция не является аналогом очереди в Кафке, как может показаться на первый взгляд. Я напомню, что формально очередь сообщений – это средство для группирования и управления потоками сообщений, позволяющее определенным читателям подписываться только на определенные потоки данных.
Так вот в Кафке функцию очереди выполняет не партиция, а topic. Он нужен для объединения нескольких партиций в общий поток. Сами же партиции, как мы сказали ранее, хранят сообщения в упорядоченном виде согласно структуре данных commit log. Таким образом, сообщение, относящееся к одному топику, может хранится в двух разных партициях, из которых читатели могут вытаскивать их по запросу.
Следовательно, единицей параллелизма в Кафке выступает не топик (или очередь в AMQP брокерах), а партиция. За счет этого Кафка может обрабатывать разные сообщения, относящиеся к одному топику, на нескольких брокерах одновременно, а также реплицировать не весь топик целиком, а только отдельные партиции, предоставляя дополнительную гибкость и возможности для масштабирования в сравнении с AMQP брокерами.
Pull и Push
Обратите внимание, что я не случайно использовал слово “вытаскивает” по отношению к читателю.
В описанных ранее брокерах доставка сообщений осуществляется путем их проталкивания (push) получателям через условную трубу в виде очереди.
В Кафке процесса доставки как такового нет: каждый читатель сам ответственен за вытягивание (pull) сообщений из партиций, которые он читает.
Производители, формируя сообщения, прикрепляют к нему ключ и номер партиции. Номер партиции может быть выбран рандомно (round-robin), если у сообщения отсутствует ключ.
Если вам нужен больший контроль, к сообщению можно прикрепить ключ, а затем использовать hash-функцию или написать свой алгоритм, по которому будет выбираться партиция для сообщения. После формирования, производитель отправляет сообщение в Кафку, которая сохраняет его на диск, помечая, к какой партиции оно относится.
Каждый получатель закреплен за определенной партицией (или за несколькими партициями) в интересующем его топике, и при появлении нового сообщения получает сигнал на вычитывание следующего элемента в commit log, при этом отмечая, какое последнее сообщение он прочитал. Таким образом при переподключении он будет знать, какое сообщение ему вычитать следующим.
Какие преимущества имеет данный подход?
Недостатки
К недостаткам данного подхода можно отнести работу с проблемными сообщениями. В отличие от классических брокеров, битые сообщения (которые не удается обработать с учетом существующей логики получателя или из-за проблем с десериализацей) нельзя бесконечно перезакидывать в очередь, пока получатель не научится их корректно обрабатывать.
В Кафке по умолчанию вычитывание сообщений из партиции останавливается, когда получатель доходит до битого сообщения, и до тех пор, пока оно не будет пропущено и закинуто в “карантинную” очередь (также именуемой “dead letter queue”) для последующей обработки, чтение партиции продолжить не получится.
Также в Кафке сложнее (в сравнении с AMQP-брокерами) реализовать приоритет сообщений. Это напрямую вытекает из того факта, что сообщения в партициях хранятся и читаются строго в порядке их добавления. Один из способов обойти данное ограничение в Кафке – создать нескольких топиков под сообщения с разным приоритетом (отличаться топики будут только названием), например, events_low, events_medium, events_high, а затем реализовать логику приоритетного чтения перечисленных топиков на стороне приложения-консьюмера.
Еще один недостаток данного подхода связан тем, что необходимо вести учет последнего прочитанного сообщения в партиции каждым из читателей. В силу простоты структуры партиций, эта информация представлена в виде целочисленного значения, именуемого offset (смещение). Оффсет позволяет определить, какое сообщение в данный момент читает каждый из читателей. Ближайшая аналогия оффсета — это индекс элемента в массиве, а процесс чтения похож на проход по массиву в цикле с использованием итератора в качестве индекса элемента.
Однако этот недостаток нивелируется за счет того, что Kafka, начиная с версии 0.9, хранит оффсеты по каждому пользователю в специальном топике __consumer_offsets (до версии 0.9 оффсеты хранились в ZooKeeper).
К тому же, вести учет оффсетов можно непосредственно на стороне получателей.
Также усложняется и масштабирование: напомню, что в AMQP брокерах для того, чтобы ускорить обработку потока сообщений, нужно просто добавить несколько экземпляров сервиса-читателя и подписать их на одну очередь, при этом не требуется вносить никаких изменений в конфигурации самого брокера.
Однако в Кафке масштабирование происходит несколько сложнее, чем в AMQP брокерах. Например, если вы добавите еще один экземпляр читателя и натравите его на ту же партицию, вы получите нулевой КПД, так как в этом случае оба экземпляра будут читать один и тот же набор данных.
Поэтому базовое правило масштабирования Кафки — количество конкурентных читателей (то бишь группа сервисов, реализующих одинаковую логику обработки (реплик)) топика не должно превышать количество партиций в этом топике, иначе какая-то пара читателей будут обрабатывать одинаковый набор данных.
Consumer Group
Чтобы избежать ситуации с чтением одной партиции конкурентными читателями, в Кафке принято объединять несколько реплик одного сервиса в consumer Group, в рамках которого Zookeeper будет назначать одной партиции не более одного читателя.
Так как читатели привязываются непосредственно к партиции (при этом читатель обычно ничего не знает о количестве партиций в топике), ZooKeeper при подключении нового читателя производит перераспределение участников в Consumer Group таким образом, чтобы каждая партиция имела одного и только одного читателя.
Читатель обозначает свою Consumer Group при подключении к Kafka.
Но здесь мы можем столкнуться с другой проблемой, порожденной тем, что Кафка использует структуру из топиков и партиций. Я напомню, что Кафка не гарантирует упорядоченность сообщений в рамках топика, только в рамках партиции, что может оказаться критичным, например, при формировании отчетов о действиях по пользователю и отправке их в хранилище as is.
Чтобы решить эту проблему, мы можем пойти от обратного: если все события, относящиеся к одной сущности (например, все действия относящиеся к одному user_id), будут всегда добавляться в одну и ту же партицию, они будут упорядочены в рамках топика просто потому, что находятся в одной партиции, порядок внутри которой гарантирован Кафкой.
Для этого нам и нужен ключ у сообщений: например, если мы будем использовать для выбора партиции, в которую будет добавлено сообщение, алгоритм, вычисляющий хэш от ключа, то сообщения с одинаковым ключом будут гарантированно попадать в одну партицию, а значит и вытаскивать получатель сообщения с одинаковым ключом в порядке их добавления в топик.
В кейсе с потоком событий о действиях пользователей ключом партицирования может выступать user_id.
Retention Policy
Теперь пришло время поговорить о Retention Policy.
Это настройка, которая отвечает за удаление сообщений с диска при превышении пороговых значений даты добавления (Time Based Retention Policy) или занимаемого на диске пространства (Size Based Retention Policy).
Compaction Policy
Еще одним способом оптимизации объема, занимаемого на диске, может быть использование Compaction Policy – эта настройка позволяет хранить только последнее сообщение по каждому ключу, удаляя все предыдущие сообщения. Это может быть полезно, когда нас интересует только последнее изменение.