Микросервисы: Как обеспечить масштабируемость вашего приложения

Никола Драгони1,5, Иван Ланезе2, Стефан Тордал Ларсен1, Мануэль Маццара3, Руслан Мустафин3, Лариса Сафина3,4
1 Технический университет Дании
2 Болонский университет / INRIA
3 Университет Иннополис, Российская Федерация
4 Университет Южной Дании
5 Университет Эребру, Швеция
ndra@dtu.dk, stephan@thordal.io, ivan.lanese@gmail.com, {n.safina, m.mazzara, r.mustafin}@innopolis.ru

Аннотация

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

1. Введение

История языков программирования и парадигм в последние несколько десятилетий характеризовалась прогрессивным сдвигом в сторону распределённости, модульности и слабой связанности с целью повышения повторного использования кода и надёжности [4]. Эта необходимость была продиктована потребностью в повышении качества программного обеспечения не только в приложениях, критичных к безопасности и финансам, но и в более распространённых готовых программных пакетах.

Сервис-ориентированные архитектуры (SOA) можно рассматривать как шаг в этом направлении, где потребность в повторном использовании кода и надёжности сочеталась с необходимостью взаимодействия между разнородными информационными системами, возможно принадлежащими разным компаниям. Это породило идею сервиса как программной сущности, взаимодействующей с другими программными сущностями посредством передачи сообщений с использованием стандартных форматов данных и протоколов (например, XML, SOAP и HTTP) и хорошо определённых интерфейсов.

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

Микросервисная архитектура [5] строится на нескольких основных принципах:

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

2. Масштабируемость

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

Распределённость

Распределённость не является оригинальной чертой микросервисов, поскольку, например, SOA также распределены. Однако благодаря своему малому размеру микросервисы доводят эту характеристику до крайности: каждая бизнес-возможность, включая её функциональности и связанные данные, реализуется независимым сервисом, который может быть развёрнут на хосте, возможно, отличном от хостов других микросервисов того же приложения. В качестве первого результата это вызывает естественное распределение рабочей нагрузки, которое может сделать систему значительно более эффективной, чем монолит [2]. Распределённость также делает микросервисные архитектуры высокодоступными, поскольку отказ одного микросервиса не обязательно приводит к отказу других микросервисов. Распределённость также может использовать локальность и размещать сервисы ближе к клиентам, которых они обслуживают, что приводит к лучшей географической масштабируемости [13, 2].

Неравномерное масштабирование

Обычно, когда монолитные архитектуры подвергаются растущей нагрузке, трудно определить, какие компоненты системы фактически затронуты, поскольку система выполняется в рамках одного процесса. Это означает, что, хотя нагрузку может испытывать только один компонент, весь монолит должен будет масштабироваться, например, путём репликации или вертикального масштабирования. Даже если известно, какой компонент испытывает нагрузку, его трудно масштабировать изолированно. Аналогичные рассуждения могут применяться к SOA: сервисы в SOA могут быть большими, часто скрывая за сервис-ориентированным интерфейсом целое монолитное приложение, следовательно, они могут масштабироваться только с большой гранулярностью. То же самое относится к случаям, когда масштабируемость необходима для реализации высокой доступности: если только некоторые компоненты монолита или крупного сервиса должны быть высокодоступными, то весь монолит/крупный сервис должен быть высокодоступным.

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

Пример. Упрощённая иллюстрация, демонстрирующая преимущества масштабирования микросервисной архитектуры по сравнению с монолитной архитектурой, приведена на Рисунке 1. Обе системы реализуют компонентизацию программного обеспечения: монолит использует обычные программные компоненты, такие как библиотеки, а микросервисная архитектура использует микросервисы, т.е. Компоненту x соответствует Сервис x. В этом сценарии Компонента/Сервис 1 испытывает нагрузку, требующую его репликации в 3 экземпляра. Поскольку монолит развёртывается как единый процесс, необходимо реплицировать всю систему, включая все 3 компонента, на трёх хостах. В микросервисной архитектуре можно просто реплицировать один сервис, испытывающий нагрузку, что приводит к выделению гораздо меньшего количества хостов. Балансировщики нагрузки присутствуют в обеих системах для распределения нагрузки между репликами. Однако в монолите балансировщик распределяет только внешние запросы, тогда как в случае микросервисной архитектуры он распределяет как внешние запросы, так и внутренние запросы между различными микросервисами, что позволяет осуществлять более равномерную балансировку нагрузки. Это происходит, в частности, когда внешние запросы могут вызывать вычисления, которые являются ресурсоёмкими, возможно, неравномерно: балансировка только внешних запросов может быть недостаточной.

