Гай Харрисон, Эндрю Маршал, Чарльз Кастер

Архитектура распределённых

транзакционных приложений

Исходный текст, 2023 / Русский перевод, 2023


Оглавление

Глава 2
Распределение слоёв приложения
Регионы и зоны
Практически все современные распределённые приложения можно разделить на два основных слоя:

Прикладной (или вычислительный) слой
В первую очередь отвечает за логику приложений и взаимодействие с конечными пользователями

Персистентный слой (или база данных)
Отвечает за хранение долгосрочных данных приложения и предоставление целостного представления о состоянии приложения вычислительному слою
Все основные публичные облачные платформы предоставляют вычислительные ресурсы, организованные по регионам и зонам. Регион представляет собой широкий географический район, определяющий физическое местоположение для развертывания приложений. Регионам часто дают расплывчатые названия (например, europewest1). Однако на практике регион обычно находится в пределах страны и практически всегда в конкретном городе. Например, регион Google Cloud asia-northeast1 находится в Токио, а регион asia-northeast2 — в Осаке.
Каждый регион содержит несколько зон — обычно не менее трёх зон на регион. Зоны обычно представляют собой конкретный центр обработки данных внутри региона, который не имеет общих точек отказа с другими зонами. Таким образом, зоны представляют собой региональную резервную систему, позволяющую региону продолжать функционировать даже в случае отказа отдельного центра обработки данных.
На рисунке 2-1 показаны три региона в публичном облаке, каждый из которых имеет три зоны. В типичном публичном облаке существует множество регионов — например, Google в настоящее время поддерживает 35 регионов.

Рисунок 2-1. Регионы и зоны в типичном публичном облаке

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

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

На рисунке 2-2 показана такая схема:

Рисунок 2-2. Регионы и зоны в типичном публичном облаке

Хотя Kubernetes автоматизирует большую часть этих механизмов, мы всё равно иногда будем использовать глобальный балансировщик нагрузки в межрегиональных конфигурациях.
Микросервисы
В архитектуре микросервисов функциональные возможности приложений разрабатываются и развёртываются независимо друг от друга. Эти микросервисы взаимодействуют для обеспечения общей функциональности приложения. Разработка микросервисов основана на некоторых фундаментальных принципах:
У микросервиса должна быть единственная задача.
Микросервис должен стремиться реализовывать только одну функциональность. Сервис должен быть «экспертом» в этой одной функции и не должен заниматься какими-либо другими задачами.
Микросервис должен быть независимым от других сервисов.
Микросервис не должен зависеть напрямую от другого сервиса и должен быть тестируемым и развёртываемым независимо от всех остальных сервисов.
Микросервис должен быть достаточно малым, чтобы его могла разрабатывать одна команда.
«Правило двух пицц», разработанное в Amazon, предусматривает, что каждая команда должна быть достаточно маленькой, чтобы её можно было накормить двумя пиццами. Основной принцип заключается в том, что микросервис должен быть достаточно маленьким, чтобы избежать зависимостей между командами.
Микросервис должен быть эфемерным (временным).
Микросервис не должен сохранять состояние таким образом, чтобы другой микросервис не мог взять его на себя в случае сбоя. На практике это просто означает, что каждое взаимодействие с микросервисом полностью независимо от прошлых или будущих взаимодействий.
Реализация микросервиса должна быть непрозрачной.
Внутренняя реализация микросервиса не должна иметь никакого отношения к другим сервисам.
Контейнеры
Микросервисы часто развёртываются в контейнерах. Контейнеры упаковывают все необходимые для выполнения микросервиса программные зависимости. Это включает в себя минимальный образ операционной системы вместе с любыми библиотеками и другими зависимостями, необходимыми для поддержки кода микросервиса.

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

