что такое гарантированная доставка
Что такое гарантированная доставка
Формат сообщений UDP
Протокол TCP предоставляет транспортные услуги, отличающиеся от услуг UDP. Вместо ненадежной доставки датаграмм без установления соединений, он обеспечивает гарантированную доставку с установлением соединений между прикладными процессами в виде байтовых потоков.
В протоколе TCP-соединения устанавливаются с помощью «тройного рукопожатия». Чтобы установить соединение, одна сторона (например, сервер) пассивно ожидает входящего соединения, выполняя примитивы LISTEN и ACCEPT, либо указывая конкретный источник, либо не указывая его.
Другая сторона (например, клиент) выполняет примитив CONNECT, указывая IP-адрес и порт, с которым он хочет установить соединение, максимальный размер TCP-сегмента и, по желанию, некоторые данные пользователя (например, пароль). Примитив CONNECT посылает TCP-сегмент с установленным битом SYN и сброшенным битом АСК и ждет ответа.
Когда этот сегмент прибывает в пункт назначения, TCP-сущность проверяет, выполнил ли какой-нибудь процесс примитив LISTEN, указав в качестве параметра тот же порт, который содержится в поле Порт получателя. Если такого процесса нет, она отвечает отправкой сегмента с установленным битом RST для отказа от соединения.
Если какой-либо процесс прослушивает какой-либо порт, то входящий ТСР-сегмент передается этому процессу. Последний может принять соединение или отказаться от него. Если процесс принимает соединение, он отсылает в ответ подтверждение. Последовательность TCP-сегментов, посылаемых в нормальном случае, (рис. а) Обратите внимание на то, что сегмент с установленным битом SYN занимает 1 байт пространства порядковых номеров, что позволяет избежать неоднозначности в их подтверждениях.
Реализация гарантированной асинхронной доставки сообщений
Статья за авторством Александра Романова, разработчика интеграционных решений.
В процессе интеграции систем мы часто сталкиваемся с необходимостью гарантированной доставки сообщений между системами. В таких случаях на помощь нам приходят очереди. Но не все задачи так просты, как доставка сообщений из системы А в систему Б. Бывают случаи, когда нужно обогатить доставляемые сообщения данными из смежных, участвующих в интеграции систем. Которые не всегда могут интегрироваться через очереди, а имеют лишь синхронные сервисы. И вот уже в нашей интеграции возникают такие явления, как недоступность, отказы и другие «приятные» особенности использования «синхронов». Можно было бы переложить обработку промежуточных отказов на систему-источник, но это некультурно, да и невозможно, если мы публикуем события сразу для нескольких систем (в топик).
Удобным и работающим решением проблемы, с нашей точки зрения, является асинхронная пошаговая обработка сообщения через внутреннюю очередь с вызовом внешнего сервиса на каждом шаге. В случае отказа сервиса по ошибке или из-за его временной неработоспособности сообщение попадает во внутреннюю очередь отказов и отправляется повторно после разборов с возникшими в сервисе проблемами.
Данное решение так же устраняет проблему невозможности отката транзакции при работе с внешними сервисами. Никакой вызов не пройдет дважды — обработка начинается именно с того шага, на котором произошел сбой.
Всё вышеописанное очень легко реализуется на интеграционной шине, в которой асинхронное взаимодействие между компонентами через внутренние очереди идёт «из коробки». Но слишком высокие цены за «коробку» могут сильно затруднить использование интеграционной шины. Мы приведем пример реализации простого приложения на Spring Integration (далее SI) + Rabbit MQ. Оговоримся, что Rabbit MQ у себя в production мы не используем из-за невозможности его работы с XA.
Сердцем всего приложения является spring-integration-context.xml. Там описана компонентная модель, инициализируются ресурсные бины и менеджер транзакций для работы с MQ. Опишем его подробнее.
Подключаем встроенный в SI драйвер и прописываем ресурсы:
Нам необходим низкоуровневый бин amqpTemplate, через который осуществляется взаимодействие с ресурсами. Данный бин мы используем напрямую в тестах, и он требуется для компонент SI, которые работают с Rabbit MQ. ConnectionFactory, необходимый для подключения к ресурсам, конфигурит Spring Boot по настройкам из application.yml (см. org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration).
Для транзакционной работы с Rabbit MQ требуется TransactionManager (нужен для отката сообщения обратно в очередь, если в процессе работы произойдет ошибка). К сожалению, Rabbit MQ не поддерживает XA-транзакции, иначе менеджер транзакций сконфигурил бы Spring Boot. Конфигурим предоставляемый Spring-ом вручную.
А теперь самое приятное. «Рисуем» flow! В кавычках, потому что пишем в виде xml, что менее приятно.
Рассмотрим сценарий взаимодействия между двумя компонентами. SomeComponentOne получает сообщение из канала, вызывает некий синхронный REST-сервис (работает с БД, пишет в файл и т.п.) и отправляет сообщение на дальнейшую обработку, которой должна заниматься SomeComponentTwo. Если SomeComponentOne не смогла выполнить порученный ей кусок работы, она должна откатить транзакцию и вернуть полученное сообщение туда, откуда она его забрала. Если всё хорошо — отправить сообщение во внутреннюю очередь и завершить транзакцию. SomeComponentOne забирает сообщение из внутренней очереди и отправляет сообщение в неё же, при этом не обязательно в том же виде, в котором получила. Сообщение может быть обогащено или изменено, нам не важно. Оно предназначено для работы компоненты SomeComponentTwo. Возникает проблема роутинга. Сообщение попадает во внутреннюю очередь и должно забираться оттуда нужной в данный момент времени компонентой. Другими словами, необходим роутинг.
В данном приложении продемонстрирован роутинг, основанный на заголовках сообщения. У сообщения заполняется кастомный заголовок PartnerComponent, указывающий на компоненту, которая должна работать с сообщением.
Распишем технические детали представленного flow.
Адаптер для чтения из входной очереди. Получает сообщение и в транзакции бросает его сразу во внутреннюю очередь.
Мы использовали специализированный для работы с очередями асинхронный канал, предоставляемый Spring-ом. Получили интерфейс SI-channel, а хранение сообщений непосредственно в очереди, в нашем случае во внутренней mq-очереди приложения. При получении сообщения из данного канала-очереди будет открываться транзакция, т.к. мы подключили наш менеджер транзакций.
К данному каналу-очереди подключаем SI-роутер, работающий на заголовках сообщений.
Новое для flow сообщение не имеет технического заголовка PartnerComponent, поэтому по-умолчанию будет обрабатываться компонентой someComponentOne, обязанностью которой является указание в заголовке сообщения PartnerComponent следующей компоненты и отправка сообщения во внутреннюю очередь. Роутер вновь забирает сообщение из внутренней очереди и отправляет его на обработку в указанную компоненту.
Описание компонент, в которые отправляются сообщения из роутера.
Адаптер для отправки в выходную очередь.
Сборка (pom.xml)
Старый добрый Maven. Стандартная сборка от Spring Boot. Зависимости от SI и AMQP предоставляют все необходимые библиотеки. Также подключаем spring-boot-starter-test для реализации проверочных кейсов на JUnit.
Работа SomeComponent*.java
Транзакционные бины, подключенные как service-activator к flow SI. Вызов REST через RestTemplate и отправка во внутреннюю очередь через innerChannel. Достаточно, чтобы продемонстрировать работу с сервисом и удобно за-mock-ить в тестах.
Тестируем
В тесте testHappyPath мы проверили работоспособность flow, когда нет сбоев при вызове REST. Mock-аем все вызовы REST-сервисов без сбоев, бросаем сообщение во входную очередь, ждем в выходной, проверяем прохождение всех компонент по контенту тела полученного сообщения.
В тесте testGuaranteedDelivery мы проверили гарантированную доставку при сбое в одном из REST. Эмулируем однократный сбой при вызове сервиса из третьей компоненты, ждём доставку сообщения до выходной очереди, проверяем тело полученного сообщения.
Заключение
Мы сделали приложение с гарантированной доставкой. Остался ряд мелких нерешенных вопросов: повторная отправка сообщений происходит бесконечно и неуправляемо, кто поддерживает данное решение — администратор или эту поддержку можно автоматизировать? Мы решаем данные вопросы при помощи самописных приложений и индивидуальных настроек для каждого типа сообщения. Возможно, в будущем мы опишем эти решения.
ZooKeeper в качестве системы гарантированной доставки для Яндекс.Почты
Ежедневно Яндекс обрабатывает входящий поток из миллионов писем и файлов. Их количество постоянно и непрерывно увеличивается. При этом уровень входящей нагрузки неравномерный, он сильно зависит от времени суток и дня недели. Кроме того, периодически случаются ботнет-атаки.
Задача усложняется тем, что каждое письмо и каждый файл требуют дополнительной обработки: мы классифицируем их, сохраняем, извлекаем текст, распознаем образы, создаем индексы. Благодаря этому в дальнейшем пользователю гораздо проще найти нужные письма или файлы.
Обработка может занимать разное время: от миллисекунд, требуемых для сохранения письма, до нескольких десятков секунд, затрачиваемых, например, на извлечение текста из фотографий.
Как бы мы не рассчитывали мощность системы приема и обработки писем и файлов, мы никогда не застрахованы от ситуаций, когда входной поток значительно превышает наши ожидания.
В этих случаях нам помогают очереди. Они позволяют сгладить всплески входной нагрузки: выиграть время и дождаться, когда кластер обработки писем станет доступен.
Что такое очередь
Очередь на обработку документов мало чем отличается от живой очереди в магазине или на почте. Там они тоже возникают по той же причине: обслуживать клиентов мгновенно невозможно. Однако важное преимущество очереди на обработку документов в Персональных сервисах Яндекса заключается в том, что стоят в ней только письма и файлы, пользователю ждать ничего не надо. По сути это буфер, в который можно быстро записать файлы и письма, а когда нагрузка на систему обработки спадет, быстро все это прочитать.
Как правило с очередью работают две программы: одна записывает в нее данные, другая вычитывает их и отправляет потребителю.
Программа записи в очередь должна быть максимально простой, чтобы не вносить никаких дополнительных задержек на приеме задачи, мы же не хотим делать еще одну очередь на помещение в основную. И программа чтения из очереди уже взаимодействует с различными функциональными элементами сервисов: Почты/Диска или других продуктов.
Программ чтения данных может быть несколько, каждая из которых служит для специализированной обработки данных. Программа чтения может работать существенно дольше, чем программа записи, поскольку на нее возложена дополнительная функциональность. Поэтому очередь должна уметь хранить данные за промежуток времени, достаточный для обработки писем/файлов своими источниками.
В основном очереди используются там, где есть неравномерная входная нагрузка и требуется выполнять операции над данными. В частности, на приеме писем в почту и файлов в диск.
Архитектура системы приема писем в Почту:
Письма влетают в почту и попадают в кластер Mx-Front, программы которого обрабатывают письма и сохраняют их в почтовые хранилища.
Если покладка письма прошла неудачно, письмо попадает на кластер Mx-Back, который состоит из очереди на PostFix и ряда программ покладки и анализа писем. Программы кластера Mx-Back делают много попыток положить письмо в хранилища в течение длительного времени, но если и здесь неудача, то письмо считается недоставленным. Если покладка письма удалась, то письмо отправляется на кластер Services, из которого оно уже доставляется в поиск.
Архитектура системы приема данных в Почтовый офис:
Поскольку сервис Почтовый офис является частью почты, то и система покладки встроена в почтовую. Отличие состоит лишь в том, что сам сервис не должен хранить сами письма. Сервису требуется лишь мета-информация из писем. А еще Постофис позволяет посмотреть на почту со стороны отправителя, а не со стороны получателя. Поэтому в качестве очереди мы решили попробовать другие решения, отличные от PostFix.
Архитектура системы приема файлов в Диск:
Файлы влетают в кладун, который складывает файлы в хранилище (mulca) и отправляет в кластер mpfs. MPFS сохраняет метаинформацию о файлах в хранилище метаданных (mongo DB) и кладет в очередь для доставки файлов в поиск. Далее файлы из очереди попадают в поиск.
Какие бывают очереди
Очередь в Mongo DB – используется в Диске для всех операций с метаданными: копирование, перемещение файлов на Диске и индексация файлов для поиска.
Достоинства – очередь сетевая. Если выпадает один сервер, данные сохраняются.
К основным недостаткам относится то, что очередь разгребается разными обработчиками, которые не синхронизированы друг с другом, поэтому не выполняется очередность постановки задач.
ZooKeeper
Учитывая богатый опыт использования очередей в персональных сервисах Яндекса, заявленные технические характеристики выше перечисленных очередей, а также наши внутренние требования по надежности доставки данных, мы решили рискнуть и попробовать Zookeeper в новом персональном сервисе Яндекса Почтовый офис.
Zookeeper заводится не с первого раза
Мы провели множество экспериментов, вот некоторые из них:
То, что у нас получилось, обязательно нужно было протестировать функционально и нагрузочно. Вот как мы это делали…
Архитектура тестового стенда
Для того, чтобы качественно провести функциональное и нагрузочное тестирование системы доставки, мы сначала описали кейсы, в которых очередь должна “доставлять”. Все возможные ситуации вытекают из продуктовых требований к надежности системы доставки и архитектурных особенностей построения систем в нашей компании.
Во-первых, протокол гарантированной доставки требует наличия нечетного количества работающих инстансов системы гарантированной доставки – это минимум три одновременно работающих программы, а лучше пять.
Во-вторых, в Яндексе является стандартом одновременное размещение любых систем в разных дата-центрах – это позволяет сохранить работоспособность сервиса в случае отключения питания в ДЦ или других аномальных явлениях в данном ДЦ.
В третьих, мы умышленно усложнили себе жизнь и решили совместить требование системы доставки и требования Яндекса, в результате мы разместили программу системы доставки на трех разных серверах, расположенных в трех разных дата-центрах.
С одной стороны, выбранное нами решение усложняет общую архитектуру и приводит к различным нежелательным эффектам в виде долгих междатацентровых пингов, с другой стороны, мы получаем систему, защищенную от атомной войны.
Получилась такая архитектура: прокси, получающий данные, очередь, хранилище.
Прокси размещены в пяти дата-центрах на десяти серверах. Очередь размещена в трех дата-центрах на трех серверах. В качестве хранилища может быть выбран любой SQL и noSQL storage, а самих хранилищ может быть любое количество. В нашем случае в качестве хранилища мы выбрали noSQL storage на базе Lucene, и разместили его на двух серверах в 2-х разных дата-центрах.
Функциональное и нагрузочное тестирование
Перед вводом в эксплуатацию мы провели функциональное и нагрузочное тестирование системы гарантированной доставки.
Тестирование проводилось последовательно в четырех режимах:
Внедрение
По результатам тестирования мы зажмурили глаза и, взяв ответственность на свою голову, решили запускать всю эту систему в продакшен. Как и ожидали, тестирование тестированием, а реальная жизнь создает такие ситуации, которые никогда не предскажешь заранее. Иногда у нас заканчивается пул соединений, иногда мы долго доставляем данные на один из бэкэндов, причем один из бэкэндов почему-то актуальный, а второй отстает.
Одним словом, в ходе эксплуатации возникают все новые и новые ситуации, которые мы успешно фиксим, и наша очередь, запущенная еще в ноябре, из экспериментальной технологии постепенно превращается в хороший автономный продукт.
Гарантированная доставка по gRPC стримам
Вот раньше сидишь себе, кодишь какой-нибудь монолит на PHP, деплоишь его по FTP и было счастье. А потом люди придумали всякое разделение, потому что с ростом нагрузки сервера не справлялись и нужны были варианты для масштабирования. Так появились всякие Event sourcing, CQRS, SOA и тому подобные. И вот у тебя уже требования немного повысились и нужно знать еще про сети, про событийную целостность, про CAP теорему и прочие вещи.
А потом, телефоны научились открывать сайты вполне себе нормально и пошла резиновая верстка в ход, потом респонзив и адаптивная верстка. И стало разрабатывать тот же сайтик на Вордпрессе сложнее.
А еще придумали SOAP, REST для API. В общем-то прогресс не стоял на месте и сейчас у нас для старта проекта есть несколько архитектурных паттернов, вроде SOA/CQRS и, прости господи, микросервисы, которые в последнее время очень популярны.
А для организации API у нас есть три подхода. RPC/REST/GraphQL. И вроде бы выбор достаточно понятен. RPC у нас для API, где мы можем писать свои методы и называть так, как удобно нам, при этом ответы более или менее стандартизированы. REST — у нас для всего того, что можно описать существительными, а действия над ними методами HTTP. А GraphQL — это некая попытка взять и сделать что-то вроде SQL поверх HTTP с одним эндпоинтом, отсылая какие-то запросы в жсоне и получая ответы обратно.
Что выбрать — не понятно. Ну и при всем изобилии подходов пришел такой Google, со словами, вы не верно понимаете RPC и сделал gRPC. И на первый взгляд все получилось у них хорошо, вместо XML протоколбуфера, работа поверх HTTP/2, но, отсутствует поддержка в браузерах, которую не страшно тащить в прод. Отсюда мы приходим к тому, что применение его сейчас — это либо микросервисы, либо мобильные приложения.
При этом, на gRPC можно строить вполне себе хорошие API, которые умеют в REST. В мобильных приложениях оно чувствует себя вполне хорошо, однако, в микросервисах у нас возникают некоторые сложности выбора.
В микросервисах мы можем строить коммуникацию как синхронно, так и асинхронно. Синхронно — это чаще всего REST, а когда нам нужно асинхронности, мы втаскиваем NATS/Kafka или другой MQ.
А что делать, если у нас такой случай, что втаскивать NATS — это оверкилл, не говоря уже о Kafka, а асинхронное взаимодействие с гарантиями нам сделать нужно?
Асинхронное взаимодействие между микросервисами без MQ
И вот, в очередном проекте у нас возникла такая ситуация. MQ втаскивать — оверкилл, а асинхронное взаимодействие нужно. Да еще и с гарантиями и не нарушая порядок доставки.
По архитектуре получилось следующее:
Функции ядра просты. Мы предоставляем внешнюю API для работы с задачами. Мы сохраняем полученные задачи в БД, передаем их на обработку и сохраняем обработанные результаты.
Процессор еще проще. То, что получили от Ядра, мы должны обработать по разным механизмам и отослать обратно.
В первой реализации мы сделали взаимодействие по обычным однонаправленным стримам. На ядре был сделан метод AddEvent, который принимал события, писал в Postgres и закидывал в гошный канал
Гошный канал мы использовали дальше при передаче ивентов в процессор. Процессор поднимает стрим до ядра, получает данные из Postgres и дальше получает то, что приходит в канал
Гарантии доставки
Когда мы строим распределенную систему (у нас всегда распределенная система, если у нас есть больше одного сервиса, которые общаются между собой), то у нас может возникнуть множество проблем
Когда мы собираемся гарантировать доставку сообщений с одного сервиса на другой, то мы не можем положиться на сеть, потому что сеть ненадежна и порядок доставки сообщений в ней не гарантирован, так еще и сообщения могут теряться и дублироваться.
Когда мы собираемся строить гарантии на уровне приложения, то приходится решать проблемы сети с помощью алгоритмов внутри приложения.
А еще нам нужно определиться со способом доставки сообщений. Сейчас есть минимум два вида доставки — это at least once delivery и at most once delivery.
at least once delivery — доставка сообщений хотя бы до одной ноды в сети. Сообщения мы можем потерять при этом методе доставки
at most once delivery — обеспечит доставку до всех нод, но при этом могут быть дубликаты сообщений.
Для реализации любого из механизмов доставки нам нужен протокол. После небольших раздумий, родилось следующее:
Этим протоколом мы покрываем реализацию at most once delivery, но для нашего случая хватает at least once, потому что сервисы запущены по одной копии на сервис, однако, дубликаты в системе еще никто не отменял.
Для проверки на дубликаты на стороне процессора была сделана обычная мапа с мутексом. А со стороны ядра мы отсылаем сообщения под ретраером с экспоненциальным бэкоффом.
Заключение
Хоть я и не любитель велосипедов и своих решений, но иногда свое решение можно сделать достаточно быстро и оно даже будет работать. А если вы выбрали gRPC в качестве инструмента для коммуникации между микросервисами, то вы можете достаточно долго жить без Кафки и не знать особых проблем, например с балансировкой. Но об этом мы поговорим в следующей статье 🙂
Зачем нужны очереди сообщений в микросервисной архитектуре: разбираем преимущества и недостатки
При проектировании микросервисов часто возникает вопрос: какой способ связи между ними выбрать.
Многие отдают предпочтение RESTful API. Однако этот подход не всегда эффективен, так как в отдельных случаях чреват долгим ожиданием на клиентской стороне и потерей информации в случае сбоев.
Мы расскажем про такой вариант для взаимодействия микросервисов, как очереди сообщений, а также попытаемся выяснить, для каких сценариев они подходят лучше всего. Разобраться в вопросе нам помог Павел Юдин, руководитель команды облачных продуктов, Tarantool / VK.
Sync vs Async: синхронное и асинхронное взаимодействие
Очереди сообщений (Message Queue) — это форма асинхронной коммуникации между сервисами. Поэтому, прежде чем говорить о них, покажем на упрощенном, немного искусственном примере разницу между синхронным и асинхронным взаимодействием.
Предположим, вы разрабатываете сайт книжного магазина и у вас есть сервис, к которому обращается пользователь, например отправка рецензии на прочитанную книгу. При нажатии кнопки «Отправить» вызывается некоторый API, который, в свою очередь, может обратиться к другим API.
При синхронном взаимодействии все запросы в этой цепочке вызовов выполняются строго друг за другом, а при выполнении последнего запроса ответы последовательно передаются в обратном направлении. В итоге пользователь вынужден пару секунд ждать сообщения о публикации своего отзыва, хотя его не интересуют особенности серверной обработки и он вполне обоснованно хочет увидеть сообщение сразу после нажатия кнопки. Конечно, время ожидания будет во многом определяться мощностью оборудования, но при пиковых нагрузках оно может стать серьезной проблемой.
Еще один недостаток такой схемы — обработка сбоев. Если на одном из шагов возникнет исключение, оно каскадно возвратится назад, и пользователь получит уведомление об ошибке с просьбой повторно отправить рецензию. Вряд ли кого-то обрадует получение подобного сообщения после длительного ожидания.
Синхронное взаимодействие на основе REST API
Описанную схему можно изменить, добавив асинхронные вызовы. Достаточно вызвать в асинхронном режиме первый REST API и параллельно вернуть пользователю сообщение о том, что его рецензия принята и будет размещена, например, в течение суток. В итоге сайт не блокируется, а вызовы всех последующих API происходят независимо от пользователя.
Но у такой схемы также есть существенный недостаток: в случае сбоя в одном из API информация, введенная пользователем, будет потеряна. Если в первом примере в случае ошибок достаточно повторно отправить рецензию, то здесь ее необходимо заполнить заново.
Вариант асинхронного взаимодействия на основе REST API
Для устранения недостатков обеих схем как раз и предназначены очереди сообщений.
Принципы работы очередей сообщений
Очереди предоставляют буфер для временного хранения сообщений и конечные точки, которые позволяют подключаться к очереди для отправки и получения сообщений в асинхронном режиме.
В сообщениях могут содержаться запросы, ответы, ошибки и иные данные, передаваемые между программными компонентами. Компонент, называемый производителем (Producer), добавляет сообщение в очередь, где оно будет храниться, пока другой компонент, называемый потребителем (Consumer), не извлечет сообщение и не выполнит с ним необходимую операцию.
Очереди поддерживают получение сообщений как методом Push, так и методом Pull:
Так как очереди могут использоваться несколькими производителями и потребителями одновременно, обычно их реализуют с помощью дополнительной системы, называемой брокером. Брокер сообщений (Message Broker) занимается сбором и маршрутизацией сообщений на основе предопределенной логики. Сообщения могут передаваться с некоторым ключом — по этому ключу брокер понимает, в какую из очередей (одну или несколько) должно попасть сообщение.
Вернемся к примеру с отправкой рецензии. Пусть та часть сервиса, к которому обращается пользователь, выступит в качестве производителя и будет направлять запросы на создание рецензий в очередь. Сразу после добавления сообщения в очередь пользователю можно направлять уведомление об успехе операции. Вся последующая логика обработки будет выполняться независимо от него на стороне потребителя, подписанного на очередь.
Завершив обработку, потребитель отправит подтверждение в очередь, после чего исходное сообщение будет удалено. Но если во время обработки произойдет сбой и подтверждение не будет получено вовремя, сообщение может быть повторно извлечено потребителем из очереди.
Вариант асинхронного взаимодействия на основе очереди сообщений
Польза и преимущества очередей сообщений в микросервисной архитектуре
Используя очереди сообщений в качестве основного средства взаимодействия микросервисов (Microservices Communication), можно добиться следующих преимуществ:
Отличительная черта микросервисов — их автономность. И очереди во многом помогают уменьшить зависимости между ними. Каждое сообщение, передаваемое в очереди, — это всего лишь массив байтов с некоторыми метаданными. Метаданные нужны для направления в конкретную очередь, а информация, содержащаяся в основной части (теле) сообщения, может быть практически любой. Брокер не анализирует данные, он выступает лишь в качестве маршрутизатора. Это позволяет настроить взаимодействие между компонентами, работающими даже на разных языках и платформах.
Очереди сообщений упрощают независимое масштабирование микросервисов. Наблюдая за состоянием очередей, можно масштабировать те сервисы, на которые приходится большая часть нагрузки. Кроме этого, очереди легко позволяют не только увеличивать число экземпляров существующих сервисов, но и добавлять новые с минимальным временем простоя. Все, что для этого требуется, — добавить нового потребителя, прослушивающего события в очереди.
Однако сами очереди также необходимо масштабировать, и это может создать дополнительные сложности.
Если один из сервисов не справляется с нагрузкой, требуется возможность запускать больше его экземпляров быстро и без дополнительных настроек. Обычно для этих целей используют балансировщик нагрузки, интегрированный с сервером обнаружения служб и предназначенный для распределения трафика. При использовании очередей сообщений сам брокер по умолчанию является балансировщиком нагрузки. Если несколько потребителей слушают очередь одновременно, сообщения будут распределяться между ними в соответствии с настроенной стратегией.
Выход из строя одного из компонентов не сказывается на работе всей системы: при восстановлении он обработает сообщение, находящееся в очереди. Ваш веб-сайт по-прежнему может работать, даже если задерживается часть обработки заказа, например, из-за проблем с сервером БД или системой электронной почты.
Правда, при этом очередь сама приобретает статус SPoF (Single Point Of Failure), поэтому необходимо заранее предусмотреть действия на случай ее аварийного отключения.
Большинство брокеров выполняют аутентификацию приложений, которые пытаются получить доступ к очереди, и позволяют использовать шифрование сообщений как при их передаче по сети, так и при хранении в самой очереди. Таким образом, очередь снимает с ваших сервисов бремя организации авторизации запросов.
Варианты использования очередей сообщений
Очереди сообщений полезны в тех случаях, где возможна асинхронная обработка. Рассмотрим наиболее частые сценарии использования очередей сообщений (Message Queue use Cases):
Сюда можно отнести задачи, которые не связаны напрямую с основным действием пользователя сайта и могут быть выполнены в фоновом режиме без необходимости ожидания с его стороны. Это обработка изображений, преобразование видео в различные форматы, создание отзывов, индексирование в поисковых системах после изменения данных, отправка электронной почты, формирование файлов и так далее.
Очереди можно использовать в качестве буфера для некоторой массовой обработки, например пакетной вставки данных в БД или HDFS. Очевидно, что гораздо эффективнее добавлять сто записей за раз, чем по одной сто раз, так как сокращаются накладные расходы на инициализацию и завершение каждой операции. Но для стандартной архитектуры может стать проблемой генерация данных клиентской службой быстрее, чем их может обработать получатель. Очередь же предоставляет временное хранилище для пакетов с данными, где они будут храниться до завершения обработки принимающей стороной.
Многие системы очередей позволяют производителю указать, что доставка сообщений должна быть отложена. Это может быть полезно при реализации льготных периодов. Например, вы разрешаете покупателю отказаться от размещения заказа в течение определенного времени и ставите отложенное задание в очередь. Если покупатель отменит операцию в указанный срок, сообщение можно удалить из очереди.
Помещая данные в очередь, вы можете быть уверены, что данные будут сохранены и в конечном итоге обработаны, даже если это займет немного больше времени, чем обычно, из-за большого скачка трафика. Увеличить скорость обработки в таких случаях также возможно — за счет масштабирования нужных обработчиков.
Нестабильная сеть в сочетании с очередью сообщений создает надежный системный ландшафт: каждое сообщение будет отправлено, как только это будет технически возможно.
Многие брокеры поддерживают очереди FIFO, полезные в системах, где важно сохранить порядок транзакций. Если 1000 человек размещают заказ на вашем веб-сайте одновременно, это может создать некоторые проблемы с параллелизмом и не будет гарантировать, что первый заказ будет выполнен первым. С помощью очереди можно определить порядок их обработки.
Очереди часто применяют для сбора некоторой статистики, например использования определенной системы и ее функций. Как правило, моментальная обработка такой информации не требуется. Когда сообщения поступают в веб-службу, они помещаются в очередь, а затем при помощи дополнительных серверов приложений обрабатываются и отправляются в базу данных.
Если у вас есть некоторая задача для группы серверов, то вам необходимо выполнить ее на каждом сервере. Например, при редактировании шаблона мониторинга потребуется обновить мониторы на каждом сервере, использующем этот шаблон. Вы можете поставить сообщение в очередь для каждого сервера и выполнять их одновременно в виде небольших операций.
Это обработка финансовых транзакций, бронирование авиабилетов, обновление записей о пациентах в сфере здравоохранения и так далее.
Сложности использования и недостатки очередей сообщений: как с ними справляться
Несмотря на многочисленные преимущества очередей сообщений, самостоятельное их внедрение может оказаться довольно сложной задачей по нескольким причинам:
Хорошая новость в том, что многие облачные провайдеры сейчас предлагают очереди как сервис (MQ as a Service). Поэтому если у вас недостаточно ресурсов для самостоятельной настройки и поддержки очередей сообщений, то можно воспользоваться одним из готовых решений. Большинство из них включает автоматизацию настройки, масштабирование, диагностику ошибок и техническую поддержку, а также поддерживает строго однократную доставку в очередях FIFO.
В каких случаях очереди неэффективны
Конечно, очереди не являются универсальным средством для любых приложений. Рассмотрим варианты, когда очереди не будут самым эффективным решением:
Выбирая инструмент для будущего приложения, обязательно взвесьте все за и против. Не стоит использовать очереди сообщений для задач, которые могут быть решены другим, более простым в настройке и обслуживании способом. Но в тех случаях, когда запланирован переход на микросервисы и бизнес-логика допускает возможность асинхронной обработки, очереди сообщений могут стать лучшим выбором для повышения производительности и надежности вашего продукта.
Если вы заинтересованы в использовании очередей, но опасаетесь, что команда не справится с их конфигурированием и последующей поддержкой самостоятельно, всегда можно воспользоваться одним из Managed-решений, представленных на рынке.