Как правило, трудно бывает «продать» руководству чистую миграцию, без улучшения функциональности или каких-то других преимуществ для бизнеса. Вполне логично, что в русле долгосрочной модернизации хотелось бы улучшить существующие сервисы и добавить новые. Поэтому в рамках модернизации очень часто также ставится задача заложить основу и внедрить лучшие практики для дальнейшего создания современных приложений.
Мигрируя на новую платформу все больше и больше сервисов, вводя новые сервисы и в целом двигаясь в сторону микросервисной архитектуры, мы сталкиваемся со следующими крупными задачами:
Как автоматизировать развертывание и эксплуатацию большого числа сервисов.
Как устранить двойную запись.
Как организовать длительные бизнес-процессы надежным и масштабируемым образом.
В старой системе этих задач, скорее всего, не было. Поэтому рассмотрим, как можно решить их с помощью определенных шаблонов проектирования и технологий.
Задача 1: крупномасштабная эксплуатация управляемых событиями сервисов
Отделяя от старого монолитного приложения все больше и больше сервисов, а также создавая новые сервисы под возникающие бизнес-задачи, мы все четче видим, что надо автоматизировать процессы развертывания, отката, размещения, управления конфигурациями, обновления и самовосстановления. И здесь на помощь приходит Kubernetes, идеальное решение для построения крупномасштабной системы на основе микросервисов. См. рис. 1.
При работе с управляемыми событиями сервисами довольно быстро становится ясно, что нам нужна автоматизация и интеграция с управляемой событиями инфраструктурой, и здесь пригодится Apache Kafka и другие проекты из экосистемы Kafka. Более того, можно использовать Kubernetes Operators, чтобы автоматизировать управление Kafka и следующими вспомогательными сервисами:
Apicurio Registry – оператор для управления Apicurio Schema Registry на платформе Kubernetes.
Strimzi – операторы для декларативного управления кластерами Kafka и Kafka Connect на платформе Kubernetes.
KEDA (Kubernetes Event-Driven Autoscaling) – автомасштабирование рабочих нагрузок для наращивания или сокращения сервисов-потребители Kafka в зависимости от нагрузки. Когда генерация сообщений возрастает и задержка превышает заданный порог, соответствующий Kubernetes-оператор запускает больше потребителей, вплоть до числа разделов.
Knative Eventing – уровень абстрагирования для управления событиями на основе Apache Kafka.
Примечание. Kubernetes – это не только целевая платформа для модернизации приложений, но и основа для развития этих приложений в крупномасштабную управляемую событиями архитектуру. Это достигается за счет автоматизации пользовательских рабочих нагрузок, нагрузок Kafka и других инструментов из экосистемы Kafka. Причем, все эти компоненты вовсе не обязательно развертывать у себя. Можно задействовать предлагаемые Red Hat управляемый сервис Apache Kafka или сервис Schema Registry, автоматически привязав их к своему приложению с помощью Kubernetes-операторов. Кластер Kafka с несколькими зонами доступности (multi-availability-zone, multi-AZ) создается на Red Hat OpenShift Streams for Apache Kafka меньше чем за минуту и абсолютно бесплатно (в течение пробного периода) – просто попробуйте и дайте фидбек, чтобы мы могли сделать его лучше.
Теперь посмотрим, как решать две оставшиеся задачи модернизации с помощью шаблонов.
Задача 2: устранение двойной записи
Создав пару микросервисов, быстро понимаешь, что самое сложное в них – это данные. В рамках своей бизнес-логики микросервисам надо часто обновлять свои локальные хранилища данных. Одновременно, им надо уведомлять о произошедших изменениях другие сервисы. Эта проблема не так остра в старом мире монолитных приложений и распределенных транзакций, но как избежать или нейтрализовать ее в условиях микросервисов и облаков? Ответ: сначала изменять только один из двух ресурсов – базу данных, и только затем обновлять второй ресурс, вроде Apache Kafka, как показано на рис. 2.
Используя шаблон Outbox вместе с Debezium, можно сделать так, что сервисы будут выполнять обе задачи – обновлять данные и уведомлять другие сервисы – безопасно и согласованно. Вместо того, чтобы напрямую отправлять сообщение в Kafka при обновлении базы данных, сервис использует одну и ту же транзакцию, чтобы 1) выполнить обычное обновление в БД и 2) вставить сообщение в специальную таблицу Outbox в своей базе данных.
После того как транзакция попала в лог БД, Debezium берет сообщение из Outbox и отправляет его в Apache Kafka. Чем хорош такой подход? За счет синхронной записи в БД в рамках одной транзакции реализуется семантика «читай то, что сам записал». То есть, если обратиться к сервису сразу после того, как он записал данные в БД, он вернет именно эту свежую запись. Одновременно мы получаем надежную асинхронную передачу данных другим сервисам через Apache Kafka.
Шаблон Outbox – это надежный и проверенный способ избежать двойной записи в масштабируемых микросервисах, управляемых событиями. Этот шаблон элегантно решает проблему взаимодействия между сервисами, поскольку теперь все участники этого взаимодействия, включая Kafka, не обязаны быть в онлайне одновременно. Вполне вероятно, что Outbox станет одним из ключевых шаблонов проектирования масштабируемых микросервисов, управляемых событиями.
Задача 3: Длительные транзакции
Шаблон Outbox отлично решает проблему взаимодействия между сервисами, однако его недостаточно для длительных распределенных бизнес-транзакций. Под такими транзакциями мы понимаем выполнение множества операций с участием нескольких микросервисов по принципу «все или ничего», например, организацию поездки с бронированием авиабилетов и проживания. Эта проблема может вообще не возникать в старых приложениях и монолитных архитектурах, где координация между модулями осуществляется в рамках одного процесса и одного транзакционного контекста. Но в распределенном мире требуется другой подхода, как показано на рис.3.
Шаблон Saga предлагает решать этой проблему путем разделения бизнес-транзакции верхнего уровня на серию из нескольких локальных подтранзакций с базой данных, которые выполняются сервисами-участниками. Как правило, есть два способа реализации распределенных saga-транзакций:
Хореография – в рамках этого подхода один из сервисов-участников отправляет сообщение следующему участнику после того, как выполнит свою локальную транзакцию.
Оркестрация – здесь есть некий центральный сервис, который осуществляет координацию и вызов соответствующих сервисов-участников.
Связь между сервисами может быть либо синхронной (HTTP или gRPC), либо асинхронной, путем обмена сообщениями через тот же Apache Kafka.
Примечание. Подробнее см. Шаблоны распределенных транзакций в микросервисных архитектурах.
Самое интересное, что шаблон Saga можно реализовать с помощью Debezium, Apache Kafka и шаблона Outbox. Используя эти инструменты, можно организовать оркестрацию и получить единый центр, чтобы управлять потоками подтранзакций и контролировать состояние транзакции верхнего уровня. Кроме того, оркестрацию можно объединить с асинхронной коммуникацией, чтобы сервис-координатор не зависел от доступности сервисов-исполнителей и даже от доступности Kafka. Таким образом, мы получаем лучшее из двух миров: и оркестрацию, и асинхронную, неблокирующую, параллельную коммуникацию с сервисами-участниками без темпоральной привязки.
Комбинация шаблонов Outbox и Saga отлично подходит для управляемой событиями реализации длительных бизнес-транзакций в мире распределенных сервисов. Подробное см. статью Saga Orchestration for Microservices Using the Outbox Pattern, а также пример реализации Saga на GitHub.
Заключение
Шаблоны Strangler, Outbox и Saga полезны как при миграции старых приложений, так для построения современных управляемых событиями сервисов.
Kubernetes, Apache Kafka и Debezium – эти проекты с открытым кодом, не только стали стандартами де-факто в своих областях, но и предлагают обширные экосистемы сопутствующих инструментов и лучших практик, чтобы эффективно применять их для построения современных стандартизированных решений.
Как уже говорилось в первой части этой статьи, современные программные системы чем-то похожи на наши города: они меняют с течением времени и строятся на месте своих предшественников. Используя проверенные практикой шаблоны проектирования, стандартизированные инструменты и открытые экосистемы, вы сможете строить долговечные решения, которые будут расти и меняться вместе с вами.