Кластер Kubernetes состоит из нескольких узлов, на каждом из которых размещён один или несколько контейнеров.
Kubernetes предоставляет высокоуровневую абстракцию, называемую «Под» (Pod, капсула или отсек). Он состоит из одного или нескольких контейнеров с общим хранилищем, сетью и пространствами имен. Хотя, в некоторых случаях, микросервис можно реализовать в одном контейнере, модульность улучшается за счёт использования контейнеров, которые реализуют общие сервисы для нескольких Подов.
Поды также часто включают в себя побочные контейнеры (sidecar containers). Побочный контейнер обычно предоставляет общие сервисы для ряда Подов, такие как мониторинг, оповещение, ведение журналов и абстракцию сети.
Сервисы Kubernetes состоят из логического набора Подов. Kubernetes может балансировать нагрузку между Подами, перезапускать отказавшие Поды и предоставлять сервису выделенную точку доступа DNS. Таким образом, Поды могут отказывать, но сервис продолжит работу. На рисунке 2-3 показана связь между контейнерами, Подами и сервисами.

Рисунок 2-3. Сервисы Kubernetes, поды и контейнеры

Kubernetes также предоставляет Подам и сервисам ресурсы хранения. Постоянные тома (Persistent Volumes) предоставляют ресурсы хранения, которые смогут пережить сбои или отключения. Эфемерные тома (Ephemeral Volumes) предоставляют хранилище, которое не сохраняется после завершения жизненного цикла Пода.
Также Kubernetes предоставляет все необходимые конфигурации для сборки сервисов в стэк приложения. Это включает в себя распределение Подов по нескольким узлам в кластере Kubernetes, управление «секретами», такими как API-ключи и пароли, ограничения на использование ресурсов и практики масштабирования.
Мы не можем подробно изучить Kubernetes в этом отчёте, но существует множество ресурсов, которые смогут помочь. O'Reilly предлагает книги (например, "Production Kubernetes" Джоша Россо и др.) и учебные практикумы (например, подготовительный курс "Certified Kubernetes Application Developer"), которые могут быть полезны.
Межрегиональный Kubernetes
Кластеры Kubernetes обычно ограничиваются одним регионом, хотя кластер может охватывать несколько зон доступности. Технически возможно развернуть «растянутый» кластер Kubernetes с узлами в нескольких регионах, но задержка, которая возникает в результате этого, скорее всего будет неприемлемой.

Следовательно, каждый регион обычно имеет свой собственный кластер Kubernetes и использует глобальный балансировщик нагрузки для маршрутизации запросов к соответствующему кластеру. Решение выглядит несколько похожим на «традиционный» шаблон, показанный на рисунке 2-2, за исключением того, что глобальный балансировщик нагрузки маршрутизирует запросы не к сервисам, выставленным внутри виртуальной машины, а к сервисам, выставленным в кластере Kubernetes. Рисунок 2-4 иллюстрирует такую конфигурацию.

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

Рисунок 2-4. Межрегиональный Kubernetes

Управление событиями
В распредёленном приложении часто требуется надёжный слой обмена сообщениями или управления событиями, позволяющий микросервисам координировать свою работу. Часто это принимает форму очередей задач, которые позволяют надёжно и асинхронно выполнять работу, требующую взаимодействия нескольких микросервисов.
Возможно использование базы данных в качестве единственного общего коммуникационного слоя между микросервисами, но это может увеличить нагрузку на то, что часто уже является критическим компонентом задержки транзакций и является неэффективным, поскольку эти сообщения не требуют долгосрочного хранения.
Поэтому многие распределённые приложения используют распределённую службу обмена сообщениями для поддержки запросов сообщениями между сервисами. Такая служба обмена сообщениями гарантирует, что сообщения будут сохраняться при сбоях Pod или узлов, не требуя долгосрочного хранения после полной обработки рабочего запроса.
Наиболее широко используемой в современных приложениях службой распределённых сообщений является Apache Kafka, хотя также широко используются исходно облачные службы обмена сообщениями, такие как Google Cloud Pub/Sub.
Во многих распределённых системах временные сообщения между сервисами не передаются между регионами. Регионы функционируют независимо друг от друга; если регион перестает работать, сообщения внутри этого региона могут быть потеряны или недоступны, пока регион не будет восстановлен. В таком сценарии система обмена сообщениями работает в каждом регионе в изолированном режиме, что позволяет поддерживаать низкую задержку и обеспечивать высокую производительность.
Однако в случае регионального сбоя, если другой регион должен продолжить незавершённую работу из отказавшего региона, решение обмена сообщениями должно охватывать несколько регионов. В таких случаях увеличивается задержка, так как сообщения должны быть реплицированы (дублированы) между регионами перед обработкой. В некоторых случаях можно реализовать компромиссное решение, при котором сообщения реплицируются асинхронно между регионами. В этом случае некоторые сообщения могут быть потеряны в случае сбоя региона, но есть надежда, что основная часть работающих задач будет передана в новый регион. На рисунке 2-5 показан сценарий, в котором каждый регион имеет свою службу обмена сообщениями Kafka, и две службы поддерживают синхронизацию через репликацию.

