Читать книгу Программирование Cloud Native. Микросервисы, Docker и Kubernetes - Иван Портянкин - Страница 23
2. Микросервисы
Микросервисы по Мартину Фаулеру
ОглавлениеМартин Фаулер знаменит своими, как правило, очень успешными попытками найти структуру и систему в динамичном, хаотичном мире программирования, архитектуры, хранения данных, и особенно в мире сложных, высоконагруженных, распределенных приложений эпохи Интернета. Конечно же он попытался проанализировать и упорядочить волну популярности микросервисов.
Если попытаться вчитаться в цитату Мартина в начале этой главы, становится понятно, что микросервисы – явление вне рамок известных ранее архитектур, они не совсем укладываются в стандарты, и довольно разнообразно применяются на практике. Тем не менее, характерные черты микросервисов, используемые Мартином, очень хороши для первого, вводного анализа этой архитектуры, что нам для знакомства и нужно. Давайте посмотрим.
Сервисы как компоненты системы
Компонент (component) – одна из основополагающих идей в разработке программ. Идея проста – компонент отвечает за определенную функцию, интерфейс API, или целый бизнес модуль. Главное – компонент можно убрать из системы, заменив на другой, со сходными характеристиками, без больших изменений для этой системы. Замена компонентов целиком встречается не так часто, но независимое обновление (upgrade) компонента без потери работоспособности системы в целом очень важно для легкости ее обновления и поддержки. Классический пример – качественная библиотека (к примеру, JAR), модуль, или пакет, в зависимости от языка, которую можно подключить и использовать в своем приложении.
Микросервисы – это использование компонентов (зависимостей, библиотек и модулей) своего приложения не внутри одного, единого процесса приложения, а запуск их в отдельных, собственных процессах, и доступ к ним не напрямую, а через сетевые вызовы RESTful API / GRPC.
Логика в микросервисах, но не в сети и посредниках
Данная черта микросервисов противопоставляется предыдущим версиям распределенных систем (часто их называют SOA, service oriented architecture). В архитектуре SOA большая роль отводилась компонентам-«посредникам» (middleware), таким как сложные очереди сообщений (message queue), адаптерам данных, и общему набору логики между различными компонентами системы, называемому интеграционной шиной ESB (enterprise service bus). Зачастую львиная доля сложной логики всей системы находится не в самих компонентах, а именно в шине ESB.
В мире микросервисов это явно выраженный анти-шаблон (anti-pattern). Вся логика в обязательном порядке должна находиться внутри микросервисов («разумные» точки доступа к сервису, smalt endpoint), даже если она повторяется в различных микросервисах. Сеть (и любой механизм передачи данных в целом), должна только передать данные, но не обладать никакой логикой (как еще говорят, «неразумные» линии передачи данных, dumb pipes).
Децентрализованное хранение данных
В монолитной архитектуре данные зачастую хранятся в единой, обычно реляционной, базе данных, со строго определенной схемой и обширным использованием запросов SQL. Микросервисы снова требуют противоположного подхода – в идеале никакого разделения данных, особенно через единую базу данных. Весь доступ, любые изменения данных происходят только через программный интерфейс API, предоставляемый микросервисом.
Микросервисы, таким образом, свободны в своем выборе способа хранения данных. Это может быть любой тип базы данных, например, нереляционная база NoSQL, база на основе документов, графовая база, или что-либо еще. Конечно, возникает значительная избыточность данных, но хранение данных не так дорого, а автономность и независимость развития каждого микросервиса, в идеале, должна окупить избыточность данных.
Культура автоматизации
Учитывая лавинообразный рост зависимостей между компонентами, и независимый набор технологий в каждом из микросервисов, надежный, автоматизированный выпуск системы и ее быстрые обновления – это жизненно необходимый элемент микросервисной архитектуры. Ручная сборка и управление микросервисами практически невозможны.
Непрерывная интеграция и тестирование (CI, continuous integration), непрерывное развертывание новых версий (CD, continuous delivery) – это обязательный атрибут команд, создающих микросервисы. Здесь огромную помощь оказывают контейнеры, со своей быстрой, легковесной виртуализацией, и «чистым», изолированным пространством, в котором запускаются микросервисы, а развертывание значительно упрощает оркестратор Kubernetes, предоставляя мощный декларативный подход. Их мы тщательно рассмотрим в следующих главах, представить микросервисы без них уже практически невозможно.
Расчет на постоянные сбои
Микросервисы приводят к распределенной, гетерогенной, динамичной системе. Ей просто предначертано испытывать постоянные сбои, из-за сетевых вызовов, нагрузок, несовместимости версий, и многого другого. Этого не избежать, и на это нужно просто рассчитывать изначально, планируя каждый вызов к остальным компонентам системы как изначально не способный гарантировать успешное завершение.
Планировать сбои можно не только на этапе дизайна кода и отдельных тестов, но и более активным способом. Так, компания Netflix использует «обезьяну с гранатой» (Chaos Monkey, перевод не дословный, но наша фраза на русском языке вполне подходит!), отдельный сервис, постоянно выводящий из строя случайный набор микросервисов. Остальные компоненты системы должны подстроиться к ситуации, и правильно реагировать на сбои системы, проводя разумную политику отката своих действий, или же используя вспомогательные компоненты и альтернативные способы исполнения логики.
Небольшая команда, акцент на своей бизнес-области
Микросервисы как правило разрабатываются небольшой командой (известен практически анекдот от компании Amazon, что команда, работающая над микросервисом, всегда сможет насытиться двумя пиццами. Пиццы скорее всего большие, возможно, с разными наполнителями – так что в итоге это может быть и чуть больше 10 человек). Идея состоит в том, что небольшой команде проще координировать свои действия и быстро проводить новые идеи и изменения в жизнь.
Второй аспект – команда больше не состоит только из программистов серверной части (back end), пользовательских интерфейсов (front end, или UI), или же системных администраторов. Они работают вместе над одной, сконцентрированной бизнес-областью. Классический пример – онлайн магазин, и сервисы по доставке, оформлению заказов, и проведению платежей. В мире микросервисов все эти сервисы разрабатываются, выпускаются в эксплуатацию, и развертываются отдельными командами, в своем ритме.
Более того, у каждой команды могут быть отдельные требования, процесс, планирование, и даже заказчики – внутренние или внешние. Общаются эти команды независимо, и микросервисы работают независимо, предоставляя четко очерченные программные интерфейсы API.
Интересно, что подобное требование лежит за пределами чисто технической, программисткой зоны ответственности – такое распределение обязанностей должно лежать в основе всей организации целиком, с множеством автономных зон ответственности, без единого, «жесткого» центра управления. Как гласит известный закон Конвея (Conway’s law), структура организации обязательно проявит себя в планировании и производстве любых продуктов и сервисов этой организации.
Владение продуктом, а не реализация проекта
Зачастую, в классической модели разработки, команда программистов и дизайнеров реализует проект, исполняя поставленные перед ней требования (requirements). Что происходит после того, как проект «сдан»? Команда начинает работать над чем-то новым, переходит в другие проекты, а в случае субподрядчиков и контрактных услуг, работа с ней может быть полностью закончена. Обслуживание системы, исправление ошибок, ее обновление нередко попадает совершенно в другие руки.
Парадигма микросервисов предпочитает, чтобы команда разработчиков «владела» (own) своим проектом в начале его дизайна, в процессе создания и настройки микросервисов, и обязательно после формальной сдачи системы, непрерывно продолжая работу над ее эволюцией. Постоянный контакт с пользователями системы, наблюдение за ней в условиях реальной эксплуатации позволяет максимально эффективно понять, как ее развить и улучшить. Как вариант подобной системы популярна концепция DevOps (developer + operations, совместная работа как над разработкой, так и над поддержкой и эксплуатацией системы). Оркестратор Kubernetes сам по себе подталкивает работать в концепции DevOps.
Эволюционный дизайн
«Эволюционный дизайн (evolutionary design) – попытка добиться результата не с одной попытки, чудесным образом дающей идеальное попадание, а в результате многих постепенных изменений, приводящих к эволюции дизайна и продукта»,
Джошуа Кириевски (Joshua Kerievsky), адепт Agile/XP-разработки.
Микросервисы дают большую свободу приверженцам эволюционного дизайна, противникам тщательного предварительного анализа системы, и попытки понять ее целиком еще до того, как написана первая строка кода, и сделан выбор первых технологий для ее реализации. Вместо классического планирования и дизайна всей необходимой функциональности системы, создается первое «примитивное» приближение (primitive whole). Система выглядит практически реально, но умеет очень мало. Пользователи получают возможность впервые посмотреть на систему, и конечно же, немедленно просят поменять или улучшить ее, не целиком, но значительно. В подобных итерациях и рождается истина.
Если создание монолитной системы немедленно приводит к сильными зависимостям от выбранной технологии, системы хранения данных и библиотек, то добавка новых микросервисов, и даже быстрое удаление «неудачных» или «временных» микросервисов становятся делом техники. У разработчиков развязаны руки – они могут свободно обращаться с выбором технологий, а также передвигать границы микросервисов – если изначально они были выбраны неверно, в первых итерациях их легко объединить или разбить.
Краткие итоги
Первый обзор типичных черт микросервисов внушает оптимизм. Четкое разделение обязанностей, абсолютная свобода в выборе технологий и хранении данных, точно настроенное масштабирование, и помощь контейнеров и Kubernetes выглядят очень заманчиво. Однако не стоит забывать о сложности распределенной системы, присущей любой, даже самой простой такой системе. Сетевые вызовы часто приносят с собой неизвестные заранее задержки (latency), и для быстрой работы приходится выполнять работу асинхронно (asynchronous), заранее не зная, в какой момент приходит ответ от остальных компонентов системы.
Сложность асинхронной, распределенной, динамично развивающейся системы в разы превышает сложность единого, монолитного приложения. Разделить систему на микросервисы зачастую очень сложно, если в компании «все делают все», и нет четко очерченных бизнес-областей. В таких случаях вместо микросервисов получается «распределенный монолит» (distributed monolith), неповоротливая, сложная система, вместо которой мог бы иметь место более эффективный монолит.
Не забывайте, что два остальных столпа концепции Cloud Native совершенно безразличны к битве монолитов, микросервисов и SOA! Вы можете совершенно спокойно развернуть классическое, монолитное приложение Enterprise Java в контейнере под управлением Kubernetes, получив многие преимущества без излишней сложности.
Один из давних соратников Мартина, Сэм Ньюмен (Sam Newman), написал отдельную, и кстати, не слишком «толстую», книгу по микросервисам, которую можно рекомендовать для чтения, и существует ее перевод на русский язык.