Saga – это наиболее распространенный паттерн для обеспечения согласованности данных между сервисами. Он разделяет глобальную транзакцию на несколько подтранзакций, и если все операции подтранзакции успешны, то успешна и глобальная транзакция, а если одна подтранзакция после повторных попыток все еще не удалась, то все выполненные подтранзакции компенсируются.
Приведенный выше процесс прост и понятен, но в распределенной среде могут возникнуть аномалии. Убедиться в том, что транзакция Saga работает правильно и данные остаются согласованными, – нелегкая задача. Мы обсудим различные аномалии, которые могут возникнуть, и способы управления ими.
Для целей обсуждения мы будем называть прямое действие транзакции филиала в Saga Action
, а компенсирующее действие – Compensation
.
Три типа аномалий
Обычно Action
выполняется перед Compensation
в транзакциях Saga, если происходит откат. Но из-за сетевых задержек или пауз в процессе, возможно, что Compensation
выполняется первым, а Action
выполняется позже или не выполняется вообще.
Этот сценарий приводит к двум аномалиям в распределенных транзакциях:
- Нулевая компенсация: Когда
Compensation
выполняется, соответствующееAction
не было выполнено, поэтомуCompensation
должен определить, чтоAction
не было выполнено, проигнорировать обновления бизнес-данных и вернуться напрямую. - Зависающее действие: Когда
Action
выполняется,Compensation
был выполнен, поэтомуAction
должен определить, чтоCompensation
был выполнен, проигнорировать обновления бизнес-данных и вернуться напрямую.
Другой распространенной аномалией, с которой необходимо бороться в распределенных транзакциях, является Duplicated Requests
. Когда процесс терпит крах и Saga повторяет попытку обращения к службе, служба может быть вызвана несколько раз, поэтому необходима идемпотентная обработка.
Эти три типа аномалий требуют тщательного управления, иначе могут возникнуть несоответствия данных и нарушить работу предприятия.
Лучшая практика
https://github.com/dtm-labs/dtm разработал технику под названием Sub-transaction Barrier
для одновременного управления этими тремя типами аномалий. Для шаблона Saga принцип заключается в следующем.
- Создайте таблицу dtm_barrier.barrier в локальной базе данных, с уникальным индексом
gid-branchid-branchop
. - Начните локальную транзакцию
- В
Action
иCompensation
вставить игнорирование строкиgid-branchid-action|compensation
, если количество затронутых строк равно 0 (в случае Duplicate Requests, Hanging Action), зафиксировать напрямую и вернуться - В
Compensation
, вставьте игнорирование дополнительной строкиgid-branchid-action
, если количество затронутых строк равно 1 (в случае Null Compensation), зафиксируйте непосредственно и вернитесь обратно. - Выполнить бизнес-логику и зафиксировать, или откатиться назад, если возникли ошибки
Дублированные запросы
Из-за уникального индекса, указанного выше, вставка в дублирующихся запросах гарантированно игнорируется, а бизнес-обработка пропускается
Компенсация нуля
Когда происходит компенсация нуля, успешная вставка шага 3 и проигнорированная вставка шага 4 будут зафиксированы напрямую и возвращены, пропуская бизнес-логику.
Зависающее действие
Когда возникает Hanging Action
, компенсация была выполнена и вставлена строка gid-branchid-action
, поэтому в Action
будет обнаружено, что вставка на шаге 3 была проигнорирована, а затем будет выполнен прямой возврат, минуя бизнес-логику.
Перекрытие действий и компенсаций
Если Action
и Compensation
перекрываются по времени выполнения, то и Compensation
и Action
вставят одну и ту же строку gid-branchid-action
. Из-за конфликта уникальных индексов только одна из этих двух операций будет успешной, в то время как другая будет ждать завершения транзакции, удерживающей блокировку, а затем вернется.
- Сценарий 1:
Action
терпит неудачу, аCompensation
успешно вставляетgid-branchid-action
. Это типичный сценарийNull Compensation
иHanging Action
, и обаAction
иCompensation
будут игнорировать бизнес-логику и возвращаться непосредственно в соответствии с приведенным выше алгоритмом. - Сценарий 2:
Action
успешна, аCompensation
неудачна при вставкеgid-branchid-action
. Порядок выполнения транзакции –Action
передCompensation
, и никакой аномалии не происходит. Согласно приведенному выше алгоритму, бизнес вAction
иCompensation
будет выполняться упорядоченно - Сценарий 3. Если база данных выйдет из строя во время перекрытия, операции будут повторно опробованы и в конечном итоге перейдут к сценарию 1 или 2.
В результате рассмотрения вышеприведенных сценариев, Sub-transaction Barrier
способен правильно управлять аномалиями и обеспечивать согласованность данных.
Описанный выше алгоритм также применим к распределенным транзакциям TCC. Даже если вы используете механизм рабочего процесса, такой как Candence, Camunda, для обработки транзакций Saga, алгоритм также применим.
Код
Описанная выше техника Sub-transaction Barrier
, при использовании в сочетании с фреймворком распределенных транзакций https://github.com/dtm-labs/dtm, была доступна в SDK нескольких языков, со следующим примером кода на Go.
app.POST(BusiAPI+"/SagaBTransOut", dtmutil.WrapHandler(func(c *gin.Context) interface{} {
barrier := MustBarrierFromGin(c)
return barrier.Call(txGet(), func(tx *sql.Tx) error {
return SagaAdjustBalance(tx, TransOutUID, -reqFrom(c).Amount, "")
})
}))
app.POST(BusiAPI+"/SagaBTransOutCom", dtmutil.WrapHandler(func(c *gin.Context) interface{} {
barrier := MustBarrierFromGin(c)
return barrier.Call(txGet(), func(tx *sql.Tx) error {
return SagaAdjustBalance(tx, TransOutUID, reqFrom(c).Amount, "")
})
}))
В сервисе TransOut
первой строкой создается барьер из http-запроса, а следующие несколько строк вызывают бизнес внутри барьера.
Когда мы помещаем бизнес-обработку внутрь барьера субтранзакции, различные аномалии, описанные ранее, отфильтровываются, и разработчику остается только рассмотреть бизнес-обработку при нормальном потоке.
Полный пример можно найти здесь: https://github.com/dtm-labs/dtm-examples. После установки dtm
вы можете запустить полный пример, выполнив следующую команду:
Заключение
В этой статье предлагается алгоритм обработки аномалий в паттерне Saga для микросервисов. Это элегантный и эффективный алгоритм, который может быть применен к шаблону TCC, а также ко многим рабочим процессам.
Приглашаем вас посетить сайт https://github.com/dtm-labs/dtm. Это проект с открытым исходным кодом, призванный облегчить разработку распределенных транзакций.