Как стать автором
Обновить

Сравнение подходов к реализации распределенных транзакций для микросервисов

Время на прочтение21 мин
Количество просмотров40K
Автор оригинала: Bilgin Ibryam

Автор Bilgin Ibryam

Оригинал здесь.

Вступление переводчика

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

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

Приятного чтения!

Как архитектор-консультант в Red Hat, я имел возможность поработать над множеством проектов для наших клиентов. У каждого из них есть свои особенности, которые, однако, имеют некоторые общие черты. Большинство клиентов хотят знать, как скоординировать запись в несколько систем одновременно. Ответ на этот вопрос обычно включает подробное объяснение двойной записи, распределенных транзакций, современных альтернатив, а также возможных сценариев сбоев и недостатков каждого подхода. Как правило, именно в этот момент заказчик понимает, что разделение монолитного приложения на микросервисы - долгий и сложный путь, обычно требующий компромиссов.

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

Примечание: если вас интересует двойная запись, посмотрите мою сессию Red Hat Summit 2021, где я подробно рассмотрел особенности двойной записи. Вы также можете просмотреть слайды из моей презентации. В настоящее время я работаю с Red Hat OpenShift Streams для Apache Kafka, полностью управляемой службы Apache Kafka. Для запуска требуется меньше минуты, а в течение пробного периода он полностью бесплатен. Попробуйте и помогите нам сформировать его с помощью ваших ранних отзывов. Если у вас есть вопросы или комментарии по поводу этой статьи, напишите мне в Twitter @bibryam, и приступим.

Проблема двойной записи

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

  • Вы выбрали лучшие инструменты для каждой задачи, и теперь вам нужно обновить базу данных NoSQL, поисковый индекс и кеш в рамках одной бизнес-транзакции;

  • Созданная вами служба должна обновить свою базу данных, а также отправить уведомление об изменении в другую службу;

  • У вас есть бизнес-операции, которые затрагивают несколько других сервисов;

  • Возможно, вам нужно реализовать идемпотентность для сервиса, потому что конечные пользователи должны повторять неудачные вызовы.

В этой статье мы будем использовать один пример сценария для оценки различных подходов к обработке двойной записи в распределенных транзакциях. Наш сценарий - это клиентское приложение, которое вызывает микросервис для внесения изменений. Служба A (Service A на рисунках) должна обновить свою базу данных A (Database A), но она также должна вызвать службу B (Service B) для операции записи, как показано на рисунке 1. Фактический тип базы данных, как и протокол взаимодействия между службами, не имеет отношения к нашему обсуждению, потому что проблема остается прежней.

Рисунок 1: Проблема двойной записи в микросервисах
Рисунок 1: Проблема двойной записи в микросервисах

Небольшое, но важное уточнение ниже объясняет, почему у этой проблемы нет простых решений. Если служба A записывает данные в свою базу, а затем отправляет уведомление в очередь для службы B (назовем это подходом «локальная фиксация затем публикация»), все равно существует вероятность, что приложение не будет работать надежно. Пока служба A записывает в свою базу данных, а затем отправляет сообщение в очередь, существует небольшая вероятность сбоя приложения после сохранения в базе данных и до второй операции, что оставит систему в несогласованном состоянии. Если сообщение отправляется до записи в базу данных (назовем этот подход «публикация затем локальная фиксация»), существует вероятность сбоя записи в базу данных или проблем с синхронизацией, в результате чего служба B получит событие до того, как служба A зафиксирует изменение в своей база данных. В любом случае этот сценарий включает двойную запись в базу данных и в очередь, что является основной проблемой, которую мы собираемся исследовать. В следующих разделах я расскажу о различных подходах, доступных сегодня для решения этой актуальной проблемы.

Модульный монолит

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

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

Архитектура модульного монолита

При таком подходе вам необходимо преобразовать микросервисы (сервис A и сервис B) в библиотечные модули, которые можно развернуть в общую среду выполнения. Затем вы настраиваете оба микросервиса для совместного использования одной базы данных. Поскольку службы написаны и развернуты как библиотеки в общей среде выполнения, они могут использовать один и тот же механизм транзакций. Поскольку модули совместно используют экземпляр базы данных, вы можете использовать локальную транзакцию для одновременной фиксации или отката всех изменений. Мы также должны по-иному развертывать эти модули, поскольку мы хотим, чтобы модули развертывались как библиотеки в более крупном развертывании и использовали общий механизм локальных транзакций.

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

Рисунок 2: Уровни изоляции кода и данных для приложений
Рисунок 2: Уровни изоляции кода и данных для приложений

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

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

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

Рисунок 3: Модульный монолит с общей базой данных
Рисунок 3: Модульный монолит с общей базой данных

Преимущества и недостатки модульного монолита

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

Преимущества

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

Недостатки

