В этой статье будет представлен полный пример TCC, чтобы дать читателю точное представление о транзакциях типа TCC.
Бизнес-сценарий
Типичным сценарием распределенных транзакций являются межбанковские переводы, когда А должен перевести средства через банк в Б. Гипотетический сценарий требования состоит в том, что как переводы из А, так и в Б могут быть успешными и неудачными, и что оба перевода в и из банка будут в конце концов добиться успеха или потерпеть неудачу.
Существует также требование, согласно которому в случае отката схема SAGA приведет к тому, что A обнаружит, что его баланс был вычтен, но получатель B задержит получение баланса, что вызовет у A большие неприятности. Бизнес предпочел бы избежать этой ситуации
Компоненты ТСС
TCC разделен на 3 части
- Пробная часть: пытается выполнить, завершает все бизнес-проверки (непротиворечивость), резервирует необходимые бизнес-ресурсы.
- Подтверждающая часть: если все ветки преуспевают на этапе «Попытка», мы переходим к этапу «Подтверждение», где «Подтверждение» фактически выполняет бизнес без каких-либо бизнес-проверок, используя только бизнес-ресурсы, зарезервированные на этапе «Попытка».
- Часть отмены: если одна из
Trys
во всех ветвях выходит из строя, мы переходим к фазе отмены, которая освобождает бизнес-ресурсы, зарезервированные на фазе попытки.
Если бы мы выполнили транзакцию, аналогичную банковскому межбанковскому переводу, с TransOut и TransIn в отдельных микросервисах, типичная временная диаграмма для успешно завершенной транзакции TCC была бы следующей.
Основные операции
Сначала мы создаем таблицу баланса счета, где trading_balance
указывает сумму, которая была заморожена.
create table if not exists dtm_busi.user_account(
id int(11) PRIMARY KEY AUTO_INCREMENT,
user_id int(11) UNIQUE,
balance DECIMAL(10, 2) not null default '0',
trading_balance DECIMAL(10, 2) not null default '0',
create_time datetime DEFAULT now(),
update_time datetime DEFAULT now(),
key(create_time),
key(update_time)
);
Давайте сначала напишем основной код, операция заморозки/разморозки средств проверит ограничение balance+trading_balance ›= 0, если ограничение недействительно, выполнение завершится ошибкой.
Давайте напишем специальные функции-обработчики Try/Confirm/Cancel.
Основная логика этих функций состоит в том, чтобы заморозить и отрегулировать баланс, роль bb.Call
в этом будет подробно объяснена позже.
ТСС-транзакции
Затем создается транзакция TCC и выполняются вызовы филиалов.
На этом полная распределенная транзакция TCC завершена.
Бегать
Если вы хотите полностью запустить успешный пример, выполните следующие действия.
- запустить дтм
git clone https://github.com/dtm-labs/dtm && cd dtm
go run main.go
- Запустите пример
git clone https://github.com/dtm-labs/dtm-examples && cd dtm-examples
go run main.go http_tcc_barrier
Обработка сетевых исключений
Предположим, что транзакция, зафиксированная в dtm
, ненадолго терпит неудачу на одном из этих шагов. dtm
повторит незавершенную операцию, требуя, чтобы подтранзакции глобальной транзакции были идемпотентными. Платформа dtm
впервые применила технику барьера подтранзакций, предоставив служебный класс BranchBarrier
, который помогает пользователям легко справляться с идемпотентностью. Он предоставляет функцию Call, которая гарантирует, что операция внутри этой функции будет вызвана не более одного раза:
func (bb *BranchBarrier) Call(tx *sql.Tx, busiCall BarrierBusiFunc) error
Этот BranchBarrier
может автоматически обрабатывать не только идемпотентность, но и нулевую компенсацию и проблемы с зависанием, подробности см. в разделе Исключения и решения.
Откат ТСС
Что произойдет, если банк, готовящийся к переводу суммы пользователю 2, обнаружит, что счет пользователя 2 неисправен и вернет отказ? Мы модифицируем код, чтобы смоделировать эту ситуацию.
app.POST(BusiAPI+"/TccBTransInTry", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
return dtmcli.ErrFailure
}))
Это временная диаграмма взаимодействия при сбое транзакции.
Разница между этим и успешным TCC заключается в том, что когда дочерняя транзакция возвращает ошибку, глобальная транзакция впоследствии откатывается, вызывая операцию Cancel каждой дочерней транзакции, чтобы гарантировать откат всей глобальной транзакции.
Прямая операция TransInTry
вернула ошибку, ничего не сделав, в этот момент вызов операции компенсации TransInCancel
приведет к неправильной обратной настройке?
Не беспокойтесь, описанный выше метод барьера подтранзакции гарантирует, что ошибка TransInTry
компенсируется как нулевая операция, если она возникает до фиксации, и что операция компенсации фиксирует данные, если ошибка TransInTry возникает после фиксации.
Вы можете изменить TccBTransInTry
на
Окончательный баланс результата все равно будет правильным, подробнее см. Исключения и решения.
Краткое содержание
В этой статье дается полное решение для транзакций TCC. Вы можете использовать его для решения ваших реальных проблем с помощью нескольких простых модификаций этого примера.
Для получения дополнительной информации о принципах TCC см. раздел TCC. Добро пожаловать на проект https://github.com/dtm-labs/dtm.