← Все статьи

Синхронная и асинхронная интеграция: когда REST, когда очереди и события

· 8 мин чтения

Чем синхронный вызов отличается от обмена сообщениями через брокер, что такое временная связанность, очереди, топики, команды и события, как выбрать способ интеграции под требования к задержке, надёжности и нагрузке и что фиксировать в постановке.

Две системы должны обменяться данными. Первая мысль — один сервис вызывает REST API другого и получает ответ. Это работает, пока второй сервис доступен, быстро отвечает и выдерживает нагрузку. Как только хотя бы одно из условий нарушается, синхронная цепочка начинает падать целиком. Асинхронная интеграция через очереди и события снимает часть проблем, но приносит свои: задержки, дубли, порядок сообщений, сложность отладки. Разберём, как выбирать между этими подходами и чем каждый из них расплачивается за свои преимущества.

Синхронная интеграция

При синхронном взаимодействии клиент отправляет запрос и ждёт ответа, прежде чем продолжить работу. Типичные варианты — REST, gRPC, SOAP, GraphQL. Модель понятна: вызвал функцию — получил результат или ошибку.

Плюсы:

  • немедленный результат: клиент сразу знает, получилось или нет;
  • простая модель согласованности: после успешного ответа данные уже изменены;
  • лёгкая отладка: один запрос, один ответ, понятная трассировка;
  • не нужна дополнительная инфраструктура — брокер, мониторинг очередей.

Минусы:

  • временная связанность: если поставщик недоступен, клиент тоже не может выполнить операцию;
  • каскадные отказы: медленный сервис в конце цепочки «подвешивает» потоки во всех сервисах выше по цепочке;
  • суммирование задержек: цепочка из пяти вызовов по 100 мс — это уже полсекунды;
  • перенос нагрузки: всплеск запросов у клиента сразу становится всплеском у поставщика.

Доступность синхронной цепочки — произведение доступностей её звеньев. Пять сервисов с доступностью 99,9% дают вместе около 99,5% — это уже не три с половиной часа простоя в год, а почти двое суток.

Асинхронная интеграция

При асинхронном взаимодействии отправитель передаёт сообщение посреднику — брокеру сообщений — и продолжает работу, не дожидаясь обработки. Получатель забирает сообщение, когда готов. Типичные брокеры: Apache Kafka, RabbitMQ, ActiveMQ/Artemis, облачные очереди (Amazon SQS, Yandex Message Queue), IBM MQ в банковском секторе.

Плюсы:

  • развязка по времени: получатель может быть недоступен — сообщения дождутся его в брокере;
  • сглаживание пиков: очередь поглощает всплеск, а получатель обрабатывает сообщения в своём темпе;
  • масштабирование: несколько экземпляров получателя делят поток сообщений между собой;
  • одно событие — много получателей: отправитель не знает, кто и сколько подписчиков обработают сообщение.

Минусы:

  • итоговая согласованность (eventual consistency): данные в системах какое-то время расходятся;
  • доставка «хотя бы один раз»: сообщение может прийти повторно, получатель обязан это выдерживать;
  • порядок не гарантирован в общем случае, а там, где гарантирован, — только в пределах партиции или очереди;
  • сложность отладки: путь бизнес-операции разбит на несколько независимых шагов в разных системах;
  • инфраструктура: брокер нужно эксплуатировать, мониторить и масштабировать.

Очередь и топик: два способа доставки

Очередь (point-to-point)Топик (publish-subscribe)
Кто получаетодин получатель из группы конкурирующихкаждый подписчик получает свою копию
Назначениераспределение работыоповещение заинтересованных сторон
Примерочередь задач на формирование PDF-счетовсобытие «Заказ оплачен» для склада, доставки и аналитики
В RabbitMQочередь с несколькими консьюмерамиfanout- или topic-exchange с очередью на каждого подписчика
В Kafkaодна consumer groupнесколько consumer group на одном топике

