Хабрахабр

Книга «Микросервисы. Паттерны разработки и рефакторинга»

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

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

Разработчики таких приложений используют фреймворки и библиотеки, которые упрощают работу с транзакциями. Предлагаем ознакомиться с отрывком «Управление транзакциями в микросервисной архитектуре»
Почти любой запрос, обрабатываемый промышленным приложением, выполняется в рамках транзакции базы данных. А такие фреймворки, как Spring, имеют декларативный механизм. Некоторые инструменты предоставляют императивный API для выполняемого вручную начала, фиксации и отката транзакций. Благодаря этому написание транзакционной бизнес-логики становится довольно простым. Spring поддерживает аннотацию @Transactional, которая автоматически вызывает метод в рамках транзакции.

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

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

4.1.1. Микросервисная архитектура и необходимость в распределенных транзакциях

Представьте, что вы — разработчик в компании FTGO и отвечаете за реализацию системной операции createOrder(). Как было написано в главе 2, эта операция должна убедиться в том, что заказчик может размещать заказы, проверить детали заказа, авторизовать банковскую карту заказчика и создать запись Order в базе данных. Реализация этих действий была бы относительно простой в монолитном приложении. Все данные, необходимые для проверки заказа, уже готовы и доступны. Кроме того, для обеспечения согласованности данных можно было бы использовать ACID-транзакции. Вы могли бы просто указать аннотацию @Transactional для метода сервиса createOrder().

Как видно на рис. Однако выполнить эту операцию в микросервисной архитектуре гораздо сложнее. 1, данные, необходимые операции createOrder(), разбросаны по нескольким сервисам. 4. createOrder() считывает информацию из сервиса Consumer и обновляет содержимое сервисов Order, Kitchen и Accounting.

Поскольку у каждого сервиса есть своя БД, вы должны использовать механизм для согласования данных между ними.

4.1.2. Проблемы с распределенными транзакциями

Традиционный подход к обеспечению согласованности данных между несколькими сервисами, БД или брокерами сообщений заключается в применении распределенных транзакций. Стандартом де-факто для управления распределенными транзакциями является X/Open XA (см. ru.wikipedia.org/wiki/XA). Модель XA использует двухэтапную фиксацию (two-phase commit, 2PC), чтобы гарантировать сохранение или откат всех изменений в транзакции. Для этого требуется, чтобы базы данных, брокеры сообщений, драйверы БД и API обмена сообщениями соответствовали стандарту XA, необходим также механизм межпроцессного взаимодействия, который распространяет глобальные идентификаторы XA-транзакций. Большинство реляционных БД совместимы с XA, равно как и некоторые брокеры сообщений. Например, приложение на основе Java EE может выполнять распределенные транзакции с помощью JTA.

image

Несмотря на внешнюю простоту, распределенные транзакции имеют ряд проблем. Многие современные технологии, включая такие базы данных NoSQL, как MongoDB и Cassandra, их не поддерживают. Распределенные транзакции не поддерживаются и некоторыми современными брокерами сообщений вроде RabbitMQ и Apache Kafka. Так что, если вы решите использовать распределенные транзакции, многие современные инструменты будут вам недоступны.

Чтобы распределенную транзакцию можно было зафиксировать, доступными должны быть все вовлеченные в нее сервисы. Еще одна проблема распределенных транзакций связана с тем, что они представляют собой разновидность синхронного IPC, а это ухудшает доступность. Если в распределенной транзакции участвуют два сервиса с доступностью 99,5 %, общая доступность будет 99 %, что намного меньше. Как описывалось в главе 3, доступность системы — это произведение доступности всех участников транзакции. Эрик Брюер (Eric Brewer) сформулировал CAP-теорему, которая гласит, что система может обладать лишь двумя из следующих трех свойств: согласованность, доступность и устойчивость к разделению (ru.wikipedia.org/wiki/Теорема_CAP). Каждый дополнительный сервис понижает степень доступности. В наши дни архитекторы отдают предпочтение доступным системам, жертвуя согласованностью.

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

4.1.3. Использование шаблона «Повествование» для сохранения согласованности данных

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

Шаблон «Повествование»

См. Обеспечивает согласованность данных между сервисами, используя последовательность локальных транзакций, которые координируются с помощью асинхронных сообщений. microservices.io/patterns/data/saga.html.

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

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

Пример повествования: создание заказа

В этой главе в качестве примера используем повествование Create Order (рис. 4.2). Оно реализует операцию createOrder(). Первая локальная транзакция инициируется внешним запросом создания заказа. Остальные пять транзакций срабатывают одна за другой.

image

Это повествование состоит из следующих локальных транзакций.

Сервис Order. 1. Создает заказ с состоянием APPROVAL_PENDING.

