Основная идея Pub-Sub довольно простая: "publish–subscribe is a messaging pattern where senders of messages, called publishers, do not program the messages to be sent directly to specific receivers, called subscribers, but instead characterize published messages into classes without knowledge of which subscribers, if any, there may be. Similarly, subscribers express interest in one or more classes and only receive messages that are of interest, without knowledge of which publishers, if any, there are." В свободном переводе это может звучать так: "Издатель-подписчик (англ. publisher-subscriber или англ. pub/sub) — поведенческий шаблон проектирования передачи сообщений, в котором отправители сообщений, именуемые издателями (англ. publishers), напрямую не привязаны программным кодом отправки сообщений к подписчикам (англ. subscribers). Вместо этого сообщения делятся на классы и не содержат сведений о своих подписчиках, если таковые есть. Аналогичным образом подписчики имеют дело с одним или несколькими классами сообщений, абстрагируясь от конкретных издателей."
Pub-sub модель
Давайте немножко пофантазируем и попробуем разработать Pub-sub систему с нуля сами. В результате получится наиболее очевидная модель, которая в дальнейшем поможет нам понять особенности и возможные проблемы конкретных реализаций.
Основной бизнес-процесс в нашей системе может выглядеть так:
- Подписчики подписываются на сообщение.
- Издатель создает сообщение.
- Издатель публикует сообщение в нашей системе.
- Сообщение какое-то время хранится в системе.
- Система отправляет сообщение всем подписчикам.
- Подписчики принимают сообщение.
- Подписчики обрабатывают сообщение.
Участников вышеописанного бизнес-процесса я выделил подчеркиванием, это:
- Издатель. Их может быть несколько.
- Подписчик. Их обычно несколько, но может и вообще не быть.
- Система. Это наша pub-sub система.
Все участники оперируют с сообщениями. Сообщения — это передаваемые данные. Обычно это просто массив байт, в который сериализуются любые данные.
Мы перечислили следующие операции над сообщениями (выделены жирным шрифтом):
- Подписаться
- Создать
- Опубликовать
- Хранить
- Отправить
- Принять
- Обработать
Очереди и асинхронность
Нигде в нашей модели мы не столкнулись с очередями. Не упомянуты они и в определении pub-sub. Также нигде не была упомянута асинхронность.
Но без очереди/очередей pub-sub реализация вряд ли возможна. Если мы попробуем отправлять сообщения сразу же после их опубликования, то возникает масса проблем. К примеру, как быстро пройдет отправка сообщения к тысячам подписчиков? Что будет, если подписчик не успеет обработать предыдущее сообщение? Что, если новые сообщения будут опубликованы в то время, когда предыдущие сообщения еще не отправлены?
Я не встречал реализаций pub-sub без очередей, хотя теоретически это возможно.
Итак, очереди. Очередь — это временное хранилище сообщений, работающее на принципе «Первый пришёл — первый ушёл». Опять же, pub-sub в своем определении не требует поддержания очередности сообщений, но обычно это подразумевается.
Где мы будем размещать очередь/очереди? Этот вопрос пройдет золотой нитью через наше дальнейшее обсуждение.
Вопросы по реализации
Теперь, пожалуй, начнем формулировать вопросы по нашему бизнес-процессу:
- Где и как хранятся адреса издателей и подписчиков?
В принципе адреса могут храниться у любого участника процесса. Но обычно система выступает в качестве брокера и хранит все адреса у себя. - Подписка, что она из себя представляет?
Наверняка подписка будет включать адрес подписчика. Кроме этого, будет указан "класс сообщения", своеобразный фильтр, который определяет, соответствует ли сообщение данной подписке или нет. Адрес издателя здесь не нужен. - Можем ли мы добавить или убрать подписку в любой момент или должны для этого остановить систему?
Если мы добавляем/убираем подписку, как это отразится на сообщениях, которые уже опубликованы и ждут отправки? - Важен ли формат сообщения?
Скорее всего сообщение будет сериализовано и размещено в пакете. Вместе с пакетом могут передаваться дополнительные параметры, которые размещаются в headers пакета. - Как сообщения фильтруются по подпискам?
Обычно сообщение сразу же фильтруется по подпискам на этапе публикации. Иногда сообщение фильтруется только на этапе отправки сообщения подписчику. Иногда сообщение фильтруется уже на стороне подписчика. - Что из себя представляют фильтры подписок?
Фильтры подписок могут представлять из себя иерархии наподобие namespaces, либо наборы ключей, либо RegEx выражения. - Что будет, если система или подписчик не может принять сообщения?
Система или подписчик может отказать в приемке, может удалить сообщение без всякого предупреждения, а может и просто зависнуть или выйти из строя. - Повторить ли отправку сообщения, если она завершилась неудачей?
Повторение отправки (retry) — это стандартное решение в случае, если принимающая сторона временно недоступна. Ключевое слово здесь «временно». Если принимающая сторона или канал передачи перестали работать на постоянной основе, то повторение отправки ничем не поможет, а только перегрузит систему/канал связи. - Можно ли настраивать алгоритм повторной отправки сообщений?
При ненадежных каналах связи этот вопрос может быть одним из главных. Можем ли мы изменять количество повторных попыток, промежуток между попытками? По какому алгоритму настраивается промежуток между попытками? - Как долго сообщения хранятся?
Если подписчик долго не забирает сообщение, что с ним надо делать? Хранить ли его неограниченное время или удалять после определенного интервала? - Кто инициирует отправку сообщения?
Подписчик может время от времени опрашивать систему на предмет новых сообщений (poll mode) или система сама отправляет новое сообщение подписчику (push mode). В первом случае подписчик должен знать адрес системы, во втором случае система должна знать адрес подписчика. Во втором случае система более экономно расходует как свои ресурсы, так и ресурсы канала передачи данных. - Что будет, если подписчик перегружен сообщениями?
Закрыть ли ему вход на прием новых сообщений и отсылать отправляющей стороне предупреждения или молча игнорировать сообщения? А может подписчик может увеличивать (масштабировать) свои ресурсы?
Если вы собрались использовать pub-sub в своем проекте и выбираете для этого одну из имеющихся pub-sub систем, пройдитесь по этим вопросам. Если какие-то из них критически важны для вас, попытайтесь найти ответы.
Детали реализации
Я хочу рассмотреть реализации Pub-Sub на примере наиболее популярных программ этого класса. Если читатель решит, что в этом списке надо что-то изменить, я буду раз обсудить это. Для чего я это делаю, вместо того, чтобы продолжить теоретическое обсуждение шаблона? Есть у меня такое ощущение, что примеры реализации не менее важны для понимания, чем голая теория.
Итак, что сейчас в списке:
- Microsoft Azure ServiceBus Topics
- Microsoft Azure EventHub
- Microsoft BizTalk Server
- RabbitMQ
- ZeroMQ
- microServiceBus
- Redis
Azure ServiceBus Topics
Вся система расположена в Microsoft Azure. Реализация, можно сказать, классическая, на основе модели брокера сообщений.
Pub-sub система — это централизованный платный сервис, брокер, через который и проходят все сообщения. Все издатели и подписчики регистрируются здесь, все их credentials также хранятся здесь. Подписчик регистрирует здесь подписку. Все сообщения поступают сюда и попадают в очередь, реализованную на базе SQL Server. Для каждой подписки создается виртуальная очередь, своеобразный курсор, в которой хранятся указатели на реальные сообщения, хранящиеся в основной очереди. В виртуальную очередь попадают сообщения выбранные фильтром подписки. Подписчик запрашивает очередное сообщение сам (poll), либо система оповещает подписчика о новом сообщении (push). Подписчик получает сообщение, после чего указатель сообщения удаляется из виртуальной очереди. Когда сообщение удалено из всех виртуальных очередей, оно удаляется из основной очереди, из системы.
Microsoft Azure EventHub
EventHub сильно отличается от классической реализации. Основное отличие в том, что EventHub не регистрирует и не хранит подписки, он не создает и не хранит виртуальные очереди. Полученные сообщения просто хранятся в системе в виде линейного списка. Они хранятся определенное время, после чего удаляются системой. Подписчики имеют доступ ко всем сообщениям. Подписчик сам фильтрует сообщения и сам хранит курсор на список. С одной стороны это возлагает на подписчика дополнительную функцию. С другой стороны EventHub освобожден от поддержки подписок, благодаря чему достигается великолепная производительность. Это также позволяет по-другому подходить к вопросам надежности, так как подписчик в любой момент может возвратиться назад по списку и заново считать сообщения. Достигается практически полная независимость подписчиков от издателей.
Операция подписки в EventHub — это нуль-операция. Любой клиент в любое время может начать считывать сообщения.
Microsoft BizTalk Server
BizTalk существует уже второй десяток лет, поэтому ждать от него новаторских подходов не стоит. По сути его Pub-sub механизм, называемый MessageBox, является предшественником Azure Topics. Отличие в том, что доступ к нему осуществляется через адаптеры. Мы не можем просто вызвать функцию API, чтобы опубликовать или получить сообщение, нам надо использовать адаптеры. Адаптеры играют роль как издателей, так и получателей сообщений.
RabbitMQ
RabbitMQ основан на идеях AMQP протокола, но не его последней версии (1.0), а на предшествующих версиях: 0-8 и 0-9-1. В этих версиях поддерживаются так называемые exchanges, от которых разработчики AMQP отказались в версии 1.0. Модель exchange элегантно реализует pub-sub для брокера, которым и является RabbitMQ.
ZeroMQ
ZeroMQ — это наследник идей AMQP. Один из создателей AMQP сделал ZeroMQ, когда процесс обсуждения AMQP версии 1 зашел в тупик. ZeroMQ представляет из себя одну из интересных идей по реализации сверхскоростной системы обмена сообщениями, в то числе c использованием pub-sub.
В ZeroMQ нет централизованного сервиса. Вся Pub-sub система реализована как часть кода издателя и подписчика. ZeroMQ — это просто библиотека, поддерживающая очереди. В самой простой модели программы подписчика и издателя создают локальные очереди. Подписчик явно соединяется по адресу издателя, после чего сообщения из очереди издателя начинают поступать в очередь подписчика. Очереди расположены в адресных пространствах программ издателя и подписчика, без промежуточного брокера. Система не представлена отдельным сервисом, а как бы размазана между издателем и подписчиком.
С одной стороны, если издатель или подписчик решит завершить работу, сообщения из его очереди просто пропадут. С другой стороны, такая реализация обеспечивает максимальную скорость передачи сообщений.
Очереди в ZeroMQ хранятся в памяти, что обеспечивает максимальную производительность, но снижает надежность.
В ZeroMQ реализуются и более сложные конфигурации. К примеру, можно создать pub-sub c брокером. Более того, ZeroMQ позволяет создавать разнообразные архитектуры, которые не имеют аналогов в классических Pub-sub системах. К примеру, можно создать различные варианты распределенных брокеров. Рекомендую почитать одно из самых удачных описаний pub-sub на сайте ZeroMQ, начиная с основ pub-sub и кончая более сложными моделями. Это займет довольно много времени, но мне, к примеру, это открыло глаза на многие важные аспекты pub-sub.
microServiceBus
Одна из новых реализаций pub-sub. Я включил ее в наш список из-за нескольких интересных решений. Базируется microServiceBus на Azure ServiceBus Topics.
Интересно то, что код издателей и подписчиков хранится здесь же, на Azure. Причем этот код может загружаться автоматически. Код реализован на Node.js, а это значит хорошую платформонезависимость. Node.js работает на практически любых операционных системах и устройствах. Любое устройство, где может работать Node.js, может быть как издателем, так и подписчиком, или тем и другим одновременно.
По примеру BizTalk Server в microServiceBus реализованы адаптеры для многих систем и протоколов. Но в отличии от BizTalk, адаптеры — лишь полезная добавка к API.
microServiceBus похожа на ZeroMQ тем, что ориентирована на разработчика.
Так же, как ZeroMQ, она многоплатформенная, хотя многоплатформенность ZeroMQ базируется на портировании библиотеки на множество языков и на том, что базовая библиотека сделана на С. Многоплатформенность microServiceBus базируется на том, что Node.js портирован на множество операционных систем.
microServiceBus ориентированна как на интеграцию систем, так и на интеграцию устройств (IoT).
Redis
Redis вообще-то не pub-sub система, а key-value data storage. Но он на удивление хорошо реализует классический pub-sub и показывает замечательную производительность.
Вы можете установить Redis локально или как распределенный сервис. Вы можете добавить возможность сохранения сообщений на диск.
Выбор системы
Если мы попробуем взглянуть на системы pub-sub с более практической стороны, перед нами неизбежно возникнет вопрос выбора системы для своих проектов, для работы. Я ни в коей мере не пытаюсь ответить на этот вопрос, лишь схематично обрисую канву для поиска ответа. Во-первых, pub-sub систем значительно больше, чем собрано в этой статье. Во-вторых, я ни в коем случае не претендую на правильную расстановку акцентов.
Буду акцентировать на системах, которые выделяются чем-нибудь особенным из общего списка.
Таблица сравнения находится здесь.
Среда разработки
BizTalk Server можно использовать только с Visual Studio. Многочисленные редакторы используются для создания различных частей системы.
Разработка для остальных систем в основном сводится к привычной работе с кодом и системным API.
Языки разработки
Имеется в виду разработка программ издателей и подписчиков, я его буду называть клиентский код. Ядро системы обычно предоставляется в виде готового сервиса и нам надо добавить программы издателей и подписчиков, чтобы получить работающую систему.
Клиентский код для Redis, ZeroMQ и RabbitMQ можно разрабатывать на большом количестве языков. Можно использовать все языки, применяемые для промышленных систем: C#, Jave, Python, PHP, JavaScript / Node.js, Scala, Ruby.
В ZeroMQ большая часть языков реализована в виде оболочек для оригинальной библиотеки, написанной на C. Существуют и оригинальные библиотеки для C#, Java, Erlang и JavaScript. Но они немного отстают в функционале от оригинальной библиотеки на С.
Azure ServiceBus Topics и Azure EventHub имеют C# и REST API.
Клиентский код microServiceBus пишется только на Node.js, но это является скорее сильной стороной, а не недостатком.
Реализация ядра системы
Системы Azure ServiceBus Topics и Azure EventHub реализованы в виде SaaS сервисов, размещенных в Microsoft Azure облаке. microServiceBus работает поверх Azure ServiceBus Topics.
ZeroMQ не имеет готовой брокерной части, но ее можно довольно просто сделать. Причем можно сделать брокер любой сложности с любым функционалом. Хорошие программисты часто выбирают ZeroMQ именно из-за этого.
Сервисы RabbitMQ и Redis поставляются в бинарном и исходном виде и их можно установить на любых серверных платформах: Windows, Linux, Mac, Unix. ZeroMQ тоже можно установить на любой платформе.
BizTalk Server реализован в виде массивного SQL кода и .NET кода, работающего в Windows services и работает он только под Windows. Вам надо будет иметь не только лицензию для BizTalk, но также лицензии для Windows и SQL Server.
Надежность и Зрелость
В этой категории выделяется BizTalk Server. Он известен тем, что системы на его базе работают годами без всякого обслуживания. Код его ядра практически не меняется, за многие годы ошибки были исправлены.
ZeroMQ и Redis довольно новые системы и постоянно находятся в стадии усовершенствований. Это накладывает свою специфику именно в отношении надежности. Известны массивные системы, реализованные на их базе, но для создания подобных систем обязательно нужны программисты хорошего уровня.
Azure Topics и EventHub — новые системы, но за ними стоит Microsoft, поэтому к надежности претензий нет.
По microServiceBus пока нет никакой информации, так как система только-только появилась.
Отмечу, что системы, хранящие очереди сообщений на дисках, имеют большую надежность, чем системы, держащие сообщения только в памяти.
Производительность
Здесь выделяется ZeroMQ. В некоторых случаях она передает сообщения даже быстрее TCP, благодаря объединению пакетов (batching).
За ZeroMQ следует Redis, потом EventHub.
Масштабируемость
ZeroMQ также лучше всего масштабируется, благодаря своей архитектуре и нетребовательности к ресурсам. Но вам придется уделить повышенное внимание дизайну системы. Хотя документация ZeroMQ предоставляет множество примеров масштабируемых систем, но вряд ли вы найдете готовый дизайн, который можно внедрить без значительной доработки.
Цена
BizTalk Server — самая дорогая система из нашего списка. Но вы должны понимать, что pub-sub — это лишь малая толика его функционала и выбирают BizTalk совсем по другим критериям.
Некоторые из перечисленных систем — Open Source системы. В крайнем случае вы потратитесь на платную поддержку.
Простота
Вам потребуется всего лишь несколько минут, чтобы начать работать с ZeroMQ. Чуть больше займет Redis. Azure ServiceBus Topics, microServiceBus, RabbitMQ и Azure EventHub тоже довольно просты для того, чтобы начать разработку.
BizTalk Server сложен во всех аспектах, но благодаря его зрелости, вы вряд ли останетесь один на один с системой, вы всегда найдете хороших специалистов с большим опытом работы.
Работающая система на Azure или RabbitMQ не так проста в настройках и масштабировании, как может показаться. Любой брокер, настраиваемый в кластерной конфигурации, потребует от вас не только знаний, но и опыта.
Сложность системы будет расти намного быстрее, чем количество узлов системы.
Автор: Leo_Gan