Опора на Предметно-ориентированное проектирование (Domain-Driven Design) [7] и стремление к сильной связности (high cohesion) означает, что растущая нагрузка обычно будет ограничена подмножеством связанных микросервисов [14]. Конкретные микросервисы, фактически испытывающие растущую нагрузку, могут затем масштабироваться, например, путём их перемещения на более производительные хосты или репликации по кластеру или в облаке.

Аналогичный аргумент применим к технологии, принятой для реализации каждого микросервиса: технология, используемая для построения микросервиса, может быть выбрана для его наилучшей производительности. Например, вычислениятельно интенсивный микросервис может быть реализован на C++, в то время как микросервис, требующий работы со сложными типами, может быть реализован на языке с развитой системой типов, таком как Haskell. Это невозможно в монолитной архитектуре, которая обычно привязана к единой платформе и языку.

Переносимость

Микросервисы обычно упаковываются в контейнеры, как предоставляется, например, Docker [10] или аналогичными технологиями. Контейнер включает микросервис и всю его среду (библиотеки, базы данных, ...) в единую сущность, которую можно легко развернуть на любой платформе, поддерживающей выбранную технологию контейнеров, обеспечивая единообразное поведение на разнородных платформах (хосты, центры обработки данных и облачные провайдеры) и изоляцию по отношению к другим контейнерам (например, разные микросервисы могут использовать разные версии одной и той же библиотеки без конфликтов). Переносимость, обеспечиваемая контейнерами, позволяет легко перемещать или реплицировать микросервис на разнородных платформах. Микросервисные архитектуры, следовательно, идеальны для горизонтального масштабирования системы, поскольку микросервисы можно легко перемещать на вновь выделенные хосты.

Эластичность (Гибкость)

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

Рисунок 1: Масштабирование в микросервисах против монолитной архитектуры

Рисунок 1: Масштабирование в микросервисах против монолитной архитектуры

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

Доступность

Мы уже выделили некоторые способы, которыми микросервисы могут способствовать доступности, но здесь мы суммируем основные аспекты, связанные с этой темой. В общем, высокая доступность достигается за счёт способности микросервисов реплицироваться и распределяться по центрам обработки данных и географическим расстояниям, что позволяет им распределять нагрузку и справляться с отказывающим и перегруженным оборудованием. Другой важный аспект касается обновления и эволюции системы: в то время как обновление монолитного приложения требует его остановки и повторного развёртывания, что вызывает возможно длительный простой, реплицируемость и независимость позволяют микросервисам решить эту проблему. Во-первых, обновление микросервисной архитектуры обычно затрагивает только один или несколько микросервисов, связанных с бизнес-возможностью, которую необходимо исправить или улучшить, следовательно, сокращая время развёртывания. Кроме того, старая и новая версия одного и того же микросервиса могут работать параллельно, например, старая завершает выполняемые запросы, а новая обрабатывает новые запросы. Старая версия может быть удалена, когда её работа завершена. Отметим, что контейнеризация избегает вмешательств между двумя версиями сервиса, например, позволяя им полагаться на разные версии одной и той же библиотеки. Это естественным образом приводит к меньшим, но более частым обновлениям, в направлении непрерывного развёртывания.

Надёжность

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

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

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

Не панацея

Вышеприведённое описание должно прояснить, как микросервисы предоставляют естественный способ достижения масштабируемости, включая доступность и отказоустойчивость. Однако это не даётся бесплатно: наличие множества независимых сущностей вводит некоторую дополнительную административную нагрузку, в частности для развёртывания, администрирования, мониторинга и безопасности. Хотя существуют подходы для смягчения этих проблем (но всё ещё далёкие от удовлетворительных, по крайней мере, в отношении безопасности), это означает, что иногда микросервисы не являются решением. Вышеприведённое описание должно помочь понять преимущества микросервисов и решить, являются ли они хорошей техникой для рассматриваемой проблемы. Мы обсудим ниже некоторые конкретные случаи, чтобы дополнительно прояснить вопрос.

3. Выбор языка

Хотя микросервисные архитектуры могут быть построены с использованием широкого спектра технологий, возможно, объединённых в одну систему, мы считаем, что использование специализированного языка может упростить разработку микросервисных систем. Наш опыт основан на языке Jolie — единственном известном нам языке, который изначально поддерживает микросервисные архитектуры. Хотя мы отсылаем к [12] для подробного описания языка Jolie, мы напомним здесь его особенности, которые полезны для нашего обсуждения, и в частности те, которые связаны с характеристиками микросервисов, описанными выше.

В Jolie каждая программа является микросервисом, и её описание состоит из поведения и некоторой информации о развёртывании, касающейся того, как он может взаимодействовать с другими микросервисами. В этом смысле распределённость присуща языку, поскольку каждый микросервис делает свои функциональности доступными по определённому URL и может быть вызван другими микросервисами. Неравномерная масштабируемость может быть легко достигнута: можно запускать новые экземпляры микросервисов и легко перенаправлять запросы от одного микросервиса к балансировщику нагрузки: цели вызовов микросервисов являются объектами первого класса в Jolie, следовательно, их можно легко и динамически изменять. Примитивы для архитектурной композиции, такие как перенаправление или агрегация, также помогают в этом направлении. Поддержка, которую Jolie предоставляет для этих основных аспектов, и тот факт, что он полностью поддерживает парадигму микросервисов, гарантируют, что другие соответствующие свойства также сохраняются. Действительно, Jolie не имеет специальной языковой поддержки для контейнеризации или эластичности, и действительно, как такая языковая поддержка может быть предоставлена и будет ли она полезна для языка или нет, является активной темой обсуждения в сообществе Jolie. Однако микросервисы Jolie могут быть легко развёрнуты в контейнерах Docker [10] или в облаке, следовательно, сказанное выше в этом отношении справедливо и для микросервисов Jolie. Мы завершаем этот раздел примечанием о надёжности: Jolie предоставляет продвинутые механизмы для уведомления о неисправностях между различными микросервисами [8]. Эти механизмы позволяют детально контролировать, распространяются ли неисправности от одного микросервиса к взаимодействующим с ним. Действительно, их непропагация позволяет избежать каскадных ошибок, но тщательное распространение позволяет восстановить

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

4. Приложения

Микросервисная архитектура имеет идеальное применение там, где требуются масштабируемость, минимализм и связность. В настоящее время несколько компаний перемещают свои монолитные архитектуры на микросервисы, чтобы пожинать плоды масштабируемости. Netflix — один из таких примеров — они были одними из пионеров, перешедших от монолита к микросервисам [6]. Теперь лежащая в основе микросервисная архитектура Netflix позволяет им эффективно масштабироваться и обслуживать миллионы пользователей каждый день. Переносимость использовалась Netflix не только для облегчения развёртывания и перемещения, но и для автоматизации развёртывания: инструмент развёртывания, который знал, как развернуть контейнер, мог развернуть его независимо от того, что было внутри. Микросервисная архитектура также позволила Netflix улучшить надёжность и доступность, запустив сервис под названием Chaos Monkey [3] для непрерывного тестирования неисправностей в системе. Chaos Monkey, как следует из названия, вызывает хаос внутри системы, отключая различные сервисы случайным образом и наблюдая, как система адаптируется к этим сбоям. Несмотря на то, что Chaos Monkey создаёт неисправности в работающей системе, он всё же работает в ограниченный период времени, когда инженеры способны отреагировать на возможный сбой.

Наша исследовательская группа изучила другое применение архитектурного стиля, использующее гибкость языка программирования Jolie: возникающую область умных зданий, с прицелом на IoT и умные города. Комнаты здания были оборудованы рядом устройств и датчиков для захвата фундаментальных параметров, определяющих благополучие, комфорт и пригодность для жизни людей, таких как температура, влажность и освещённость [17, 18]. Цель состоит в мониторинге и оптимизации рабочих условий, а программная инфраструктура, тесно связанная с аппаратным обеспечением, использует Jolie и микросервисы.

Система спроектирована с разделением логики на небольшие компоненты. Каждый сервис отвечает за управление одним датчиком или одной конкретной функцией. Некоторые сервисы написаны на Java для более простого взаимодействия с устройствами, а Jolie работает как оркестратор для всего набора сервисов. В этом подходе есть несколько преимуществ. Прежде всего, повторное использование. Система поддерживает разные виды датчиков, но центральная логика извлечения данных остаётся неизменной, даже когда датчики добавляются, удаляются или заменяются. Во-вторых, читаемость кода, поскольку сервисы являются простыми компонентами с простой логикой и чётким соглашением об именовании. Сочетание читаемости и повторного использования также приводит к снижению количества ошибок. Масштабируемость, минимализм и связность необходимы из-за необходимости подключения датчиков и исполнительных механизмов, их удаления, добавления новых, управления неисправностями и мониторинга динамической природы инфраструктуры, особенно когда мобильные устройства и "вещи" являются частью системы. Эластичность контекста должна управляться

частично автоматически, частично через человеческое вмешательство с центральной панели управления, следовательно, требуя необходимости оркестрации сервисов и управления workflow.

5. Микросервисы и не только

Микросервисная архитектура не строится на пустом месте и связана с устоявшимися парадигмами, такими как ООП и SOA. В [5] представлен всесторонний обзор последних разработок в области микросервисной архитектуры с акцентом на эволюционные аспекты больше, чем на революционные. Изложение там предназначено помочь читателю в понимании микросервисов, их происхождения и их возможного будущего.

Микросервисы могут быть построены с использованием широкого спектра технологий, объединённых в одну систему. Однако мы поддерживаем идею, что подход, основанный на языке, может упростить разработку. Jolie — единственный известный нам язык, который изначально поддерживает парадигму. Другие workflow-языки способны описывать оркестрацию сервисов, например, WS-BPEL [15]. WS-BPEL действительно предоставляет многие функции, необходимые для описания workflow сервисов, плюс аспекты коммуникации (порты, интерфейсы). Динамическая реконфигурация workflow также может быть выражена [9]. Однако WS-BPEL был разработан для высокоуровневой оркестрации, в то время как программирование внутренней логики отдельного микросервиса требует мелкозернистых процедурных конструкций.

Наша исследовательская команда глубоко вовлечена в сообщество микросервисов и активно способствует его более широкому принятию. Как проект с открытым исходным кодом, Jolie уже построил сообщество разработчиков по всему миру — как в промышленности, так и в академических кругах — которое заботится о разработке, непрерывно улучшает его удобство использования и, следовательно, расширяет принятие. Последние разработки и вклады нашей команды: расширение системы типов [16], разработка статической проверки типов [19], добавление более итеративных управляющих структур для поддержки программирования и встроенная автоматическая документация [1]. Эти работы активизировали среду разработки и начали процесс её преобразования в полный комплект, который делает всю концепцию привлекательной для разработчиков и рыночно пригодной для компаний.

Будущее, конечно, не свободно от вызовов. Безопасность парадигмы — это вопрос, почти полностью нетронутый [5]. Пакеты качества коммерческого уровня для разработки ещё далеки от завершения, несмотря на ускорение интереса к этому вопросу. Полностью верифицированное программное обеспечение — это открытая проблема так же, как и для более традиционных моделей разработки. Основная нерешённая проблема — как микросервисы могут интегрироваться с двумя основными возникающими платформами, которые, вероятно, будут доминировать в ближайшем будущем: облаком и Интернетом вещей. В то время как микросервисы кажутся идеальными для работы в облаке благодаря своим свойствам переносимости и эластичности, работа в Интернете вещей всё ещё представляет некоторые трудности. В частности, многие "вещи" имеют низкие вычислительные возможности и представляют более высокие риски с точки зрения безопасности, поскольку их легче скомпрометировать. В качестве примера этого второго пункта просто рассмотрим, что ботнеты, такие как Mirai [11], состоят из "вещей"

(маршрутизаторы, IP-камеры, цифровые видеорегистраторы, ...), которые обычно имеют очень низкую защиту (например, пароли, установленные производителем и никогда не изменяемые). Следовательно, интеграция микросервисов и Интернета вещей сделала бы потребность в специфических решениях безопасности ещё более актуальной.

Литература

  1. A. Bandura, N. Kurilenko, M. Mazzara, V. Rivera, L. Safina, and A. Tchitchigin. Jolie community on the rise. In SOCA, 2016.
  2. A. B. Bondi. Characteristics of scalability and their impact on performance. In WOSP, page 195–203, 2000.
  3. A. Tseithin, C. Bennett. Chaos Monkey Released Into The Wild. http://techblog.netflix.com/2012/07/chaos-monkey-released-into-wild.html, (2012).
  4. E. Santana de Almeida, A. Alvaro, D. Lucredio, V. Cardoso Garcia, and S. Romero de Lemos Meira. Rise project: Towards a robust framework for software reuse. In IRI, pages 48–53, 2004.
  5. N. Dragoni, M. Mazzara, S. Giallorenzo, F. Montesi, A. Lluch Lafuente, R. Mustafin, and L. Safina. Microservices: yesterday, today, and tomorrow. In Present and Ulterior Software Engineering. Springer Berlin Heidelberg, 2017.
  6. M. McGarr, E. Bukoski, B. Moyles. How We Build Code at Netflix. http://techblog.netflix.com/2016/03/how-we-build-code-at-netflix.html, (2016).
  7. E. Evans. Domain-driven design: tackling complexity in the heart of software. Addison-Wesley Professional, 2004.
  8. C. Guidi, I. Lanese, F. Montesi, and G. Zavattaro. Dynamic error handling in service oriented applications. Fundam. Inform., 95(1):73–102, 2009.
  9. M. Mazzara, F. Abouzaid, N. Dragoni, and A. Bhattacharyya. Design, modelling and analysis of a workflow reconfiguration. In International Workshop on Petri Nets and Software Engineering, pages 10–24, 2011.
  10. D. Merkel. Docker: lightweight linux containers for consistent development and deployment. Linux Journal, 2014(239):2, 2014.
  11. Mirai botnet - wikipedia. https://en.wikipedia.org/wiki/Mirai_(malware).
  12. F. Montesi, C. Guidi, and G. Zavattaro. Service-Oriented Programming with Jolie. In Web Services Foundations, pages 81–107. Springer, 2014.
  13. B. Clifford Neuman. Scale in distributed systems. In Readings in Distributed Computing Systems, page 463–489. IEEE Computer Society Press, 1994.
  14. S. Newman. Building Microservices. O'Reilly Media, Inc., 2015.
  15. OASIS. Web Services Business Process Execution Language Version 2.0, 2007. http://docs.oasis-open.org/wsbpel/2.0/OS/wsbpel-v2.0-OS.pdf.
  16. L. Safina, M. Mazzara, F. Montesi, and V. Rivera. Data-driven workflows for microservices (genericity in jolie). In AINA, 2016.
  17. D. Salikhov, K. Khanda, K. Gusmanov, M. Mazzara, and N. Mavridis. Jolie good buildings: Internet of things for smart building infrastructure supporting concurrent apps utilizing distributed microservices. In CCIT, pages 48–53, 2016.
  18. D. Salikhov, K. Khanda, K. Gusmanov, M. Mazzara, and N. Mavridis. Microservice-based iot for smart buildings. In WAINA, 2017.
  19. A. Tchitchigin, L. Safina, M. Mazzara, M. Elwakil, F. Montesi, and V. Rivera. Refinement types in jolie. In Spring/Summer Young Researchers Colloquium on Software Engineering, SYRCoSE, 2016.