GrabDuck

Сложности с микросервисами - Dots and Brackets: Блог

:

Кривые обученияО достоинствах микросервисов говорят много и с удовольствием. Немного реже упоминают то, что с ними не так. Проблема в том, что микросервисы, как и любой другой инструмент, хорошо решают одни задачи, абсолютно бесполезны в других, и в любом случае подразумевают какие-то компромиссы.

На случай, если кто забыл, микросервисы — это паттерн проектирования приложения, в котором оно существует как набор независимых сервисов. Каждый из них выполняет определенный круг задач и общается с соседями по какому-нибудь легковесному протоколу. При такой архитектуре приложение получается  более живучее, его проще масштабировать, можно чаще обновлять, и легко раздавать на задачи командам разработчиков.

Но,

О чём стоит помнить, отдавая душу микросервисам.

Разделить приложение на сервисы — сложно.

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

Микросервисы требуют больше ресурсов.

Если раньше сервисы общались внутри монолита в рамках одного процесса, то теперь им придётся общаться через сеть, что априори медленнее. Контейнеризация, сама по себе быстрая, тоже стоит ресурсов. Достанется и дискам. Так как у сервисов свои базы данных, то в общем случае какой-то срез данных будет дублироваться. Например, сервису генерации отчётов не практично лазить по сети к сервису платежей и грузить его запросами. Он лучше заведет своё зеркало платежей, и будет грузить сам себя. И вот они, гигабайты!

Распределенное приложение тяжелее запустить.

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

С распределенным приложением иногда тяжело сказать, оно работает, или нет.

В монолитном приложении это просто: если в task manager есть процесс, а в окне нажимаются кнопки — значит работает. Но в микросервисах нормальная ситуация, когда какой-то сервис временно не работает. Сколько их должно упасть, чтобы сказать, что у нас проблема? Как именно определить, где именно проблема? Если маленький сервис по отправке писем «ваш заказ выслан» задумался «о вечном», как мы можем заметить это? Всё же остальное работает.

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

Появляется новый класс ошибок и проблем.

То, что раньше работало просто так, теперь может стать проблемой. Например:

Распределенные транзакции.

Допустим, пользователь нажимает кнопку «купить», в ответ на что мы уменьшаем его баланс, добавляем запись в таблицу заказов и уменьшаем количество доступного товара на складе. Разумеется, это всё делается в одной большой транзакции:

А теперь представьте, что за склад, ордера и аккаунты отвечают три разных сервиса, что вполне логично. Допустим, я отправил три запроса этим сервисам. Два успешно вернулись сразу, а третий, со склада, — нет. Значит нужно отправлять еще запросы, чтобы откатить первые два. А кто-то из них ведь тоже может не вернуться. А вообще, запрос на склад упал до того, как дошёл до него, или уже на обратном пути? А если второе, он успешно отработал, или нет?

Каким образом в такой ситуации можно остаться в здоровой психике и гарантировать согласованность данных?

Эпичный кирдык.

В более серьёзных источниках упоминается как catastrophic failover. Этот вообще прекрасен. Если сервисы общаются между собой через очереди сообщений (и не только), то в какой-то момент в очередь может попасть неправильно собранное сообщение, которое отправит сервис-подписчик в страну вечного рестарта. Но за получение сообщения-то он не отчитался, так что оно отправится к следующему сервису в списке. И следующему, пока они не закончатся. В какой-то момент очередь сообщений переполнится, начинает тормозить и присоединится к павшим товарищам. Наконец, заметив, что отправлять сообщения больше некому, оставшиеся сервисы дружно совершают сипуку.

Реальный ход событий будет, конечно, несколько иным, но суть остаётся прежней — какое-то некорректное сообщение при должном стечении обстоятельств может последовательно убить приложение целиком. Найти же, что было в самом начале, очень проблематично.

Нужно учить новые инструменты.

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

Безопасность.

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

Но что делать со стаей маленьких сервисов, разбросанных по облакам? У того же сервиса для работы со складом не будет собственного UI. Он, конечно, может доверять всем входящим запросам, но тогда его нужно прятать в скрытой сети, чтобы только «свои» могли эти запросы отправлять. А «свои» — это N целей для взлома. Если хоть одна из них скомпрометирована — сервис-склад тоже попал.

Можно, конечно, подавать сервису на вход какой-нибудь токен. Но как этот токен проверить, и как узнать, какие именно операции он разрешает? Дать складу копии таблиц с аккаунтами и ролями — это новые проблемы и лишние ресурсы. Разрешить ему обращаться за помощью к какому-нибудь сервису авторизации — это повышает связность сервисов и вносит дополнительный вектор атаки. Как правильно — не совсем очевидно.

Мораль.

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