dlm https://github.com/yedf/dtm
是一个用go实现的轻量级分布式事务的的框架 大概2000行代码,结合源码我们对分布式事务进理解

1.什么是分布式事务?

分布式事务

分布式事务简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
更多分布式事务介绍
分布式事务方案包括:

  • xa
  • tcc
  • saga
  • 可靠消息

    2.XA事务

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的:
这里我们用两个窗口模拟两个微服务对数据进行分布式

微服务一 银行扣款服务 负责对数据进扣减
微服务二 银行加钱服务 负责对钱进行增加

image.png

第一阶段(prepare)

微服务一

  1. XA start '4fPqCNTYeSG';
  2. UPDATE `user_account` SET `balance`=balance + 30,`update_time`='2021-06-09 11:50:42.438' WHERE user_id = '1';
  3. XA end '4fPqCNTYeSG';
  4. XA prepare '4fPqCNTYeSG';

微服务二

  1. XA start '4fPqCPijxyC';
  2. UPDATE `user_account` SET `balance`=balance - 30,`update_time`='2021-06-09 11:50:42.493' WHERE user_id = '2'
  3. XA end '4fPqCPijxyC';
  4. XA prepare '4fPqCPijxyC';

第二阶段 (commit/rollback)

微服务一

  1. # 服务1输出
  2. xa commit '4fPqCNTYeSG';

这时候查询一下数据库
image.png

分支被提交后数据的修改就生效了

微服务二

  1. # 服务2输出
  2. xa commit '4fPqCNTYeSG';

image.png

xa 事务提交完毕

分布式事务----dlm项目源码阅读 - 图6

分布式事务----dlm项目源码阅读 - 图7

DLM是如何实现事务管理器的?

简单来说就是 微服务一 执行

  1. XA start '4fPqCNTYeSG';
  2. 微服务要执行的sql
  3. XA end '4fPqCNTYeSG';
  4. XA prepare '4fPqCNTYeSG';

并报告给下一个服务 我准备好了。

微服务二执行

  1. XA start '4fPqCNTYeSG';
  2. 微服务要执行的sql
  3. XA end '4fPqCNTYeSG';
  4. XA prepare '4fPqCNTYeSG';

并报告给下一个事务

当事务全部准备好了 事务管理器依次回调各个微服务 的接口让他们依次提交 xa commit ‘4fPqCNTYeSG’;
当全部回调完成了就修改整个事务的状态为完成
dlm源码阅读

首先在客户端调用 XaGlobalTransaction

这个方法非常简单 就是生成一个 gid 和提交一个 trans_type 为xa的json调用 /api/dtmsvr/prepare

  1. func (xc *XaClient) XaGlobalTransaction(gid string, xaFunc XaGlobalFunc) (rerr error) {
  2. //生成xa事务的相关参数 生成事务id 和提交的服务器
  3. xa := Xa{TransBase: TransBase{IDGenerator: IDGenerator{}, Dtm: xc.Server}, Gid: gid}
  4. data := &M{
  5. "gid": gid,
  6. "trans_type": "xa",
  7. }
  8. //提交参数为 {
  9. // "gid": gid,
  10. // "trans_type": "xa",
  11. // }
  12. //调用事务管理器的接口 /api/dtmsvr/prepare 提交事务状态为 prepare
  13. rerr = xa.CallDtm(data, "prepare")
  14. if rerr != nil {
  15. return
  16. }
  17. //等待Dtm返回事务创建是否成功
  18. var resp *resty.Response
  19. // 小概率情况下,prepare成功了,但是由于网络状况导致上面Failure,那么不执行下面defer的内容,等待超时后再回滚标记事务失败,也没有问题
  20. defer func() {
  21. x := recover()
  22. operation := If(x != nil || rerr != nil, "abort", "submit").(string)
  23. err := xa.CallDtm(data, operation)
  24. if rerr == nil { // 如果用户函数没有返回错误,那么返回dtm的
  25. rerr = err
  26. }
  27. if x != nil {
  28. panic(x)
  29. }
  30. }()
  31. resp, rerr = xaFunc(&xa)
  32. rerr = CheckResponse(resp, rerr)
  33. return
  34. }

数据管理器这里其实就插入了一条数据 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项目源码阅读 - 图8
分布式事务----dlm项目源码阅读 - 图9分布式事务----dlm项目源码阅读 - 图10分布式事务----dlm项目源码阅读 - 图11
Dlm的SEGA 是简单的Sega 简单来说就是 一次http请求带上 分支信息 再由Dlm依次调用分支实现
如果有一个分支返回失败 就调用补偿操作。

4.TCC 事务

TCC分为3个阶段

  • Try 阶段:尝试执行,完成所有业务检查(一致性), 预留必须业务资源(准隔离性)
  • Confirm 阶段:如果所有分支的Try都成功了,则走到Confirm阶段。Confirm真正执行业务,不作任何业务检查,只使用 Try 阶段预留的业务资源
  • Cancel 阶段:如果所有分支的Try有一个失败了,则走到Cancel阶段。Cancel释放 Try 阶段预留的业务资源。

如果我们要进行一个类似于银行跨行转账的业务,转出(TransOut)和转入(TransIn)分别在不同的微服务里,一个成功完成的TCC事务典型的时序图如下:
分布式事务----dlm项目源码阅读 - 图12

失败的时序图如下:
分布式事务----dlm项目源码阅读 - 图13

5.服务提供和客户端调用

//todo