seata分布式事务快速入门

前沿:

简单说下改分布式事务是从fascr改名场seata,是蚂蚁金服贡献(TCC)和阿里巴巴共同研究的分布式事务的完美解决方案。目前的活跃度很高,版本迭代也很频繁,seata的宗旨是完美解决各种分布式环境下的事务处理,无缝对接,所以目前是在研究和更新阶段。当然也有很多公司在使用该架构解决微服务的事务难题。在使用过程中会有不断的更新,包括server和client都会进行对应的更改,有建议说低版本不建议进行商用,1.0后可以,推荐使用阿里开源的GTS,也有的很多公司在用如:深圳市点购电子商务控股股份有限公司广州技术中心,北京科蓝,中航信移动科技有限公司,深圳雁联,会通教育等中小型企业再用。

目前版本:

seata分布式事务快速入门

迭代过程:

seata分布式事务快速入门

分布式解决方案?

  其实分布式的思想也就是将各个服务进行本地事务控制,在进行统一的提交或者回滚的方案只要一台服务出现宕机不可用等意外情况就会导致整个服务链路失败进而达到分布式事务的目的--数据的一致性。基于这样的想法有很多方案,如:2PC,TCC,TCC等,各有千秋。

seata解决了什么?

     源自seata的初衷

  • 对业务无侵入:即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入
  • 高性能:减少分布式事务解决方案所带来的性能消耗
  • 基于他的实现方案有四种(AT,TCC,SAGA,XA)
  • github:https://github.com/seata/seata

项目Demo

本项目案例以0.8版本进行测试  重要的会进行源代码的跟踪

1:引入服务的注册中心

以erueka为例:对应要进行更改seata服务下的file.cof文件。

seata分布式事务快速入门

途中可以看出seata支持的注册中心比较多.更改对应的type参数

在启动对应的注册中心:

seata分布式事务快速入门

seata服务的启动

这是TC需要管理整个微服务中的事务组,开启文件配置和注册中心,数据库(代理)的映射。启动脚本在bin目录下

seata分布式事务快速入门

数据库建立

    目前的seata在数据库这块只兼容mysql,并且使用mysql5.7以上的版本,这样会更好的支持seata数据库事务所的XA一致性问题。

执行对应的微服务下的sql文件。

seata分布式事务快速入门

  Seata在数据库的拆分上在做很大的改动 这个后期再进行数据的分表分库(mycat)和双机热备,但是mycat在使用过程中会有些问题有待完善。将各个微服务下的yml文件更改数据库的连接。

seata分布式事务快速入门

其次注意seata处的配置需要对应项目配置文件的事务组进行对应。

事务组的修改

   关于这两个配置文件具体每一项的含义是什么,需要进一步探讨seata中TC组件源码的跟踪。

如:更改事务组的key 是通过mapping来进映射的

seata分布式事务快速入门

      上面的错误是不能获取可用的服务 seata发现不了 从而进行不了事务组的管理,更改的结果可以通过seata的后台日志看到 进行错误的排查。

进行项目的测试  若访问正常

使用postman测试

localhost:8083/order/create?id=1&userId=1&productId=1&count=10&money=10        查看后台的日志和数据的变化

seata分布式事务快速入门

seata分布式事务快速入门

订单成功对应的账户余额减少,库存也减少。

异常情况

异常一:在其中一台服务进行异常代码的模拟 如:在账户扣款的时候超时

seata分布式事务快速入门

产生读取数据异常:

seata分布式事务快速入门

异常二:事务发起方异常

哪里有@GloableTrancation的业务方法 他就是改分布式事务的发起方 由他决定整个事务的状态

如:在其他服务都成功后在事务的发起者(订单)生成订单最后一步以上查看下日志

也是不能成功的

测试全局事务

假设我在其他的服务都进行添加该注解 会产生异常么?

不会。

将库存和账务都进行添加seata分布式事务快速入门

大家可以看下异常和正常情况日志的XID的变化

场景假设:如A调用B进行积分添加,在调用C服务进行工单的流程发送;假如B调用增加成功,而C失败!积分会进行增加么?

不能:分布式事务管理,就证明只要你本链路 添加了事务 事务就会管理到底

好啦!来瞧瞧他的工作原理 使用的思想挺好 涉及到 代理,构建,模板,策略,适配器等设计模式。

seata主要做的是:全局事务的扫描和数据源代理的配置

seata中重要的组件  

重要的组件:

TM:事务的发起者。用来告诉 TC,全局事务的开始,提交,回滚。

RM:具体的事务资源,每一个 RM 都会作为一个分支事务注册在 TC。

TC:事务的协调者seata-server,用于接收我们的事务的注册,提交和回滚。

应用百度的图片:

seata分布式事务快速入门

TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。

XID 在微服务调用链路的上下文中传播。

RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。

TM 向 TC 发起针对 XID 的全局提交或回滚决议。

TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

由异常可以查看出事务的发起调用异常产生时全局的XID为null,随之服务就不能调用导致XID不能再各个事务直接进行传递,进而进行全局的rollback,到达数据的一致性。

Seata是怎样获取服务应用等配置的?

Seata有一个全局的事务扫描器。

GloablTranscationScanner

seata分布式事务快速入门

该方法调用afterPropertiesSet中的initClient方法

seata分布式事务快速入门

主要就是初始化rm,tm(at模式中的角色)。对于一个服务既可以是TM角色也可以是RM角色,至于什么时候是 TM 或者 RM 则要看在一次全局事务中 @GlobalTransactional 注解标注在哪。Client创建的结果要与TC进行netty进行通讯告诉TC开始工作.

seata分布式事务快速入门

这是RM的初始化将进行Rpc的远程调用和监听seata分布式事务快速入门

会进行提交,回滚等的控制

利用Aop的核心动态代理方法

seata分布式事务快速入门

检查TCC模式 并生成代理对像

1:如果是tcc模式,就增加tcc相应的方法拦截器,否则就加入全局事务的方法拦截器

2:执行父类的wrapIfNecessary方法,获取所有的增强器,根据接口名称,以及pointcut匹配规则生成代理类,替换目标类

查看GlobalTransactionalInterceptor 的反射方法

seata分布式事务快速入门

这里使用桥接模式进对象的依赖注入:

查看TransactionalTemplate类的execute方法

seata分布式事务快速入门

如何开启分布式事务的:最终会调用DefaultGloabTrancation类

seata分布式事务快速入门

a,判断是不是分布式事务的发起者,GlobalTransactionRole.Launcher就是事务发起者角色,如果不是就直接return

b,这个方法主要利用TmRpcClient 之前建立好的channel给tc发送请求,获取全局事务id

c,将获取到到全局事务id放到seata上下文中

由全局的XID是RootContext获得并放在上下文进行管理

seata分布式事务快速入门

查看上下文类

seata分布式事务快速入门

2,处理方法本身的业务逻辑

3,处理业务逻辑的时候报错了,则进行事务回滚,并抛出异常

跟开启事务一样最终会调到DefaultGlobalTransaction,只是方法这回变成了rollback

如果角色是参与者就直接return,参与者没有责任去决定整体事务的状态;如果是发起者,则发送消息去tc回滚。tm的大致逻辑就是这样了样了

数据源的配置

AT模式还需要数据源的代理配置

原先的数据源替换成seata自带的代理数据源DataSourceProxy。使用代理数据源的原因是seata rm要在原先的数据操作上增加自己的一些业务处理,以此来达到分布式事务的功能。

查看AbstractDMLBaseExecutor

seata分布式事务快速入门

判断是否自动提交

这个方法主要是对执行sql操作之前和之后生成对应对数据快照,防止到时候rollback时可以数据回滚,这是比较关键的点

执行sql最终回到ExecuteTemplate,有点类似与上面tm中的transactionalTemplate

结束

目前seata只支持mysql(5.7+),版本现在升级到0.8,很快0.9马上出来,1.0有望在年底出来。最终,会支持更多的数据库,seata的集群模式等。。

8:版本更新0.9

问题1:关于数据切换问题:

seata分布式事务快速入门

0.9后可以不使用seata的自动代理dataSourceProxy,可以自动进行数据源的切换

如:在seataServer服务中此行:

seata分布式事务快速入门

设置为true,服务端进行对应的版本更新在数据源代理使用原有的dataSource 如

seata分布式事务快速入门

问题2:系统双数据源管理

如:我两个系统,一个系统是单数据源,两一个是双数据源,只把双数据源的这个系统改为自动代理可以吗?还是两个都需要改?

先进行双数据源的更改。

重要文件file.conf的配置几个参数的重要说明

配置文件的详解  随版本变化而不变化

transport {
  # tcp udt unix-domain-socket 
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat  | client和server通信心跳检测开关   |默认true开启 |
  heartbeat = true 
  #thread factory for netty
  thread-factory {
    boss-thread-prefix = "NettyBoss"
    worker-thread-prefix = "NettyServerNIOWorker"
    server-executor-thread-prefix = "NettyServerBizHandler"
    share-boss-worker = false
    client-selector-thread-prefix = "NettyClientSelector"
    client-selector-thread-size = 1
    client-worker-thread-prefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    boss-thread-size = 1
    #auto default pin or 8
    worker-thread-size = 8
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}

service {
  #vgroup->rgroup
  vgroup_mapping.my_test_tx_group = "default" #|  TM,RM    | 事务群组(附录1)   |my_test_tx_group为分组,配置项值为TC集群名 |
  #only support single node
  default.grouplist = "127.0.0.1:8091" #|   TM,RM   | TC服务列表  仅注册中心为file时使用  |
  #degrade current not support
  enableDegrade = false    |  TM    | 降级开关话说还没实现 在1.0会实现 |  默认false。业务侧根据连续错误数自动降级不走seata事务  |
  #disable
  disable = false
  #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
  max.commit.retry.timeout = "-1"    
  max.rollback.retry.timeout = "-1"
  disableGlobalTransaction = false  # 是否禁用全局事务
}

client {
  async.commit.buffer.limit = 10000
  lock {
    retry.internal = 10  # |  RM    | 校验或占用全局锁重试间隔 |  默认10,单位毫秒  |
    retry.times = 30  # |  RM    | 校验或占用全局锁重试次数 |  默认30  |
  }
  #控制事务回滚成功
  report.retry.count = 5   #|  TM,RM    | 一阶段结果上报TC重试次数 |  默认5次  |
  tm.commit.retry.count = 1  #|  TM    | 一阶段全局提交结果上报TC重试次数 |  默认1次,建议大于1  |
  tm.rollback.retry.count = 1 |  TM    | 一阶段全局回滚结果上报TC重试次数 |  默认1次,建议大于1  |
}

transaction {
  undo.data.validation = true #|  RM    | 二阶段回滚镜像校验 |  默认true开启,false关闭 |
  undo.log.serialization = "jackson"    #|  RM    | undo序列化方式 |  默认jackson  |
  undo.log.save.days = 7
  #schedule delete expired undo_log in milliseconds
  undo.log.delete.period = 86400000 | undo清理线程间隔时间 |默认86400000,单位毫秒    |
  undo.log.table = "undo_log" |db模式全局事务表名 |默认global_table    |

support {
  ## spring
  spring {
    # auto proxy the DataSource bean
    datasource.autoproxy = false #|  RM    | 数据源自动代理开关 |  默认false关闭  |
  }
}

 

本文章会持续的进行更改