Easily complete a distributed transaction TCC with PHP, nanny-level tutorial

叶东富
中文

What is TCC? TCC is the abbreviation of Try, Confirm and Cancel. It was first proposed by Pat Helland in a paper entitled "Life beyond Distributed Transactions: an Apostate's Opinion" published in 2007.

TCC composition

TCC is divided into 3 stages

  • Try phase: try to execute, complete all business checks (consistency), reserve necessary business resources (quasi-isolation)
  • Confirm phase: If the Try of all branches is successful, go to the Confirm phase. Confirm actually executes the business without any business inspections, and only uses the business resources reserved during the Try phase
  • Cancel phase: If one of the Try of all branches fails, go to the Cancel phase. Cancel releases the business resources reserved in the Try phase.

In TCC distributed transaction, there are 3 roles, the same as the classic XA distributed transaction:

  • AP/application, initiate a global transaction, define which transaction branches the global transaction contains
  • RM/resource manager, responsible for the management of various resources of branch affairs
  • TM/transaction manager, responsible for coordinating the correct execution of global transactions, including the execution of Confirm and Cancel, and handling network exceptions

If we want to conduct a business similar to bank inter-bank transfer, the transfer out (TransOut) and transfer in (TransIn) are in different microservices. A typical sequence diagram of a successfully completed TCC transaction is as follows:

image.png

TCC practice

Below we carry out the specific development of a TCC transaction

The open source framework currently available for TCC is mainly the Java language, with seata being the representative. Our example uses nodejs, the distributed transaction framework used is dtm, and its support for distributed transactions is very elegant. Let's explain the composition of TCC in detail below

Let's write a specific Try/Confirm/Cancel processing function

$vega->handleFunc('/api/TransOutTry', function (Mix\Vega\Context $ctx) {
    var_dump('TransOutTry', $ctx->request->getQueryParams(), $ctx->request->getParsedBody());
    $ctx->JSON(200, ['result' => 'SUCCESS']);
})->methods('POST');
$vega->handleFunc('/api/TransOutConfirm', function (Mix\Vega\Context $ctx) {
    var_dump('TransOutConfirm', $ctx->request->getQueryParams(), $ctx->request->getParsedBody());
    $ctx->JSON(200, ['result' => 'SUCCESS']);
})->methods('POST');
$vega->handleFunc('/api/TransOutCancel', function (Mix\Vega\Context $ctx) {
    var_dump('TransOutCancel', $ctx->request->getQueryParams(), $ctx->request->getParsedBody());
    $ctx->JSON(200, ['result' => 'SUCCESS']);
})->methods('POST');

$vega->handleFunc('/api/TransInTry', function (Mix\Vega\Context $ctx) {
    var_dump('TransInTry', $ctx->request->getQueryParams(), $ctx->request->getParsedBody());
    $ctx->JSON(200, ['result' => 'SUCCESS']);
})->methods('POST');
$vega->handleFunc('/api/TransInConfirm', function (Mix\Vega\Context $ctx) {
    var_dump('TransInConfirm', $ctx->request->getQueryParams(), $ctx->request->getParsedBody());
    $ctx->JSON(200, ['result' => 'SUCCESS']);
})->methods('POST');
$vega->handleFunc('/api/TransInCancel', function (Mix\Vega\Context $ctx) {
    var_dump('TransInCancel', $ctx->request->getQueryParams(), $ctx->request->getParsedBody());
    $ctx->JSON(200, ['result' => 'SUCCESS']);
})->methods('POST');

At this point, the processing function of each sub-transaction has been OK, and then the TCC transaction is opened, and the branch call is made

    Dtmcli\tccGlobalTransaction($dtm, function ($tcc) use ($svc) {
        /** @var Dtmcli\Tcc $tcc */
        $req = ['amount' => 30];
        $tcc->callBranch($req, $svc . '/TransOutTry', $svc . '/TransOutConfirm', $svc . '/TransOutCancel');
        $tcc->callBranch($req, $svc . '/TransInTry', $svc . '/TransInConfirm', $svc . '/TransInCancel');
    });

At this point, a complete TCC distributed transaction has been written.

If you want to run a successful sample completely, then refer to this example yedf/dtmcli-php-sample, it is very simple to run it

# 部署启动dtm
# 需要docker版本18以上
git clone https://github.com/yedf/dtm
cd dtm
docker-compose up

# 另起一个命令行
https://github.com/yedf/dtmcli-php-sample.git
cd dtmcli-php-sample
composer install
php demo.php start

TCC rollback

What happens if the bank finds that the account of user 2 is abnormal when the bank is about to transfer the amount to user 2 and the return fails? We can make TransIn return failure to simulate this situation

$vega->handleFunc('/api/TransInTry', function (Mix\Vega\Context $ctx) {
    var_dump('TransInTry', $ctx->request->getQueryParams(), $ctx->request->getParsedBody());
    $ctx->JSON(200, ['result' => 'FAILURE']);
})->methods('POST');

We give a sequence diagram of transaction failure interaction

image.png

The difference between this and successful TCC is that when a sub-transaction returns to failure, the global transaction is subsequently rolled back, and the Cancel operation of each sub-transaction is called to ensure that the global transaction is all rolled back.

In the TCC transaction mode, many readers will ask, what happens if Confirm/Cancel fails? This is a good question, which means that you are thinking deeply about the TCC transaction model. The first case is a temporary failure, such as a network failure, application or database downtime. This type of error is retried and finally returns success; the other case is a business failure. According to the TCC agreement, the first phase locks resources to ensure Sufficient resources can allow Confirm/Cancel to be executed. That is to say, in the program logic, Confirm/Cancel is not allowed to return to business failure. If a business fails, it is a bug, and the developer needs to manually fix the bug.

summary

In this article, we introduced the theoretical knowledge of TCC, and through an example, gave a complete process of writing a TCC transaction, covering the normal successful completion and successful rollback. I believe that readers have an in-depth understanding of TCC through this article.

For more comprehensive knowledge of distributed transactions, please refer to "Seven Classic Solutions for Distributed Transactions"

The examples used in the article are excerpted from yedf/dtm , which supports multiple transaction modes: TCC, SAGA, XA, transaction message cross-language support, and has supported clients in languages such as golang, python, Java, PHP, nodejs, refer to each language. Provide sub-transaction barrier function, elegantly solve the problems of idempotence, suspension, null compensation and so on.

After reading this dry goods, welcome everyone to visit the https://github.com/yedf/dtm project, give stars to support!

阅读 2.4k

944 声望
6.1k 粉丝
0 条评论
944 声望
6.1k 粉丝
文章目录
宣传栏