windawake / laravel-reset-transaction
用于调用远程API服务的分布式事务
Requires
- php: ^7.1
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)
特性
- 开箱即用,无需重构原始项目的代码,与MySQL事务写入一致,简单易用。
- 遵循两阶段提交协议,这是一个强一致性事务。在高并发情况下,它支持读取提交事务的隔离级别,数据一致性接近100%的MySQL xa。
- 由于事务被分割成多个,成为几个小事务,压力测试发现死锁比MySQL普通事务少。
- 支持分布式事务嵌套,与savepoint一致。
- 支持避免不同业务代码并发引起的脏数据问题。
- 默认支持HTTP协议的服务端接口。如果想要支持其他协议,需要重写中间件。
- 支持子服务的嵌套分布式事务(世界首次).
- 支持服务,混合嵌套本地事务和分布式事务(世界首次)
- 支持3次超时重试,重复请求确保幂等性
- 支持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); } }
联系
扫描二维码加入微信群。希望更多朋友能相互学习,共同研究分布式事务知识。