1. Проблема синхронного взаимодействия: почему REST не всегда эффективен
Проблема синхронного взаимодействия: почему REST не всегда эффективен
Представьте, что вы пришли в кофейню в утренний час пик. Вы делаете заказ, кассир принимает оплату, поворачивается к бариста и… замирает. Он стоит неподвижно, глядя на то, как готовится ваш капучино, и игнорирует растущую очередь недовольных клиентов. Только когда бариста передаст ему стаканчик, кассир отдаст его вам и скажет следующему клиенту: «Свободная касса!». Звучит как абсурдный сценарий для бизнеса, не так ли? Однако именно так прямо сейчас общаются между собой миллионы программных компонентов в интернете.
Чтобы понять, зачем IT-гигантам понадобились сложные системы вроде Kafka и RabbitMQ, мы должны сначала осознать боль, которую они решают. И эта боль кроется в самом популярном способе общения программ — синхронном взаимодействии.
Иллюзия простоты: как работает REST
Когда мы разбиваем большое приложение на микросервисы (маленькие независимые программы), им нужно как-то обмениваться данными. Самый интуитивный и распространенный стандарт для этого — REST API поверх протокола HTTP.
Его логика предельно проста и похожа на телефонный разговор:
Такой подход называется синхронным взаимодействием. Ключевое слово здесь — ждет (в программировании это называется блокировкой).
Пока Сервис ожидает ответа, поток выполнения, выделивший память и ресурсы процессора под этот запрос, простаивает. Если Сервис отвечает за 10 миллисекунд — мы не замечаем проблемы. Но современные архитектуры редко состоят из двух сервисов.
!Синхронная цепочка микросервисов
Анатомия катастрофы: каскадные сбои
Давайте рассмотрим классический пример из электронной коммерции (e-commerce). Пользователь нажимает кнопку «Оплатить». Что происходит под капотом?
Возникает жесткая связность (tight coupling). Сервис заказов физически не может завершить свою работу, пока не отработают все остальные звенья цепи.
А теперь представим, что наступила «Черная пятница». Нагрузка выросла в 10 раз. Банковский шлюз начал «тормозить» и отвечать не за 0.1 секунды, а за 5 секунд.
Что произойдет с нашей системой?
Мы столкнулись с эффектом домино, или каскадным сбоем. Один медленный компонент на самом дне архитектуры утянул за собой абсолютно всю систему.
Почему масштабирование не спасает
Первая мысль инженера, столкнувшегося с нехваткой ресурсов: «Давайте добавим больше серверов!».
В синхронной архитектуре это часто превращается в сжигание денег. Если Сервис оплаты является узким горлышком (bottleneck) и не справляется с нагрузкой, добавление еще десяти копий Сервиса заказов никак не решит проблему. Новые Сервисы заказов просто быстрее забьют Сервис оплаты запросами, и система рухнет еще стремительнее.
Более того, при синхронном REST-взаимодействии мы теряем данные в случае сбоя. Если Сервис склада ушел на перезагрузку ровно в тот момент, когда Сервис заказов пытался списать купленный телевизор, запрос выдаст ошибку. Телевизор оплачен, но со склада не списан. Чтобы этого избежать, разработчикам приходится писать сложную логику повторных попыток (retry), что еще сильнее нагружает сеть.
Смена парадигмы
Вернемся к нашей кофейне. Как эта проблема решена в реальной жизни? Кассир принимает ваш заказ, берет деньги, пишет ваше имя на стаканчике и ставит его в специальную зону для бариста. Затем он сразу же поворачивается к следующему клиенту. Вы отходите в сторону и ждете, пока бариста не выкрикнет ваше имя.
Кассир и бариста разорвали жесткую связь. Они работают в своем собственном темпе, а стаканчик с заказом выступает в роли сообщения, оставленного в буфере.
Это и есть суть асинхронного взаимодействия, к которому мы перейдем в следующей главе. Чтобы реализовать такую схему в IT, нам понадобится надежный посредник — тот самый буфер, который сохранит заказ, даже если бариста временно отошел. Этот посредник называется брокером сообщений.