Laravel基于RT模式实现分布式事务(突破技术支持子服务嵌套事务)
快速预览#
安装 laravel5.5 - laravel8 之间的版本,然后安装 composer 包
## 必须使用composer2版本
composer require windawake/laravel-reset-transaction dev-master
首先创建 order,storage,account3 个 mysql 数据库实例,3 个控制器,3 个 model,在 phpunit.xml 增加 testsuite Transaction,然后启动 web 服务器。这些操作只需要执行下面命令全部完成
php artisan resetTransact:create-examples && php artisan serve --host=0.0.0.0 --port=8000
打开另一个 terminal,启动端口为 8001 的事务中心,也可以换成 go 版事务中心 github.com/windawake/gortcenter
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%。
- 性能超过 seata AT 模式,由于事务拆分成多个,变成了几个小事务,压测发现比 mysql 普通事务更少发生死锁。
- 支持分布式事务嵌套,与 savepoint 一致效果。
- 支持避免不同业务代码并发造成脏数据的问题。
- 默认支持 http 协议的服务化接口,想要支持其它协议则需要重写中间件。
- 支持子服务嵌套分布式事务(突破技术)。
- 支持服务,本地事务和分布式事务混合嵌套
- 支持超时 3 次重试,重复请求保证幂等性
- 几乎支持所有 sql 语句,可以批量插入,批量更新,批量删除(突破技术)
- 支持检测 xa prepare 造成的锁,更加精准地释放 xa 锁
- 支持 go,java 语言(开发中)
- 分布式事务可视化管理后台(开发中)
对比阿里 seata AT 模式,有什么优点?请阅读 博客:强一致性的分布式事务几种模式对比(php 有了新方案)
可行性报告,请阅读 博客:分布式事务 RT 模式可行性报告(待完善)
解决了哪些并发场景#
- 一个待发货订单,用户同时操作发货和取消订单,只有一个成功
- 积分换取优惠券,只要出现积分不够扣减或者优惠券的库存不够扣减,就会全部失败。
原理解析#
Reset Transaction,中文名为重置型分布式事务,又命名为 RT 模式,与 seata AT 模式都是属于二段提交。跟中国电视剧的【穿越】是同一个意思。
看过《明日边缘》电影就会知道,存档和读档的操作。这个分布式事务组件仿造《明日边缘》电影的原理,每次请求基础服务一开始时读档,然后继续后面的操作,结束时所有操作全部回滚并且存档,最后一步 commit 把存档全部执行成功。整个过程是遵守两段提交协议,先 prepare,最后 commit。
以创建一个订单并且扣减一个库存的场景为例子,画了以下流程图。
右图开启分布式事务 RT 模式后,比左图多了请求 4。请求 4 所做的事情,都是请求 1-3 之前做过的东西,又回来原点重新再来,最终提交事务,结束这创建订单的流程。
支持子服务嵌套分布式事务(突破技术)#
世界级的一个难题:A 服务 commit->B 服务 rollback->C 服务 commit->D 服务 commit sql,这种场景下,ABCD 都是不同数据库,如何才能实现让 A 服务提交了 B 服务,回滚了 C 服务和 D 服务的所有操作呢?
这个问题,seata 和 go-dtm 都没法解决。解决问题的关键点在于 C 服务和 D 服务必须要假提交,不能真提交,如果真提交就无力回天了。
实现支持子服务嵌套分布式事务后,带来什么好处呢?可以让 A 服务成为别人的服务,并且任意嵌套在链路里任何一层。打破了以往的束缚:A 服务必须是根服务,A 服务若要成为子服务,必须大改代码。用了 RT 模式的话,A 服务不需要修改代码就能成为别人的服务。
如何使用#
在 laravel 框架里,把门面 DB 换成 RT 就能实现分布式事务。
<?php
use Illuminate\Support\Facades\DB;
use Laravel\ResetTransaction\Facades\RT;
DB::beginTransaction();
...
DB::commit();
#换成
RT::beginTransaction();
...
RT::commit();
#详细的例子
RT::beginTransaction();
(new Client)->put('http://127.0.0.1:8000/api/resetOrder/11', [
'json' => [
'order_no' => 'aaa',
],
'headers' => [
'rt_request_id' => session_create_id(), //支持幂等
'rt_transact_id' => RT::getTransactId(), //让订单服务知道,当前是在分布式事务内部
]
]);
RT::commit();
具体例子可以查看 composer 包里面的 vendor/windawake/laravel-reset-transaction/examples/tests/Transaction/ServiceTest.php
的代码。
个人笔记#
本人之前写了 laravel 快速服务化包,但是它没有解决数据一致性的问题。尝试用 XA,但是 XA 只能解决跨数据但是不能解决跨服务的问题。然后我又尝试去研究 tcc 和 seata,难学而且难用,没办法了只能自创分布式事务解决方案。一直以来,我一直以为单单只用 mysql 是没法解决分布式事务的问题,现在终于明白,还是有办法滴!
希望有更多的朋友相互学习和一起研究分布式事务的知识。
相关资源#
laravel 版本:
github.com/windawake/laravel-reset...
gitee.com/windawake/laravel-reset-...
hyperf 版本(包含压测报告):
github.com/windawake/hyperf-reset-...
gitee.com/windawake/hyperf-reset-t...
go 版本事务中心
github.com/windawake/gortcenter
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: