В этой статье будет представлен полный пример 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 завершена.

Бегать

Если вы хотите полностью запустить успешный пример, выполните следующие действия.

  1. запустить дтм
git clone https://github.com/dtm-labs/dtm && cd dtm
go run main.go
  1. Запустите пример
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.