dlm https://github.com/yedf/dtm
是一个用go实现的轻量级分布式事务的的框架 大概2000行代码,结合源码我们对分布式事务进理解
1.什么是分布式事务?
分布式事务
分布式事务简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
更多分布式事务介绍
分布式事务方案包括:
XA是由X/Open组织提出的分布式事务的规范,XA规范主要定义了(全局)事务管理器(TM)和(局部)资源管理器(RM)之间的接口。本地的数据库如mysql在XA中扮演的是RM角色
XA一共分为两阶段:
第一阶段(prepare):即所有的参与者RM准备执行事务并锁住需要的资源。参与者ready时,向TM报告已准备就绪。 第二阶段 (commit/rollback):当事务管理者(TM)确认所有参与者(RM)都ready后,向所有参与者发送commit命令。
目前主流的数据库基本都支持XA事务,包括mysql、oracle、sqlserver、postgre
我们看看本地数据库是如何支持XA的:
这里我们用两个窗口模拟两个微服务对数据进行分布式
微服务一 银行扣款服务 负责对数据进扣减
微服务二 银行加钱服务 负责对钱进行增加
第一阶段(prepare)
微服务一
XA start '4fPqCNTYeSG';
UPDATE `user_account` SET `balance`=balance + 30,`update_time`='2021-06-09 11:50:42.438' WHERE user_id = '1';
XA end '4fPqCNTYeSG';
XA prepare '4fPqCNTYeSG';
微服务二
XA start '4fPqCPijxyC';
UPDATE `user_account` SET `balance`=balance - 30,`update_time`='2021-06-09 11:50:42.493' WHERE user_id = '2'
XA end '4fPqCPijxyC';
XA prepare '4fPqCPijxyC';
第二阶段 (commit/rollback)
微服务一
# 服务1输出
xa commit '4fPqCNTYeSG';
这时候查询一下数据库
分支被提交后数据的修改就生效了
微服务二
# 服务2输出
xa commit '4fPqCNTYeSG';
DLM是如何实现事务管理器的?
简单来说就是 微服务一 执行
XA start '4fPqCNTYeSG';
微服务要执行的sql
XA end '4fPqCNTYeSG';
XA prepare '4fPqCNTYeSG';
并报告给下一个服务 我准备好了。
微服务二执行
XA start '4fPqCNTYeSG';
微服务要执行的sql
XA end '4fPqCNTYeSG';
XA prepare '4fPqCNTYeSG';
并报告给下一个事务
当事务全部准备好了 事务管理器依次回调各个微服务 的接口让他们依次提交 xa commit ‘4fPqCNTYeSG’;
当全部回调完成了就修改整个事务的状态为完成
dlm源码阅读
首先在客户端调用 XaGlobalTransaction
这个方法非常简单 就是生成一个 gid 和提交一个 trans_type 为xa的json调用 /api/dtmsvr/prepare
func (xc *XaClient) XaGlobalTransaction(gid string, xaFunc XaGlobalFunc) (rerr error) {
//生成xa事务的相关参数 生成事务id 和提交的服务器
xa := Xa{TransBase: TransBase{IDGenerator: IDGenerator{}, Dtm: xc.Server}, Gid: gid}
data := &M{
"gid": gid,
"trans_type": "xa",
}
//提交参数为 {
// "gid": gid,
// "trans_type": "xa",
// }
//调用事务管理器的接口 /api/dtmsvr/prepare 提交事务状态为 prepare
rerr = xa.CallDtm(data, "prepare")
if rerr != nil {
return
}
//等待Dtm返回事务创建是否成功
var resp *resty.Response
// 小概率情况下,prepare成功了,但是由于网络状况导致上面Failure,那么不执行下面defer的内容,等待超时后再回滚标记事务失败,也没有问题
defer func() {
x := recover()
operation := If(x != nil || rerr != nil, "abort", "submit").(string)
err := xa.CallDtm(data, operation)
if rerr == nil { // 如果用户函数没有返回错误,那么返回dtm的
rerr = err
}
if x != nil {
panic(x)
}
}()
resp, rerr = xaFunc(&xa)
rerr = CheckResponse(resp, rerr)
return
}
数据管理器这里其实就插入了一条数据 gid 来自客户端提交 trans_type 为客户端提交 Status = “prepared”的一天数据到数据库
写个Sping版本的XA事务管理器 XA-Zoom
//todo 用spring拦截器和注解扫描实现 一个注解开启XA事务的方法
//todo 用mybatis的sql拦截实现 XA事务包裹
//todo 用简单时间轮实现事务调度
// java 我的超人
3.SEGA事务
SAGA最初出现在1987年Hector Garcaa-Molrna & Kenneth Salem发表的论文SAGAS里。其核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。
如果我们要进行一个类似于银行跨行转账的业务,转出(TransOut)和转入(TransIn)分别在不同的微服务里,一个成功完成的SAGA事务典型的时序图如下:
Dlm的SEGA 是简单的Sega 简单来说就是 一次http请求带上 分支信息 再由Dlm依次调用分支实现
如果有一个分支返回失败 就调用补偿操作。
4.TCC 事务
TCC分为3个阶段
- Try 阶段:尝试执行,完成所有业务检查(一致性), 预留必须业务资源(准隔离性)
- Confirm 阶段:如果所有分支的Try都成功了,则走到Confirm阶段。Confirm真正执行业务,不作任何业务检查,只使用 Try 阶段预留的业务资源
- Cancel 阶段:如果所有分支的Try有一个失败了,则走到Cancel阶段。Cancel释放 Try 阶段预留的业务资源。
如果我们要进行一个类似于银行跨行转账的业务,转出(TransOut)和转入(TransIn)分别在不同的微服务里,一个成功完成的TCC事务典型的时序图如下:
5.服务提供和客户端调用
//todo