Автор статьи: Артем Михайлов
Микросервисы — это популярный подход к построению систем, где приложение разбивается на отдельные компоненты, называемые микросервисами. Каждый микросервис занимается определенным функционалом и может работать независимо, что приносит немало преимуществ.
Но, разумеется, с такой архитектурой возникают и свои сложности, особенно касающиеся управления транзакциями. Когда у нас был монолит, одна транзакция могла обернуть в себя несколько операций и либо выполняться успешно, либо полностью откатываться при ошибке. В микросервисах такой простоты уже нет.
Теперь у нас есть набор независимых микросервисов, каждый из которых может обновляться и масштабироваться по-отдельности. Это приводит к проблемам атомарности, целостности и консистентности транзакций. Как обеспечить, чтобы все микросервисы, участвующие в одной крупной транзакции, выполнили свою работу успешно и не нарушили целостность данных?
Именно здесь нам на помощь приходит интересный паттерн — Saga. Суть его в том, чтобы разбить большую транзакцию на ряд более мелких, локальных транзакций внутри каждого микросервиса. Если что-то идет не так, мы можем применить компенсирующие действия для возврата системы в предыдущее согласованное состояние. Поэтому Saga становится мощным инструментом для управления транзакциями в микросервисной архитектуре.
Понимание паттерна Saga и его применение в микросервисах
Паттерн Saga представляет собой методологию разбиения сложных и длинных транзакций на более мелкие, управляемые шаги. Каждый микросервис в цепочке выполняет свою локальную транзакцию, которая может быть атомарной и возвращать систему в предыдущее согласованное состояние при ошибке.
Паттерн Saga сосредотачивается на достижении атомарности каждого шага транзакции в микросервисной архитектуре. Атомарность означает, что каждая локальная транзакция должна быть либо выполнена успешно, либо полностью откатана, не допуская никакого промежуточного состояния. Это критически важно для обеспечения надежности и целостности данных в системе.
Представьте, у нас есть сложная транзакция, которая включает несколько этапов, выполняемых в разных микросервисах. Каждый этап может вносить изменения в данные или выполнять другие операции. Если в каком-то микросервисе происходит ошибка на одном из этапов, и мы не применяем атомарность, то система останется в неконсистентном состоянии, что может привести к непредсказуемому поведению и потере целостности данных.
Поэтому, когда мы говорим о том, что каждый шаг транзакции должен быть атомарным, это означает, что мы должны гарантировать, что каждая операция внутри микросервиса выполняется полностью и корректно. И если в каком-то микросервисе происходит ошибка, нам нужно применить компенсирующие действия, чтобы вернуть систему в состояние, которое было до начала этого шага.
Это обеспечивает надежность и целостность данных, потому что если что-то идет не так на одном из шагов, мы можем применить компенсирующие действия, чтобы отменить все изменения, внесенные на предыдущих этапах, и вернуть систему в предыдущее согласованное состояние. Таким образом, мы избегаем непредсказуемых состояний и гарантируем, что наша система всегда остается в согласованном состоянии, даже при возникновении ошибок.
Паттерн Saga предоставляет эффективный механизм для управления транзакциями в микросервисной архитектуре, гарантируя атомарность каждого шага и обеспечивая надежность и целостность данных. Это позволяет разрабатывать более надежные и отказоустойчивые системы, облегчая обработку ошибок и исключительных ситуаций. В итоге, применение паттерна Saga способствует повышению качества и стабильности нашего программного обеспечения в условиях микросервисной архитектуры.
Реализация паттерна Saga: шаг за шагом
1. Определение границ сервисов и определение саги:
Перед тем, как начать кодирование, нам нужно ясно определить границы наших микросервисов и определить сагу. Давайте представим, что у нас есть три микросервиса: «Orders» для управления заказами, «Payments» для обработки платежей и «Notifications» для отправки уведомлений. Наша сага будет охватывать процесс оформления заказа, который включает создание заказа, выполнение платежа и отправку уведомления о статусе заказа.
Для начала, создадим класс OrderService, который будет отвечать за выполнение операций, связанных с заказами. Помните, что определение границ микросервисов — это основополагающий шаг для успешной реализации паттерна.
class OrderService: def create_order(self, order_data): # Здесь реализация создания заказа pass def cancel_order(self, order_id): # Здесь реализация отмены заказа и компенсирующих действий pass
Разделение саги на локальные транзакции:
Теперь, когда у нас есть наш OrderService, давайте разделим нашу сагу на локальные транзакции для каждого микросервиса. Для этого нам понадобится еще два класса: PaymentService и NotificationService, чтобы обрабатывать соответственно платежи и уведомления.
class PaymentService: def process_payment(self, order_id, payment_data): # Здесь реализация обработки платежа pass def rollback_payment(self, order_id): # Здесь реализация отмены платежа и компенсирующих действий pass class NotificationService: def send_notification(self, order_id, notification_data): # Здесь реализация отправки уведомления pass def rollback_notification(self, order_id): # Здесь реализация отмены уведомления и компенсирующих действий pass
Обработка компенсирующих действий при ошибке:
Важный шаг — это обработка ситуаций, когда что-то идет не так. В паттерне Saga для этого применяются компенсирующие действия. Если одна из локальных транзакций завершается неудачно, мы должны выполнить компенсирующие действия в каждом микросервисе, чтобы отменить предыдущие шаги.
Для этого, нам нужно реализовать обработку ошибок и вызывать компенсирующие функции, если что-то идет не так.
class SagaCoordinator: def execute_saga(self, order_data, payment_data, notification_data): order_service = OrderService() payment_service = PaymentService() notification_service = NotificationService() try: # Шаг 1: Создание заказа order_id = order_service.create_order(order_data) # Шаг 2: Выполнение платежа payment_service.process_payment(order_id, payment_data) # Шаг 3: Отправка уведомления notification_service.send_notification(order_id, notification_data) # Все успешно выполнено, завершаем сагу print("Сага успешно завершена!") except Exception as e: # Обработка ошибки и выполнение компенсирующих действий print(f"Произошла ошибка: {e}") self.rollback_saga(order_id) def rollback_saga(self, order_id): # Вызываем компенсирующие действия для отмены всех предыдущих операций order_service = OrderService() payment_service = PaymentService() notification_service = NotificationService() try: # Шаг 1: Отмена платежа payment_service.rollback_payment(order_id) # Шаг 2: Отмена уведомления notification_service.rollback_notification(order_id) # Шаг 3: Отмена создания заказа order_service.cancel_order(order_id) print("Сага успешно отменена!") except Exception as e: # Обработка ошибки во время отката print(f"Ошибка при откате саги: {e}")
Теперь, когда у нас есть класс SagaCoordinator, который координирует выполнение саги и обработку ошибок, мы можем вызвать метод execute_saga() и передать ему данные для оформления заказа, обработки платежа и отправки уведомления.
if __name__ == "__main__": saga_coordinator = SagaCoordinator() order_data = {"customer_id": 123, "products": [1, 2, 3]} payment_data = {"amount": 100.0, "payment_method": "credit_card"} notification_data = {"message": "Ваш заказ успешно оформлен!"} saga_coordinator.execute_saga(order_data, payment_data, notification_data)
Таким образом, мы создали мощный механизм управления транзакциями с помощью паттерна Saga в микросервисной архитектуре. Каждый шаг саги выполняется отдельно, и в случае ошибки мы аккуратно выполняем компенсирующие действия для восстановления целостности данных. Python предоставляет много инструментов для реализации этого паттерна, делая наш код надежным, гибким и устойчивым к ошибкам.
Сценарии и компенсирующие транзакции
Примеры типичных сценариев для паттерна Saga:
1. Оформление заказа с оплатой и доставкой:
Допустим, у нас есть микросервисы для управления заказами, платежами и доставкой. При оформлении заказа с клиента снимается оплата, и заказ передается на доставку. В этом сценарии, мы можем использовать паттерн Saga, чтобы обеспечить атомарность операций. Если оплата не прошла успешно, мы можем откатить заказ и вернуть средства клиенту. Если же доставка не удалась, мы можем отменить оплату и вернуть деньги клиенту.
2. Бронирование и отмена брони:
Представим, что у нас есть микросервисы для бронирования отелей и отмены бронирования. В этом сценарии, паттерн Saga позволяет нам обрабатывать бронирование и отмену отдельно. Если бронирование прошло успешно, но клиент решает отменить бронь, мы можем использовать компенсирующие действия для отката операции.
3. Обработка заказов с различными платежными методами:
Иногда, различные клиенты предпочитают разные платежные методы. У нас есть микросервисы для обработки кредитных карт и электронных кошельков. Паттерн Saga позволяет нам справиться с этими различиями. Если один из платежных методов недоступен или возникает ошибка, мы можем применить компенсирующие действия для отката операции и попробовать другой платежный метод.
Как обрабатывать ошибки и откатывать изменения:
Ошибки могут возникать в разных местах при выполнении саги, и для обеспечения целостности данных нам необходимо уметь обрабатывать их и применять компенсирующие транзакции при необходимости.
Для примера, реализуем сценарий оформления заказа с помощью Python и покажем, как обрабатывать ошибки и использовать компенсирующие действия.
class OrderService: def create_order(self, order_data): try: # Шаг 1: Создание заказа order_id = self._create_order_in_database(order_data) # Шаг 2: Выполнение платежа self._process_payment(order_id, order_data["payment_data"]) # Шаг 3: Отправка уведомления self._send_notification(order_id, "Ваш заказ успешно оформлен!") print("Заказ успешно оформлен!") return order_id except Exception as e: print(f"Произошла ошибка при оформлении заказа: {e}") self._handle_saga_failure(order_id) def _create_order_in_database(self, order_data): # Здесь реализация создания заказа в базе данных pass def _process_payment(self, order_id, payment_data): # Здесь реализация обработки платежа pass def _send_notification(self, order_id, message): # Здесь реализация отправки уведомления pass def _handle_saga_failure(self, order_id): # Вызываем компенсирующие действия для отката предыдущих операций self._rollback_payment(order_id) self._rollback_order_creation(order_id) def _rollback_payment(self, order_id): # Здесь реализация отмены платежа pass def _rollback_order_creation(self, order_id): # Здесь реализация отмены создания заказа pass
В этом примере мы представили класс OrderService, который отвечает за оформление заказа и его выполнение в рамках саги. В методе create_order, мы вызываем шаги создания заказа, обработки платежа и отправки уведомления. Если в ходе выполнения возникнет ошибка, мы вызываем метод _handle_saga_failure, который аккуратно откатит все предыдущие изменения и вернет систему в предыдущее согласованное состояние.
Инструменты и подходы для реализации паттерна Saga
Координаторы транзакций:
Координаторы транзакций — это ключевой элемент при реализации паттерна Saga. Они отвечают за управление выполнением каждого шага саги, отслеживание его статуса и реагирование на возможные ошибки. Координатор выполняет роль оркестратора, который согласовывает действия различных сервисов, чтобы обеспечить атомарность и целостность транзакции.
В микросервисной архитектуре координатор может быть реализован как отдельный сервис, который общается с другими микросервисами через API. Когда сага запускается, координатор отправляет запросы на выполнение каждого шага и следит за успешным завершением. В случае ошибки, он активирует компенсирующие действия для отката предыдущих операций и восстановления системы в согласованное состояние.
Использование message brokers:
Использование message brokers (брокеров сообщений) является распространенным подходом для реализации асинхронной коммуникации между микросервисами. Брокеры сообщений позволяют отправлять и принимать сообщения между различными компонентами системы, что делает процесс координации транзакций более эффективным.
При реализации паттерна Saga с помощью брокеров сообщений, каждый шаг саги представляется как сообщение, которое отправляется в соответствующую очередь или топик. Когда микросервис готов выполнить определенный шаг, он просто получает сообщение из очереди и выполняет соответствующую операцию. Такой асинхронный подход позволяет распределенным компонентам системы работать независимо друг от друга и более устойчиво реагировать на возможные ошибки.
Фреймворки для паттерна Saga:
Существует несколько отличных фреймворков и библиотек, которые упрощают реализацию паттерна Saga и позволяют сосредоточиться на бизнес-логике приложения, минимизируя сложность управления транзакциями. Некоторые из них стоят особого внимания:
Choreography-based Saga:
Этот фреймворк подходит для сценариев, где сага включает большое количество микросервисов. Он основан на использовании событийной модели и асинхронной коммуникации между сервисами с помощью брокеров сообщений. Подход «через хореографию» позволяет каждому микросервису знать, какие действия выполняются другими сервисами, и соответственно реагировать на изменения состояния системы.
Orchestration-based Saga:
Этот подход базируется на использовании централизованного координатора (оркестратора), который управляет выполнением саги и отправляет запросы на выполнение шагов каждому микросервису. Подход «через оркестрацию» облегчает реализацию и отслеживание саги, так как всю логику координации можно вынести в отдельный сервис.
Axon Framework:
Это Java-фреймворк, который обеспечивает реализацию паттерна Saga через CQRS (Command Query Responsibility Segregation) и Event Sourcing. Axon Framework позволяет легко разделять команды и запросы в системе, что облегчает координацию транзакций и обработку событий.
Заключение
Благодаря паттерну Saga, мы можем создавать более надежные, гибкие и устойчивые системы, способные успешно справляться с реальными вызовами микросервисной архитектуры. Независимо от выбранного подхода или инструментов, правильное использование паттерна Saga позволит нам создавать высококачественные приложения, которые предоставляют безупречный опыт пользователя и обеспечивают стабильную работу в условиях постоянно меняющегося окружения.
Сейчас количество компаний, переходящих на микросервисы с монолитов стремительно растет, таким компаниям требуются инженеры знающие паттерны работы с микросервисами и умеющие с ними работать. В связи с этим хочу пригласить вас на вебинар, где эксперты OTUS расскажут про, плюсы и минусы микросервисной архитектуры, а также про особенности перехода.
ссылка на оригинал статьи https://habr.com/ru/companies/otus/articles/751134/
Добавить комментарий