Рисунок 2-5. Реплицирование службы обмена сообщениями Kafka в разных регионах

Бессерверное развёртывание
Kubernetes быстро стал «облачной ОС» благодаря своей способности абстрагировать базовую облачную платформу и позволять оркестрировать контейнеры таким образом, чтобы доставлять распределённые масштабируемые приложения. Однако настройка и управление Kubernetes далеки от простоты и в некоторых случаях могут стать сложнее, чем само приложение.

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

Бессерверные платформы имеют сниженные административные накладные расходы, что может уменьшить затраты на персонал. Они также могут снизить затраты на хостинг: развёртывание Kubernetes обычно влечёт определённые затраты на хостинг, даже когда он не используется, тогда как оплата за бессерверное развёртывание обычно основана на фактическом использовании, а не на максимальном.

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

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

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

Алгоритм взаимодействия разработчика с бессерверной платформой очень прост:
  1. Разработчик реализует сервис, используя поддерживаемый язык для платформы.
  2. Разработчик вызывает команду бессерверной платформы для развёртывания сервиса. Эта команда обычно упаковывает сервис в Docker контейнер и разворачивает его на облачной инфраструктуре.
  3. Масштабирование и управление сервисом осуществляются через интерфейс управления платформы.

Хотя бессерверность часто привлекательна с точки зрения издержек, автоматическое масштабирование может привести к неожиданным расходам. Большинство бессерверных платформ предоставляют механизмы для оповещений о расходах и установки лимитов для предотвращения неоправданного масштабирования и перерасхода средств.
Существует несколько популярных бессерверных платформ:

Google Cloud Run
Контейнерно-ориентированная бессерверная платформа Docker, доступная в Google Cloud Platform.

Amazon AWS Lambda и AWS Fargate
Lambda — это предложение типа «функция как услуга», которое подходит для очень кратковременных вызовов функций, а Fargate — это контейнеризированное решение, подобное Cloud Run.

Microsoft Azure Container Apps
Контейнеризованная бессерверная платформа, доступная в облачной платформе Azure.

Knative
Платформа на базе Kubernetes, предлагающая бессерверную работу в кластере Kubernetes. Это позволяет предприятиям предоставлять бессерверную работу в частном облаке на базе Kubernetes и упрощает развёртывание разработчикам, имеющим доступ к кластерам Kubernetes.
Межрегиональная бессерверная система
Большинство облачных платформ поддерживают бессерверное развёртывание только в определённых регионах. Чтобы создать межрегиональное (multiregion) бессерверное решение, нам понадобится развернуть бессерверное решение в каждом регионе, а затем использовать глобальный балансировщик нагрузки для маршрутизации трафика в соответствующий регион. Глобальный балансировщик нагрузки работает точно так же, как показано на рисунках 2-2 и 2-4, направляя запросы к сервису в регионе, который может удовлетворить запрос.
Подводим итоги
Современные облачные платформы и решения контейнеризации предоставляют эффективные с точки зрения затрат и рисков механизмы развёртывания глобально распределённых приложений. Уже существующие (legacy) архитектуры приложений могут быть развёрнуты на виртуальных машинах в облаке с использованием глобальных балансировщиков нагрузки для обеспечения отказоустойчивости и распределения рабочей нагрузки. Docker Контейнеры, оркестрируемые Kubernetes, предоставляют более надёжные и эффективные средства для развёртывания микросервисных приложений. Бессерверные платформы предоставляют ещё более высокий уровень абстракции и упрощения, хотя с некоторыми ограничениями в гибкости.

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