Kafka стоит отдельно: это не очередь, а журнал событий. Сообщения не удаляются после чтения, а хранятся заданное время; каждый потребитель сам помнит, до какого места дочитал (offset), и может перечитать историю. Это удобно для восстановления данных и подключения новых потребителей, но меняет модель мышления: «прочитано» не значит «удалено».

Команда, событие, документ

Сообщения различаются по смыслу, и это различие важно зафиксировать в контракте.

  • Команда — просьба что-то сделать: «Сформируй счёт», «Отправь SMS». У неё один адресат, и отправитель ожидает, что действие будет выполнено. Название — в повелительном наклонении.
  • Событие — уведомление о свершившемся факте: «Заказ оплачен», «Клиент сменил адрес». Отправитель не знает, кто его обработает, и не ожидает действий. Название — в прошедшем времени.
  • Документ (event-carried state transfer) — событие, которое несёт полное состояние объекта, чтобы получателям не нужно было обращаться за подробностями к источнику.

Частая ошибка — события, которые на деле являются скрытыми командами: «ЗаказНуждаетсяВДоставке» с единственным подписчиком. Это синхронная связанность, переодетая в асинхронность, со всеми минусами обоих миров.

Тонкое или толстое событие

Тонкое событие несёт только идентификатор и тип: {"type": "OrderPaid", "orderId": "42"}. Подписчик сам идёт за деталями в API источника. Плюс — маленький контракт; минус — синхронная зависимость возвращается, а данные к моменту запроса могли уже измениться.

Толстое событие несёт всё, что нужно подписчикам: сумму, состав, адрес. Плюс — подписчики автономны; минус — контракт события шире и его изменения затрагивают больше потребителей. На практике выбирают середину: в событие кладут данные, которые нужны большинству подписчиков, и фиксируют их в контракте.

Как выбрать

Удобно идти от вопросов, а не от технологий.

ВопросСкорее синхронноСкорее асинхронно
Нужен ли ответ, чтобы продолжить?да: проверка лимита, расчёт цены, авторизациянет: уведомления, отчёты, синхронизация справочников
Ждёт ли пользователь результат на экране?да, и результат быстрыйнет, или результат долгий (минуты, часы)
Сколько получателей у информации?одиннесколько, и их число будет расти
Допустима ли временная рассогласованность?нетда, секунды или минуты допустимы
Что будет, если получатель недоступен?операцию можно отклонитьоперацию нельзя потерять
Нагрузка равномерная или пиковая?равномерная, поставщик справляетсяпиковая, нужен буфер

Полезная эвристика: запросы на чтение и проверки — синхронно, изменения, интересные другим системам, — событиями. Сервис принимает заказ по REST, сразу отвечает клиенту «принято» и публикует событие, на которое реагируют остальные.

Гибридные схемы

Асинхронный запрос — ответ

Клиент отправляет запрос в очередь с указанием, куда прислать ответ (reply-to) и идентификатором корреляции (correlation-id). Поставщик обрабатывает запрос и кладёт ответ в указанную очередь. Так работают многие банковские интеграции на IBM MQ. Важно описать таймаут ожидания ответа и поведение при его превышении.

Принять синхронно — обработать асинхронно

API принимает запрос, проверяет его, сохраняет и сразу возвращает 202 Accepted с идентификатором задачи. Обработка идёт в фоне, а клиент узнаёт результат одним из способов:

  • опрос (polling): GET /tasks/{id} с интервалом;
  • обратный вызов (webhook): поставщик сам вызывает API клиента по завершении;
  • событие: клиент подписан на топик результатов;
  • push в интерфейс: WebSocket или Server-Sent Events.

Это стандартное решение для долгих операций: формирование отчётов, импорт файлов, проверки в сторонних системах.

Синхронный фасад над событиями

Для внешних партнёров, которые не умеют работать с брокером, асинхронный процесс оборачивают REST API, а внутри сервисы общаются событиями. Аналитику важно не забыть описать, что видит партнёр в промежуточных состояниях.

Надёжность: о чём договориться заранее

Асинхронная интеграция надёжна только тогда, когда о надёжности договорились явно.

  • Гарантии доставки. Почти всегда это «хотя бы один раз» (at-least-once). «Ровно один раз» на уровне бизнес-эффекта достигается идемпотентной обработкой на стороне получателя.
  • Порядок. Если важно, чтобы события по одному заказу обрабатывались по порядку, в Kafka их отправляют с одним ключом (id заказа), тогда они попадут в одну партицию. Порядок между разными заказами при этом не гарантирован — и обычно не нужен.
  • Ошибки обработки. Что делать с сообщением, которое не удаётся обработать: сколько повторов, с какими паузами, куда его отложить (dead letter queue), кто и как разбирает отложенные.
  • Атомарность публикации. Сохранить данные в БД и отправить событие нужно согласованно. Наивное «сначала commit, потом send» теряет события при сбое; стандартное решение — шаблон Transactional Outbox.
  • Срок жизни сообщений. Сколько хранится сообщение, если получатель недоступен долго.
  • Версионирование. Как меняется схема сообщения, как долго поддерживаются старые версии.

У синхронной интеграции свой список: таймауты на каждом вызове, повторы с экспоненциальной паузой, ограничение числа одновременных запросов, предохранитель (circuit breaker), который перестаёт вызывать упавший сервис и даёт ему восстановиться.

Что фиксировать в постановке

Для синхронной интеграции:

  • контракт в OpenAPI (или WSDL, .proto): операции, структуры, ошибки;
  • ожидаемая нагрузка: запросов в секунду в среднем и в пике;
  • требования ко времени ответа: например, 95% запросов быстрее 300 мс;
  • таймауты и политика повторов на стороне клиента;
  • поведение при недоступности поставщика: отказ пользователю, деградация, данные из кэша.

Для асинхронной интеграции:

  • контракт в AsyncAPI: каналы (топики, очереди), сообщения, заголовки, схемы данных;
  • тип сообщения: команда или событие, кто отправитель и кто получатели;
  • ключ партиционирования и требования к порядку;
  • идентификатор сообщения для дедупликации, идентификатор корреляции для трассировки;
  • политика повторов и dead letter;
  • допустимая задержка доставки: секунды, минуты;
  • объём: сообщений в секунду и средний размер сообщения.

Обязательно — диаграмма последовательности сквозного процесса, где видно, какие шаги синхронные, а какие асинхронные, и где система может оказаться в промежуточном состоянии.

Типичные ошибки

  • Синхронная цепочка через всю систему. Сервис A вызывает B, B вызывает C, C вызывает D — и всё это в рамках запроса пользователя. Любой сбой в конце цепочки становится сбоем для пользователя.
  • Брокер как синхронный вызов. Отправитель кладёт сообщение и блокируется в ожидании ответа без таймаута — получаем минусы обоих подходов.
  • Нет идемпотентности. Повторная доставка приводит к двойному списанию, двойному письму, двойной отгрузке.
  • Нет плана на «ядовитые» сообщения. Одно сообщение, которое всегда падает при обработке, блокирует всю очередь или партицию.
  • События без владельца. Никто не отвечает за схему события, её меняют без согласования, подписчики ломаются.
  • Асинхронность там, где пользователь ждёт ответа. Кнопка «Оплатить» показывает «готово», а через минуту приходит отказ — сценарий нужно было проектировать с промежуточным статусом.

Итог

Синхронная интеграция проще и даёт немедленный результат, но связывает системы по времени и доступности. Асинхронная развязывает системы и выдерживает пики, но требует продуманной надёжности: идемпотентности, повторов, порядка, мониторинга. Зрелая архитектура использует оба подхода: синхронные вызовы — для запросов, на которые нужен ответ здесь и сейчас, события — для распространения изменений между системами.

Синхронные контракты удобно описывать в редакторе OpenAPI, асинхронные — в редакторе AsyncAPI (см. гайд «AsyncAPI онлайн»), а сквозной процесс с синхронными и асинхронными шагами — на диаграмме последовательности PlantUML. О выборе между REST, gRPC и GraphQL — в статье «gRPC, GraphQL и REST».