windawake/laravel-reset-transaction

用于调用远程API服务的分布式事务

v1.0.0 2022-02-21 13:46 UTC

This package is auto-updated.

Last update: 2024-09-09 21:34:24 UTC


README

中文文档

RT(重置事务)模式可以作为调用远程API服务的分布式事务

概述

安装laravel5.5-laravel8之间的版本,然后安装composer包

## Composer2 version must be used
composer require windawake/laravel-reset-transaction dev-master

首先创建订单、存储、账户3个MySQL数据库实例,3个控制器,3个模型,将测试套件Transaction添加到phpunit.xml中,然后启动web服务器。这些操作只需要执行以下命令即可完成

php artisan resetTransact:create-examples && php artisan serve --host=0.0.0.0 --port=8000

打开另一个终端,使用端口8001启动web服务器

php artisan serve --host=0.0.0.0 --port=8001

最后运行测试脚本 ./vendor/bin/phpunit --testsuite=Transaction --filter=ServiceTest 运行结果如下,3个示例都通过了测试。

DESKTOP:/web/linux/php/laravel/laravel62# ./vendor/bin/phpunit --testsuite=Transaction --filter=ServiceTest
Time: 219 ms, Memory: 22.00 MB

OK (3 tests, 12 assertions)

特性

  1. 开箱即用,无需重构原始项目的代码,与MySQL事务写入一致,简单易用。
  2. 遵循两阶段提交协议,这是一个强一致性事务。在高并发情况下,它支持读取提交事务的隔离级别,数据一致性接近100%的MySQL xa。
  3. 由于事务被分割成多个,成为几个小事务,压力测试发现死锁比MySQL普通事务少。
  4. 支持分布式事务嵌套,与savepoint一致。
  5. 支持避免不同业务代码并发引起的脏数据问题。
  6. 默认支持HTTP协议的服务端接口。如果想要支持其他协议,需要重写中间件。
  7. 支持子服务的嵌套分布式事务(世界首次).
  8. 支持服务,混合嵌套本地事务和分布式事务(世界首次)
  9. 支持3次超时重试,重复请求确保幂等性
  10. 支持go、java语言(开发中)

原理

观看电影《明日边缘》后,您将了解归档和读取文件的操作。这个分布式事务组件模仿了《明日边缘》电影的原则。重置意味着重置,即在每次请求基本服务开始时读取文件,然后继续后续操作。最后,所有操作都回滚并归档,最后成功执行所有归档。整个过程是遵循两阶段提交协议,首先准备,然后提交。

以用户A使用招商银行卡将100元转账到用户B的招商银行账户的场景为例,下面是流程图。 在右图中的重置分布式事务开启后,比左图多了4个请求。第4个请求所做的操作是第1-3个请求之前所做的操作,然后回到原点重新开始,最后提交事务,结束转账过程。

支持子服务的嵌套分布式事务(世界首次)

一个世界级难题:服务A提交->服务B回滚->服务C提交->服务D提交sql。在这种情况下,ABCD都是不同的数据库。我如何让服务A提交服务B并回滚?关于服务C和D的所有操作怎么办?

seata和go-dtm都无法解决这个问题。解决这个问题的关键是C服务D服务必须提交伪数据,而不能提交真数据。如果提交了,将无法恢复。

实现支持子服务的嵌套分布式事务有哪些好处?你可以让服务A成为其他服务的服务,并且它可以被任意层级的链接嵌套。打破之前的枷锁:服务A必须是根服务,如果服务A要成为子服务,代码必须改变。如果使用RT模式,服务A可以不修改代码成为其他人的服务。

如何使用

vendor/windawake/laravel-reset-transaction/tests/ServiceTest.php文件为例

<?php

namespace Tests\Transaction;

use App\Models\ResetAccountModel;
use Tests\TestCase;
use Illuminate\Support\Facades\DB;
use App\Models\ResetOrderModel;
use App\Models\ResetStorageModel;
use GuzzleHttp\Client;
use Laravel\ResetTransaction\Facades\RT;

class ServiceTest extends TestCase
{
    private $baseUri = 'http://127.0.0.1:8000';
    /**
     * @var \GuzzleHttp\Client
     */
    private $client;

    protected function setUp(): void
    {
        parent::setUp();
        DB::setDefaultConnection('service_order'); //默认是订单服务
        $this->client = new Client([
            'base_uri' => $this->baseUri,
            'timeout' => 60,
        ]);
	$requestId = session_create_id();
	session()->put('rt_request_id', $requestId);
    }

    public function testCreateOrderWithCommit()
    {
        $orderCount1 = ResetOrderModel::count();
        $storageItem1 = ResetStorageModel::find(1);
        $accountItem1 = ResetAccountModel::find(1);
	// 开启RT模式分布式事务
        $transactId = RT::beginTransaction();
        $orderNo = rand(1000, 9999); // 随机订单号
        $stockQty = 2; // 占用2个库存数量
        $amount = 20.55; // 订单总金额20.55元

        ResetOrderModel::create([
            'order_no' => $orderNo,
            'stock_qty' => $stockQty,
            'amount' => $amount
        ]);
        // 请求库存服务,减库存
	$requestId = session_create_id();
        $response = $this->client->put('/api/resetStorage/1', [
            'json' => [
                'decr_stock_qty' => $stockQty
            ],
            'headers' => [
                'rt_request_id' => $requestId,
                'rt_transact_id' => $transactId,
            ]
        ]);
        $resArr1 = $this->responseToArray($response);
        $this->assertTrue($resArr1['result'] == 1, 'lack of stock'); //返回值是1,说明操作成功
        // 请求账户服务,减金额
	$requestId = session_create_id();
        $response = $this->client->put('/api/resetAccount/1', [
            'json' => [
                'decr_amount' => $amount
            ],
            'headers' => [
                'rt_request_id' => $requestId,
                'rt_transact_id' => $transactId,
            ]
        ]);
        $resArr2 = $this->responseToArray($response);
        $this->assertTrue($resArr2['result'] == 1, 'not enough money'); //返回值是1,说明操作成功
	// 提交RT模式分布式事务
        RT::commit();

        $orderCount2 = ResetOrderModel::count();
        $storageItem2 = ResetStorageModel::find(1);
        $accountItem2 = ResetAccountModel::find(1);

        $this->assertTrue(($orderCount1 + 1) == $orderCount2); //事务内创建了一个订单
        $this->assertTrue(($storageItem1->stock_qty - $stockQty) == $storageItem2->stock_qty); //事务内创建订单后需要扣减库存
        $this->assertTrue(($accountItem1->amount - $amount) == $accountItem2->amount); //事务内创建订单后需要扣减账户金额
    }

    private function responseToArray($response)
    {
        $contents = $response->getBody()->getContents();
        return json_decode($contents, true);
    }
}

联系

扫描二维码加入微信群。希望更多朋友能相互学习,共同研究分布式事务知识。