Общая среда выполнения не позволяет нам независимо развертывать и масштабировать модули и изолировать сбои отдельного модуля.

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

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

Примеры

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

Apache Camel direct и direct-vm компоненты позволяют открывать операции для вызовов в памяти и сохранять контексты транзакций в процессе JVM.

Apache Isis - один из лучших примеров модульной монолитной архитектуры. Он позволяет разрабатывать приложения на основе предметной области, автоматически создавая пользовательский интерфейс и REST API для ваших приложений Spring Boot.

Apache OFBiz - еще один пример модульного монолита и сервис-ориентированной архитектуры (SOA). Это комплексная система планирования ресурсов предприятия с сотнями таблиц и служб, которые могут автоматизировать бизнес-процессы предприятия. Несмотря на размер, модульная архитектура позволяет разработчикам быстро понимать и настраивать ее.

Двухфазная фиксация

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

  • Когда запись в разрозненные ресурсы должна быть строго согласованной;

  • Когда нам нужно писать в разнородные источники данных;

  • Когда требуется однократная обработка сообщения, и мы не можем провести рефакторинг системы и сделать ее операции идемпотентными;

  • При интеграции со сторонними системами «черного ящика» или устаревшими системами, реализующими спецификацию двухфазной фиксации.

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

Реализация архитектуры двухфазной фиксации

Технические требования для двухфазной фиксации состоят в том, что вам нужен распределенный менеджер транзакций, такой как Narayana, и надежный уровень хранения для журналов транзакций. Вам также потребуются источники данных, совместимые с DTP XA, с соответствующими драйверами XA, которые могут участвовать в распределенных транзакциях, например RDBMS, брокеры сообщений и кеши. Если вам повезло, что у вас есть нужные источники данных, но вы работаете в динамической среде, такой как Kubernetes , вам также понадобится специальный механизм, чтобы гарантировать наличие только одного экземпляра распределенного диспетчера транзакций. При этом диспетчер транзакций должен быть высоко доступным и всегда иметь доступ к журналу транзакций.

Как пример реализации вы можете использовать на контроллер восстановления Snowdrop, который использует шаблон Kubernetes StatefulSet для гарантии наличия одного элемента и постоянные тома для хранения журналов транзакций. В эту категорию я также включаю такие спецификации, как атомарные транзакции веб-служб (WS-AtomicTransaction) для веб-служб SOAP. Все эти технологии объединяет то, что они реализуют спецификацию XA и имеют центральный координатор транзакций.

В нашем примере, показанном на рисунке 4, служба A использует распределенные транзакции для фиксации всех изменений в своей базе данных и сообщения в очереди, не оставляя никаких шансов дублированию или потере сообщений. Точно так же служба B может использовать распределенные транзакции для приема сообщений и фиксации в базе данных B в одной транзакции без каких-либо дубликатов. Или служба B может решить не использовать распределенные транзакции, а использовать локальные транзакции и реализовать шаблон идемпотентного потребителя. Прошу заметить, подходящим примером для этого раздела было бы использование WS-AtomicTransaction для координации записи в базу данных A и базу данных A в одной транзакции и избежать согласованности в целом. Но в наши дни такой подход встречается еще реже, чем то, что я описал.

Рисунок 4: Двухэтапная фиксация между базой данных и брокером сообщений
Рисунок 4: Двухэтапная фиксация между базой данных и брокером сообщений

Преимущества и недостатки архитектуры двухфазной фиксации

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

В таблице 2 суммированы преимущества и недостатки этого подхода.

Преимущества

Стандартный подход с готовыми менеджерами транзакций и вспомогательными источниками данных.

Сильная согласованность данных для успешных сценариев.

Недостатки

Ограничения масштабируемости.

Возможные сбои восстановления при сбое диспетчера транзакций.

Ограниченная поддержка источников данных.

Требования к хранилищу и наличие единого центрального координатора транзакций в динамических средах.

Примеры

Jakarta Transactions API (ранее Java Transaction API)

WS-AtomicTransaction

JTS/IIOP

eBay GRIT

Atomikos

Narayana

Брокеры сообщений, такие как Apache ActiveMQ

Реляционные источники данных, реализующие спецификацию XA; хранилища данных в памяти, такие как Infinispan

Оркестровка

В модульном монолите мы используем локальные транзакции и всегда знаем состояние системы. Распределенные транзакции, основанные на протоколе двухфазной фиксации, также гарантируют согласованное состояние. Единственным исключением может быть неисправимый сбой, связанный с координатором транзакции. Но что, если бы мы хотели упростить требования к согласованности, при этом зная состояние всей распределенной системы и координируя ее из одного места? В этом случае мы можем рассмотреть оркестровку - подход, при котором одна из служб действует как координатор и оркестратор общего изменения распределенного состояния. Служба оркестратора обязана вызывать другие службы, пока они не достигнут желаемого состояния, или предпринимать корректирующие действия в случае сбоя. Оркестратор использует свою локальную базу данных для отслеживания изменений состояния и отвечает за восстановление любых сбоев, связанных с изменениями состояния.

Реализация архитектуры оркестровки

Самыми популярными реализациями техники оркестрации являются реализации спецификации BPMN, такие как проекты jBPM и Camunda . Потребность в таких системах не исчезает из-за чрезмерно распределенных архитектур, таких как микросервисы или бессерверные архитектуры, а, наоборот, увеличивается. В качестве доказательства мы можем обратиться к более новым механизмам оркестровки с отслеживанием состояния, которые не соответствуют спецификации, но обеспечивают аналогичное поведение с отслеживанием состояния, например, Netflix Conductor, Uber Cadence и Apache Airflow. Бессерверные архитектуры с отслеживанием состояния, такие как Amazon StepFunctions, Azure Durable Functions и Azure Logic Apps, также относятся к этой категории. Кроме того, существуют библиотеки с открытым исходным кодом, которые позволяют реализовать поведение координации и отката с отслеживанием состояния, такие как реализация шаблона Apache Camel Saga и функциональность NServiceBus Saga . Многие встроенные реализации паттерна «Saga» также относятся к этой категории.

Рисунок 5: Организация распределенных транзакций между двумя сервисами
Рисунок 5: Организация распределенных транзакций между двумя сервисами

В нашем примере диаграммы, показанном на рисунке 5, у нас есть служба A, действующая как оркестратор с отслеживанием состояния, ответственный за вызов службы B и восстановление после сбоев с помощью операции компенсации, если это необходимо. Важнейшей характеристикой этого подхода является то, что служба A и служба B имеют локальные границы транзакций, но служба A обладает знаниями и обязанностью управлять общим потоком взаимодействия. Вот почему его граница транзакции касается конечных точек службы B. Что касается реализации, мы могли бы использовать синхронный подход, как показано на диаграмме, или асинхронный с помощью очереди сообщений между службами (в этом случае вы также можете использовать двухфазную фиксацию).

Преимущества и недостатки оркестровки

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

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

Преимущества

Координирует состояние разнородных распределенных компонентов.

Нет необходимости в транзакциях XA.

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

Недостатки

Сложная модель распределенного программирования.

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

Конечная последовательность.

Возможны неисправимые отказы при компенсациях.

Примеры

jBPM

Camunda

MicroProfile Long Running Actions

Netflix Conductor

Uber Cadence

Amazon StepFunctions

Azure Durable Functions

Реализация паттерна Apache Camel Saga

Реализация паттерна NServiceBus Saga

Спецификация CNCF Serverless Workflow

Встроенные реализации паттерна Saga

Хореография

Как вы уже видели в обсуждении, одна бизнес-операция может привести к множеству вызовов между службами, и может потребоваться неопределенное время, прежде чем бизнес-транзакция будет обработана от начала до конца. Для управления этим в шаблоне оркестрации используется централизованная служба контроллера, которая сообщает участникам, что им делать.

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

Рисунок 6: Хореография сервисов с использованием уровня обмена сообщениями
Рисунок 6: Хореография сервисов с использованием уровня обмена сообщениями

Хореография с двойной записью

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

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

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

Хореография без двойной записи

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

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

Рисунок 7: Хореография сервисов посредством опроса
Рисунок 7: Хореография сервисов посредством опроса

Самый простой сценарий - подключение службы B к базе данных службы A и чтение таблиц, принадлежащих службе A. Однако отрасль пытается избежать такого уровня связи с общими таблицами, и по уважительной причине: любое изменение в реализации службы A и модели данных может нарушить работу службы B. Мы можем внести несколько постепенных улучшений в этот сценарий, например, используя шаблон «Исходящие» и предоставив службе A таблицу, которая действует как открытый интерфейс. Эта таблица могла бы содержать только те данные, которые требуются Сервису B, и ее можно было бы спроектировать так, чтобы было легко запрашивать и отслеживать изменения. Если этого недостаточно, дальнейшее улучшение будет заключаться в том, что служба B будет запрашивать у службы A любые изменения через API уровня управления, а не подключаться напрямую к базе данных A.

По сути, все эти варианты страдают одним и тем же недостатком: служба B должна постоянно опрашивать службу A. Это может привести к ненужной непрерывной нагрузке на систему или ненужной задержке принятия изменений. Опрос микросервиса на предмет изменений - сложная задача, поэтому давайте посмотрим, что мы можем сделать для дальнейшего улучшения этой архитектуры.

Хореография с Debezium или альтернативами

Один из способов улучшить хореографическую архитектуру и сделать ее более привлекательной - это ввести такой инструмент, как Debezium , который мы можем использовать для выполнения сбора измененных данных (change data capture - CDC) с использованием журнала транзакций базы данных A. Рисунок 8 иллюстрирует этот подход.

Рисунок 8: Хореография сервисов с отслеживанием измененных данных
Рисунок 8: Хореография сервисов с отслеживанием измененных данных

Debezium может отслеживать журнал транзакций базы данных, выполнять любую необходимую фильтрацию и преобразование и вносить соответствующие изменения в тему Apache Kafka. Таким образом, служба B может прослушивать общие события в теме, а не опрашивать базу данных службы A или API. Замена опроса базы данных для потоковой передачи изменений и введение очереди между службами делает распределенную систему более надежной, масштабируемой и открывает возможность введения других потребителей для новых вариантов использования. Использование Debezium предлагает элегантный способ реализации шаблона “Исходящие” для реализаций шаблона Saga на основе оркестровки или хореографии.

Побочным эффектом этого подхода является то, что он вводит возможность службы B получать повторяющиеся сообщения. Эту проблему можно решить, реализовав службу как идемпотентную либо на уровне бизнес-логики, либо с помощью технического дедупликатора (с чем-то вроде обнаружения дублирующихся сообщений Apache ActiveMQ Artemis или идемпотентного шаблона потребителя Apache Camel).

Хореография с источником событий

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

Рисунок 9: Хореография обслуживания через поиск событий
Рисунок 9: Хореография обслуживания через поиск событий

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

Преимущества и недостатки хореографии

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

Преимущества

Разъединяет реализацию и взаимодействие.

Нет центрального координатора транзакций.

Улучшенные характеристики масштабируемости и устойчивости.

Взаимодействие почти в реальном времени.

Меньше накладных расходов при использовании Debezium и аналогичных инструментов.

Недостатки

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

Последовательность-в-целом.

Примеры

Собственные реализации баз данных или API опросов.

Шаблон “исходящие”

Хореография на базе шаблона Saga.

источник событий

Eventuate

Debezium

Zendesk Maxwell

Alibaba Canal

Linkedin Brooklin

Axon Framework

EventStoreDB

Параллельные конвейеры

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

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

Рисунок 10: Обработка через параллельные конвейеры
Рисунок 10: Обработка через параллельные конвейеры

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

Слушай себя

Существует более легкая альтернатива этому подходу, известная как шаблон «Слушай себя» , в котором одна из служб также действует как маршрутизатор. При этом альтернативном подходе, когда служба A получает запрос, она не будет записывать изменения в свою базу данных, а вместо этого опубликует запрос в системе обмена сообщениями, где конечными потребителями являются служба B, и сам сервис А. Рисунок 11 иллюстрирует этот подход.

Рисунок 11: Шаблон «Слушай себя»
Рисунок 11: Шаблон «Слушай себя»

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

Преимущества и недостатки параллельных конвейеров

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

Преимущества

Простая масштабируемая архитектура для параллельной обработки.

Недостатки

Требуется временный демонтаж на независимые события; трудно понять глобальное состояние системы.

Примеры

Многоадресная рассылка и разделитель Apache Camel с параллельной обработкой.

Как выбрать стратегию распределенных транзакций

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

Рисунок 12: Характеристики шаблонов двойной записи
Рисунок 12: Характеристики шаблонов двойной записи

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

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

Рисунок 13: Характеристики относительной согласованности данных и масштабируемости шаблонов двойной записи
Рисунок 13: Характеристики относительной согласованности данных и масштабируемости шаблонов двойной записи

В качестве хорошей отправной точки мы могли бы оценить различные подходы - от наиболее масштабируемых и высокодоступных до наименее масштабируемых и доступных.

Высокий уровень масштабирования и доступности: параллельные конвейеры и хореография.

Если ваши шаги временно изолированы, тогда имеет смысл запустить их в методе параллельных конвейеров. Скорее всего, вы можете применить этот шаблон к определенным частям системы, но не ко всем. Далее, предполагая, что между этапами обработки существует временная связь, и определенные операции и службы должны выполняться раньше других, вы можете рассмотреть подход “хореография”. Используя хореографию сервисов, можно создать масштабируемую, управляемую событиями архитектуру, в которой сообщения перетекают от сервиса к сервису через децентрализованный процесс оркестровки. В этом контексте реализации шаблона “Исходящие” с Debezium и Apache Kafka (например, Red Hat OpenShift Streams для Apache Kafka ) особенно интересны и набирают обороты.

Средний уровень масштабирования и доступности: оркестровка и двухфазная фиксация

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

Низкий уровень масштабирования и доступности: модульный монолит

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

Заключение

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

Теги:
Хабы:
Всего голосов 36: ↑36 и ↓0+36
Комментарии9

Публикации

Истории

Работа

Ближайшие события