Станут ли микросервисы архитектурой будущего

Меня зовут Михаил Бродский, я Lead Software Engineer, Consultant в GlobalLogic (Харьков). В сфере IT уже более 7 лет. Занимался проектированием, разработкой информационных систем и их внедрением. Сейчас возглавляю проект, связанный с сетевой безопасностью. Занимаюсь повышением эффективности процесса разработки с помощью виртуализации, разработкой и анализом архитектурных решений, а также реализацией программной функциональности.

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

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

Начнем со следующего примера: представим себе систему продажи железнодорожных билетов несколько десятилетий назад. Каким образом можно было купить билет? Существовало несколько вариантов: пойти в кассу и отстоять очередь, заказать билет по телефону, приобрести его через знакомых либо через сервисный центр. Эта система работала отлично (если не брать во внимание длину очереди и скорость обслуживания): в любой момент времени доступ к системе имели несколько кассиров, обеспечивая безупречное функционирование этой модели.

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

Микросервисы

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

Высокоуровневая схема микросервисной архитектуры

Почему мы использовали именно микросервисы?

В первую очередь из-за повышения скорости работы модели. При разделении монолитной системы на независимые элементы такую систему становится легче разрабатывать, ее можно «расскейлить», распараллелить: несколько команд смогут работать над разными микросервисами, при этом каждый микросервис будет иметь свой pipeline, continuous integration, continuous delivery. Таким образом, скорость работы системы повышается.

Безопасность и стабильность. После распределения модели на несколько микросервисов появляются некоторые дополнительные возможности. Например, представим себе скоростной суперкар. Что произойдет, если мы не будем контролировать его скорость? Он разобьется. Если мы имеем дело с «умной» машиной, ее датчики и контроллеры помогут нам отследить скорость, контролировать ее, управлять транспортным средством. Это позволяет повысить безопасность работы.

Это же относится и к работе с микросервисами: мы можем применять некоторые паттерны стабильности. Например, паттерн Timeout: если один из микросервисов не отвечает, мы можем быстро отключиться по тайм-ауту, показав пользователю, что этот сервис недоступен. Можно также применить паттерн Circuit Breaker («Предохранитель») — здесь уместна аналогия с домашней сетью электроприборов: при резком скачке напряжения барьер, контролирующий сетевое напряжение, отключает систему на некоторое время и после тайм-аута проверяет, вернулось ли напряжение в норму, можем ли мы подключить устройства к сети. Если нет, после определенного периода покоя проверка выполняется снова. Также возможно применение паттерна переборок. При распределении монолита между несколькими микросервисами становится возможным независимое применение таких паттернов.

Таким образом, если у нас есть определенное число независимых элементов, используя облачное решение, мы можем настроить правильные метрики, и сервис можно будет очень легко масштабировать в каждом облачном решении (в AWS это CloudWatch).

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

Три основные группы микросервисов

Существует три основные группы микросервисов:

  • Stateless — сервисы без сохранения состояния.
  • Persistence — сервисы, которые сохраняют свое состояние.
  • Aggregators — агрегаторы.

Stateless

Такие микросервисы не имеют никакой зависимости от storage (cache и прочее) и определяют очень простые действия. Пример — конвертация из одной валюты в другую. Очень быстрое действие, нет зависимости от других сервисов и от дискового пространства. Их легко заменить и легко масштабировать.

Persistence

Другой тип микросервисов — сохраняющие свое состояние. К ним относятся сервисы, которые сохраняют зависимость от storage, у них есть операции записи/чтения, их точно так же легко масштабировать, если есть зависимость от диска, если разделены операции, применив паттерн CQRS. В двух словах: у нас есть запрос на чтение и запрос на запись. Мы можем распределить эти модели — за каждую модель будет отвечать определенный микросервис. Таким образом, мы можем масштабировать такую модель на независимые микросервисы. В первую очередь при этом мы зависим от других сервисов. Если взять Amazon или Netflix, один сервис вызывает огромное количество других сервисов. Кроме зависимости от других сервисов, есть зависимость от сети. Появляется запись в базу данных и, конечно же, Disk I/O Dependence.

Aggregators

Рассмотрим пример архитектуры, которую мы применяли для реализации группы паттерна микросервиса «Агрегатор».

Задача состояла в следующем: мы работали с безопасностью сети и безопасностью клиентских точек (endpoints). В нашем распоряжении было определенное количество брандмауэров. Брандмауэр аналогичен роутеру, только намного «умнее»: существует программная оболочка, определенная логика, осуществляющая анализ сети, и в зависимости от повышения сетевой активности и появления атак мы можем ограничивать сетевой доступ для клиентских точек, а также их взаимодействие между собой. Необходимо было собирать телеметрию, логи данных устройств для последующей обработки и генерации уведомлений. На самом деле эта задача является подзадачей создаваемого нами огромного решения.

Изначально наша архитектура выглядела так:

Микросервис-агрегатор с блокирующим (синхронным) запросом

Было устройство — брандмауэр, использовался микросервис, ответственный за сбор телеметрии (Device Data Collector MS), микросервис, ответственный за обработку полученной информации (Device Data Processor), а также микросервис, ответственный за генерацию уведомлений (Alert Generator).

Почему это называется подсистемой? Причина в том, что все эти брандмауэры интегрировались в систему, доступ к которой можно получать из облака. Представим, что есть распределенная сеть, безопасность которой необходимо повысить. Есть dashboard, к которому подключены брандмауэры, находящиеся в разных локациях. Вы заказываете брандмауэр, приезжают администраторы, настраивают его, после чего перед вами встает задача: как же управлять этим оборудованием? Очень легко! Вы регистрируете его в нашей системе, можете управлять им через «админку» устройства из любой точки мира и настраивать систему. Вопрос: почему я не могу получить доступ к «админке», находясь в другом месте? Некоторые правила блокируются, определенная конфигурация доступна только при подключении к устройству. Как раз этот сервис реализовывал процесс сбора информации, генерации уведомлений и их отображения на центральном dashboard.

В нашей системе коммуникация между устройством и микросервисом, собирающим информацию, была синхронной — и это была самая большая проблема. Такой подход работал до момента, пока не поменялись требования.

Изначально мы должны были поддерживать 5000 устройств. Каждые 5 минут — не очень часто — каждое из этих устройств отправляло какую-то информацию о своей работе, и это составляло 17 запросов в секунду (тоже не очень много). За два часа работы собиралось 35 мегабайт, эту информацию мы сохраняли в базу данных, хранили ее 2 часа, и, если что-то произошло, запускалось задание, анализировалась информация, вызывался следующий микросервис, генерирующий уведомление, которое отображалось на доске уведомлений.

Все было отлично, пока не поменялись требования. Вместо 5000 устройств от нас потребовалось подключение 50 000. И тут мы поняли, что возникнут проблемы. Собрали все метрики, проанализировали информацию — проблема заключалась в том, что устройство блокировалось на время обработки всей цепочки сообщения. Как можно было решить эту проблему?

Микросервис-агрегатор с асинхронным ответом

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

И тут возникает несколько вопросов.

Первый. Что произойдет с системой, если сообщений будет очень много и очередь не будет справляться с этой нагрузкой? Существует несколько решений:

  • Заблокировать отправителя до того момента, пока не будет обработано сообщение.
  • Блокировать очередь либо при увеличении сохранять ее часть на жесткий диск.
  • Применить метод контролирования обратного потока (используется в TCP-протоколе): создается буфер складирования сообщений, при переполнении которого поступление сообщений блокируется.

Второй. Что произойдет, если «отвалится» сеть и очередь перестанет быть доступной? Можно кешировать сообщения, проверять доступность сети: если доступа нет, мы кешируем сообщения, а когда сеть появляется, накопленные сообщения отправляются в очередь и обрабатываются.

Третий. Что произойдет, если Device Data Processor не сможет выбирать сообщения (как мы описывали ранее)? А что, если устройству будет необходим результат обработки сообщения?

Один из вариантов решения: устройство отправило сообщение с ID, сообщение было обработано, и микросервис создает вторую очередь для ответа, о существовании которой будет «знать» только это устройство. Когда будет обработано сообщение из второй очереди (ответ), эта очередь будет удалена.

Если мы не хотим использовать асинхронную обработку — с очередями, брокерами сообщений и прочим, — существует еще один подход, о котором будет рассказано ниже.

Системы обработки сообщений

Рассмотрим основные понятия.

Message

Первое — сообщение. Можно провести аналогию: HTTP-запрос.

Message Broker & Message Queue

«Посредники» — брокер сообщений или очередь. К ним относятся TIBCO Enterprise Message Service, WebSphere, webMethods, RabbitMQ, ActiveMQ, HornetQ, NATS, Apache Kafka. Это нечто среднее между RPS и реляционной базой данных. Другими словами, это базы данных, адаптированные исключительно для работы с очередью.
Приведу несколько различий между этими двумя подходами.

Direct Communication

TCP, one-to-one

Один продюсер и один подписчик. Пример — TCP-протокол. Используется обработка контроля обратного потока. Посредник не используется. Если сообщение вдруг не было обработано, мы можем переслать пакет еще раз и обработать его.

UDP, ZeroMQ (over TCP), Webhooks (HTTP Callbacks)

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

Message Brokers

JMS, AMQP, RabbitMQ, ActiveMQ, HornetQ, Qpid, TIBCO Enterprise Message Service, IBM MQ, Azure Service Bus, Google Cloud Pub/Sub, SQS — их особенность в том, что при необходимости сообщить подписчику о нештатной ситуации (например, сообщение не было обработано, доставлено) используются посредники. Далее я расскажу, чем отличается Kafka от остальных существующих подходов.

Logs-Based Message Brokers

Посредники, работающие на основании логов.

HTTP Callbacks with Payload

Схема паттерна HTTP Callbacks with Payload

Этот метод объединяет посредника (Hub), подписчика (Subscriber), создающего сообщение, и издателя (Publisher). Например, подписчик заявляет, что хочет получать новости от определенного сервиса, и запрашивает ссылку на посредника (Hub): отправляет запрос, получает ссылку, отправляет подзапрос посреднику, происходит валидация подписчика. После этого, когда создаются дополнительные новости, посылается запрос на Hub о появлении новой информации, и посредник идентифицирует нового подписчика. Это стандартный паттерн — вы можете применить его, если нет необходимости использовать какого-то определенного посредника (например, Kafka).

Logs-Based Message Storage

Рассмотрим системы отправки сообщений на основании логов. К ним относятся Kafka, Distribute Logs от Twitter и реализация от Azure.

Схема работы систем сообщений на основе логов

У нас есть несколько очередей, несколько топиков, несколько разделов (Partitions). Есть продюсеры, которые создают сообщения и сохраняют их в очереди. Какую проблему решает этот подход?

Что произойдет с обработанным сообщением? Оно будет удалено из очереди. Новые подписчики не увидят сообщений, добавленных ранее. Что, если мы объединим два ранее упомянутых подхода? Мы сможем хранить сообщения некоторое время, делать запросы, кроме того, сообщения не будут удаляться из этих очередей. Допустим, надо протестировать нашу систему. Что для этого нужно? Необходимо подключить все устройства, сгенерировать информацию, загрузить ее в очередь и посмотреть, как будет происходить обработка. После того как обработка закончится, все сообщения будут удалены. На основании логов сообщения не будут удаляться из очередей — они будут храниться не постоянно, а в течение определенного времени (это не реляционная база данных). Мы можем не пересоздавать эти сообщения, а прогнать обработку и посмотреть на результат. Как это работает?

Итак, у нас имеются очереди, добавляются продюсеры и потребители (Consumers). Когда какой-то из потребителей считывает сообщение, присваивается сдвиг (Offset): сколько сообщений было прочитано и какое сообщение было прочитано. Допустим, если мы прочитали определенное количество сообщений в разделе 0, мы записываем, чему равен сдвиг для этого раздела (например, 6):

Offset for P0 = 6

Это означает, что клиент считал и обработал определенное количество логов (сообщений в очереди). Если приходит другой потребитель и пытается считать эти сообщения, посредник уже знает, что эта система не обработала ни одного сообщения, и отдает команду считать все соответствующие сообщения.

Объем релиза

В теории графов существует интересная формула, которая подсчитывает оптимальное число изменений в релизе: n — количество изменений (количество ребер в графе; соединения между ребрами — это изменения, зависимости) (рисунок 6). Политика Netflix — минимизировать число изменений, повысив частоту релизов. Если вы включаете в релиз большое число изменений, растет число дефектов, которые необходимо будет обнаружить и исправить. Например, при числе изменений n = 5 мы получаем 10 зависимостей. Минимизируя число изменений, мы уменьшаем число дефектов в системе.

Расчет оптимального релиз-скоупа

Выводы

В итоге на базе микросервисов мы создали асинхронный компонент, основная задача которого — сбор телеметрии от IoT-девайсов и создание необходимых оповещений (алертов) для уведомления пользователя. Каждые 2 часа мы получаем и сохраняем до 400 мегабайт информации.

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

  • получили практику предварительной оценки и разработки прототипов микросервисов;
  • с нуля создали асинхронный компонент, который позволил оптимизировать работу всей системы для большего количества подключенных устройств (до 50 000 устройств в одном регионе).
Что дальше? Мы смотрим в будущее с оптимизмом: переход от монолитной системы к микросервисам не только даст бизнесу и пользователям лучший user experience, но и позволит создавать целый набор различных сервисов, которые облегчат работу и жизнь.

Сейчас перед нами стоит задача поддержки и развития этого компонента, впереди еще много работы!

Хотите узнать больше или есть вопросы? Пишите в комментарии, в LinkedIn и подписывайтесь на YouTube-канал.

Похожие статьи:
Пропонуємо список питань, які ставлять українським розробникам на технічних співбесідах з PHP. Звісно, він не є вичерпним, проте має...
Смартфон Huawei Honor 7 был официально представлен на российском рынке и вскоре поступит в продажу в России. Как указано в официальном...
Компания Lenovo объявила о выпуске на российском рынке двух новых смартфонов серии VIBE, оснащенных процессорами MediaTek. Lenovo VIBE P1m -...
Сервіс анонімного пошуку роботи Джин представив результати рейтингу найсильнішого HR-бренда серед українських ІТ-компаній...
Image source[Про автора: Сергій Іщеряков — керівник громадської організації «Фундація Розвитку Інновацій» та програми...
Яндекс.Метрика