Введение
Привет, я Михаил Ковалев, Python-разработчик в Точке.
Именно в Точке я впервые столкнулся с микросервисами и конкурентной средой. В предыдущих компаниях мне не приходилось с ними работать. Не то чтобы я жил в уверенности, что Монолит исполнит все мои желания, просто таковы были особенности проектов.
Разумеется, у меня не получилось сразу усвоить все боевые нюансы работы в новых условиях, и по пути я наступил на немалое количество граблей, а мои ревьюверы — да пребудут с ними терпение и внимательность — в последний момент уберегали меня от настойчивого желания отстрелить себе какую-нибудь конечность.
С тех пор я прошёл непростой путь, и теперь многие вещи кажутся элементарными. Но можно только гадать, сколько разработчиков идёт по тому же пути и совершают те же ошибки.
Конечно, в интернете есть много различной информации, но зачастую там только сухая теория, и не всегда можно понять, как её использовать на практике. У меня возникла идея написать свой цикл статей. Возможно, мой опыт кому-то поможет.
Все примеры будут на Python. Однако это будет полезно всем, кто имеет небольшой опыт разработки для конкурентной среды. А вставки кода на Python можете рассматривать как обычный псевдокод ????
Мы разберемся, как у наших сервисов получается не запутаться в непрерывном потоке информации — как получаемой от клиентов, так и передаваемой между собой. В этой статье я широкими мазками опишу, как рядовой разработчик видит инфраструктуру Точки, с какими проблемами мы регулярно сталкиваемся и почему нам так важна консистентность данных.
Цех разработки Точки
Как я уже писал выше, у нас в Точке микросервисная архитектура. Каждый сервис запускается в нескольких параллельно работающих экземплярах. Это нужно для распределения нагрузки и большей отказоустойчивости. Сервисы создаются разными командами разработки, и каждая команда сама выбирает стек приложения, правила оформления кода, флоу для релиза и многое другое.
Кроме сервисов на Python, у нас есть сервисы на PHP, Java, GO и т.д. Даже внутри Python-комьюнити нет единого стандарта: кто-то пишет на Django, кто-то предпочитает Flask или FastAPI, а кто-то комбинирует различные инструменты.
Подобная децентрализация очень удобна и полезна: мы не тратим время на глобальные холивары и процессуальные проволочки, и к каждой задаче можем выбрать наиболее подходящий инструмент.
Однако, все эти сервисы не существуют каждый в своём изолированном пространстве: им надо как-то общаться между собой. Давайте посмотрим, как это происходит.
Для взаимодействия сервисов существует два основных способа:
Взаимодействие через HTTP.
Сервисная шина предприятия (Enterprise Service Bus или просто ESB).
Не думаю, что первый способ нуждается в особых пояснениях, поэтому сразу перейду ко второму.
ESB выполняет роль почтового сервиса: шина принимает и доставляет сообщения. Она снимает с других сервисов множество обязанностей, и это одно из ее главных преимуществ.
Например, сервис, управляющий платежами, не хранит информацию о том, какие сервисы надо уведомить об изменении статуса платежа. Он просто забрасывает сообщение в шину. Если кто-то из адресатов в данный момент недоступен, сервис-отправитель не заботится о том, чтобы послать уведомление повторно: за него это сделает ESB. А ещё шина следит за маршрутами сообщений, чтобы сервис, отвечающий за рассылку email-ов пользователям, не мог отправлять запросы на создание платежей.
Мы ещё вернёмся к ESB в следующих статьях, когда будем рассматривать особенности отправки и обработки сообщений.
Итак, код написан, тесты пройдены, ветка замержена в мастер, контейнеры с приложением подняты. И вот тут начинаются всякие нежданчики:
каждый экземпляр в любой момент может начать обрабатывать данные, которые уже обрабатывает другой экземпляр;
одновременно пришли два запроса с противоположными требованиями: например, на создание платежа и блокировку счета;
запрос дошел до адресата, а ответ отправителю уже нет — например, из-за проблем с сетью;
или ответ может быть и дошел, а отправителя уже нет, потому что к нему пришёл OOM Killer.
Но нам надо несмотря ни на что осуществлять электронный документооборот, формируя и рассылая клиентам всевозможные договоры и отчётности, запрашивая справки и выписки из внешних источников. При этом надо быть готовыми к тому, что внешние источники (да и наши внутренние, чего греха таить) могут быть недоступны, и важно не забыть к ним обратиться при возобновлении доступа. Я уже не говорю про такие важные вещи как денежные переводы и отображение данных в интернет-банке.
И это только верхушка айсберга. Глаза разбегаются от всего того, за чем нужно успевать следить. И наша задача — обеспечить максимально возможную надёжность работы в условиях, когда что угодно может пойти не так. Все мы люди, и никто не застрахован от опечаток, ошибочной трактовки требований задач и т.д.
Поэтому нам крайне важна консистентность данных во всех наших сервисах, ведь при её нарушении мы рискуем как минимум нарваться на немаленькие такие штрафы при очередной сдаче отчётности, а как максимум – потерять лояльность наших клиентов. ????
Вот так выглядит для разработчика поле задач и ответственности. В следующей статье я расскажу про поддержание консистентности в пределах одного сервиса, а после выйдем и на межсервисный уровень.
А пока напишите в комментариях, живёте ли вы в монолите или в микросервисах? Есть ли у вас проблемы, связанные с конкурентностью? Как вы справляетесь с ними?
murkin-kot
Понятно, что с переходом на микросервисы у вас появилось много неожиданных проблем, но разве ради неожиданных проблем всё это затевалось? Как минимум, в тексте я не обнаружил указания на преимущества вашего решения, то есть пока всё было исключительно ради проблем.
mixon271 Автор
Разумеется, это затевалось не ради неожиданных проблем :slightly_smiling_face:
Цель всего цикла статей -- рассказать о наших подходах к решению определённого круга проблем. И в самом начале я решил очертить круг проблем и почему они вообще возникают
Цель данной статьи не в том, чтобы показать преимущества или недостатки микросервисов. Её цель -- описать реальность, в которой работают наши разработчики