Сервис Consumer. 2. Проверяет, может ли заказчик размещать заказы.

Сервис Kitchen. 3. Проверяет детали заказа и создает заявку с состоянием CREATE_PENDING.

Сервис Accounting. 4. Авторизует банковскую карту заказчика.

Сервис Kitchen. 5. Меняет состояние заявки на AWAITING_ACCEPTANCE.

Сервис Order. 6. Меняет состояние заказа на APPROVED.

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

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

Повествования применяют компенсирующие транзакции для отката изменений

У традиционных ACID-транзакций есть одно прекрасное свойство: бизнес-логика может легко откатить транзакцию, если обнаружится нарушение бизнес-правила. Она просто выполняет команду ROLLBACK, а база данных отменяет все изменения, внесенные до этого момента. К сожалению, повествование нельзя откатить автоматически, поскольку на каждом этапе оно фиксирует изменения в локальной базе данных. Это, к примеру, означает, что в случае неудачной авторизации банковской карты на четвертом этапе повествования Create Order приложение FTGO должно вручную отменить изменения, сделанные на предыдущих трех этапах. Вы должны написать так называемые компенсирующие транзакции.

Необходимо нивелировать последствия от предыдущих n транзакций. Допустим, (n + 1)-я транзакция в повествовании завершилась неудачно. Чтобы компенсировать эффект от первых n этапов, повествование должно выполнить каждую транзакцию Ci в обратном порядке. На концептуальном уровне каждый из этих этапов Ti имеет свою компенсирующую транзакцию Ci, которая отменяет эффект от Ti. 4. Последовательность выглядит так: T1… Tn, Cn… C1 (рис. В данном примере отказывает этап Tn + 1, что требует отмены шагов T1… Tn. 3).

image

Повествование выполняет компенсирующие транзакции в обратном порядке по отношению к исходным: Cn… C1. Здесь действует тот же механизм последовательного выполнения, что и в случае с Ti. Завершение Ci должно инициировать Ci – 1.

Оно может отказать по целому ряду причин. Возьмем, к примеру, повествование Create Order.

Некорректная информация о заказчике, или заказчику не позволено создавать заказы. 1.

Некорректная информация о ресторане, или ресторан не в состоянии принять заказ. 2.

Невозможность авторизовать банковскую карту заказчика. 3.

В табл. В случае сбоя в локальной транзакции механизм координации повествования должен выполнить компенсирующие шаги, которые отклоняют заказ и, возможно, заявку. 1 собраны компенсирующие транзакции для каждого этапа повествования Create Order. 4. Это относится, например, к операциям чтения, таким как verifyConsumerDetails(), или к операции authorizeCreditCard(), все шаги после которой всегда завершаются успешно. Следует отметить, что не всякий этап требует компенсирующей транзакции.

image

В разделе 4.3 вы узнаете, что первые три этапа повествования Create Order называются доступными для компенсации транзакциями, потому что шаги, следующие за ними, могут отказать. Четвертый этап называется поворотной транзакцией, потому что дальнейшие шаги никогда не отказывают. Последние два этапа называются доступными для повторения транзакциями, потому что они всегда заканчиваются успешно.

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

Сервис Order. 1. Создает заказ с состоянием APPROVAL_PENDING.

Сервис Consumer. 2. Проверяет, может ли заказчик размещать заказы.

Сервис Kitchen. 3. Проверяет детали заказа и создает заявку с состоянием CREATE_PENDING.

Сервис Accounting. 4. Делает неудачную попытку авторизовать банковскую карту заказчика.

Сервис Kitchen. 5. Меняет состояние заявки на CREATE_REJECTED.

Сервис Order. 6. Меняет состояние заказа на REJECTED.

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

Об авторе

Крис Ричардсон (Chris Richardson) — разработчик, архитектор и автор книги POJOs in Action (Manning, 2006), в которой описывается процесс построения Java-приложений уровня предприятия с помощью фреймворков Spring и Hibernate. Он носит почетные звания Java Champion и JavaOne Rock Star.

Крис разработал оригинальную версию CloudFoundry.com — раннюю реализацию платформы Java PaaS для Amazon EC2.

Крис создал сайт microservices.io, на котором собраны шаблоны проектирования микросервисов. Ныне он считается признанным идейным лидером в мире микросервисов и регулярно выступает на международных конференциях. Сейчас Крис работает над своим третьим стартапом Eventuate.io. А еще он проводит по всему миру консультации и тренинги для организаций, которые переходят на микросервисную архитектуру. Это программная платформа для разработки транзакционных микросервисов.

» Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок

Для Хаброжителей скидка 25% по купону — Microservice Patterns
По факту оплаты бумажной версии книги на e-mail высылается электронная книга.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть