ToB企服应用市场:ToB评测及商务社交产业平台

标题: Seata聚合 AT、TCC、SAGA 、 XA事务模式打造一站式的分布式事务解决方案 [打印本页]

作者: 魏晓东    时间: 2022-8-23 16:40
标题: Seata聚合 AT、TCC、SAGA 、 XA事务模式打造一站式的分布式事务解决方案
Seata


Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各BU业务进行了有力的支撑。经过多年沉淀与积累,商业化产品先后在阿里云、金融云进行售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,开放以来,广受欢迎,不到一年已经成为最受欢迎的分布式事务解决方案。
官方中文网:https://seata.io/zh-cn
github项目地址:https://github.com/seata/seata
4.1 Seata术语

TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

Seata  致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

4.1 Seata AT模式

​                Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。其中AT模式最受欢迎,使用也非常简单,但它内在的原理不简单。
AT模式的相关资料请参考官方文档说明:https://seata.io/zh-cn/docs/overview/what-is-seata.html
下图是AT模式的执行流程:

4.1.1 AT模式及工作流程

见官方文档:https://seata.io/zh-cn/docs/overview/what-is-seata.html
4.1.2 Seata-Server安装

我们在选择用Seata版本的时候,可以先参考下官方给出的版本匹配(Seata版本也可以按自己的要求选择):
https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明
Spring Cloud Alibaba VersionSentinel VersionNacos VersionRocketMQ VersionDubbo VersionSeata Version2.2.5.RELEASE1.8.01.4.14.4.02.7.81.3.02.2.3.RELEASE or 2.1.3.RELEASE or 2.0.3.RELEASE1.8.01.3.34.4.02.7.81.3.02.2.1.RELEASE or 2.1.2.RELEASE or 2.0.2.RELEASE1.7.11.2.14.4.02.7.61.2.02.2.0.RELEASE1.7.11.1.44.4.02.7.4.11.0.02.1.1.RELEASE or 2.0.1.RELEASE or 1.5.1.RELEASE1.7.01.1.44.4.02.7.30.9.02.1.0.RELEASE or 2.0.0.RELEASE or 1.5.0.RELEASE1.6.31.1.14.4.02.7.30.7.1我们当前SpringCloud Alibaba的版本是2.2.5.RELEASE,对应Seata版本是1.3.0,所以我们首先安装Seata-Server1.3.0
我们直接基于docker启动得到:
  1. docker run --name seata-server -p 8091:8091 -d -e SEATA_IP=192.168.200.129 -e SEATA_PORT=8091  --restart=on-failure seataio/seata-server:1.3.0
复制代码
4.1.3 集成springcloud-alibaba

我们接下来开始在项目中集成使用Seata的AT模式实现分布式事务控制,关于如何集成,官方也给出了很多例子,可以通过
https://github.com/seata/seata-samples

所以各种集成模式需要大家都自行的去翻看对应的samples。
集成可以按照如下步骤实现:
  1. 1:引入依赖包spring-cloud-starter-alibaba-seata
  2. 2:配置Seata
  3. 3:创建代理数据源
  4. 4:@GlobalTransactional全局事务控制
复制代码
案例需求:

如上图,如果用户打车成功,需要修改司机状态、下单、记录支付日志,而每个操作都是调用了不同的服务,比如此时hailtaxi-driver服务执行成功了,但是hailtaxi-order有可能执行失败了,这时候如何实现跨服务事务回滚呢?这就要用到分布式事务。
鉴于我们一般事务都是在service层进行的管理,所以,改造一下hailtaxi-order中的OrderInfoController#add
方法,将业务实现放到对应的Service中
  1. /***
  2.      * 下单
  3.      */
  4. /*@PostMapping
  5.     public OrderInfo add(){
  6.         //修改司机信息  司机ID=1
  7.         Driver driver = driverFeign.status("3",2);
  8.         //创建订单
  9.         OrderInfo orderInfo = new OrderInfo("No"+((int)(Math.random()*10000)), (int)(Math.random()*100), new Date(), "深圳北站", "罗湖港", driver);
  10.         orderInfoService.add(orderInfo);
  11.         return orderInfo;
  12.     }*/
  13. @PostMapping
  14. public OrderInfo add() {
  15.     return orderInfoService.addOrder();
  16. }
复制代码
在Service实现中:
  1. @Service
  2. public class OrderInfoServiceImpl  implements OrderInfoService {
  3.      @Autowired
  4.     private DriverFeign driverFeign;
  5.     /**
  6.      * 1、修改司机信息  司机ID=1
  7.      * 2、创建订单
  8.      * @return
  9.      */
  10.     @Override
  11.     public OrderInfo addOrder() {
  12.         //修改司机信息  司机ID=1
  13.         Driver driver = driverFeign.status("1",2);
  14.         //创建订单
  15.         OrderInfo orderInfo = new OrderInfo("No"+((int)(Math.random()*10000)), (int)(Math.random()*100), new Date(), "深圳北站", "罗湖港", driver);
  16.         int count = orderInfoMapper.add(orderInfo);
  17.         System.out.println("====count="+count);
  18.         return orderInfo;
  19.     }
  20. }   
复制代码
案例实现:
0) 创建undo_log表
在每个数据库中都需要创建该表:
  1. CREATE TABLE `undo_log` (
  2.   `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3.   `branch_id` bigint(20) NOT NULL,
  4.   `xid` varchar(100) NOT NULL,
  5.   `context` varchar(128) NOT NULL,
  6.   `rollback_info` longblob NOT NULL,
  7.   `log_status` int(11) NOT NULL,
  8.   `log_created` datetime NOT NULL,
  9.   `log_modified` datetime NOT NULL,
  10.   PRIMARY KEY (`id`),
  11.   UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
  12. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
复制代码
1)依赖引入
我们首先在hailtaxi-driver和hailtaxi-order中引入依赖:
  1. <dependency>
  2.     <groupId>com.alibaba.cloud</groupId>
  3.     <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  4.     <version>2.2.5.RELEASE</version>
  5. </dependency>
复制代码
2)配置Seata
依赖引入后,我们需要在项目中配置SeataClient  端信息,关于SeataClient端配置信息,官方也给出了很多版本的模板,可以参考官方项目:
https://github.com/seata/seata/tree/1.3.0/script,如下图:


我们可以选择spring,把application.yml文件直接拷贝到工程中,文件如下:

完整文件内容见:https://github.com/seata/seata/blob/1.3.0/script/client/spring/application.yml
修改后我们在hailtaxi-driver和hailtaxi-order项目中配置如下:
  1. seata:
  2.   enabled: true
  3.   application-id: ${spring.application.name}
  4.   tx-service-group: my_seata_group
  5.   enable-auto-data-source-proxy: true
  6.   use-jdk-proxy: false
  7.   excludes-for-auto-proxying: firstClassNameForExclude,secondClassNameForExclude
  8.   client:
  9.     rm:
  10.       async-commit-buffer-limit: 1000
  11.       report-retry-count: 5
  12.       table-meta-check-enable: false
  13.       report-success-enable: false
  14.       saga-branch-register-enable: false
  15.       lock:
  16.         retry-interval: 10
  17.         retry-times: 30
  18.         retry-policy-branch-rollback-on-conflict: true
  19.     tm:
  20.       degrade-check: false
  21.       degrade-check-period: 2000
  22.       degrade-check-allow-times: 10
  23.       commit-retry-count: 5
  24.       rollback-retry-count: 5
  25.     undo:
  26.       data-validation: true
  27.       log-serialization: jackson
  28.       log-table: undo_log
  29.       only-care-update-columns: true
  30.     log:
  31.       exceptionRate: 100
  32.   service:
  33.     vgroup-mapping:
  34.       my_seata_group: default
  35.     grouplist:
  36.       default: 192.168.200.129:8091
  37.     enable-degrade: false
  38.     disable-global-transaction: false
  39.   transport:
  40.     shutdown:
  41.       wait: 3
  42.     thread-factory:
  43.       boss-thread-prefix: NettyBoss
  44.       worker-thread-prefix: NettyServerNIOWorker
  45.       server-executor-thread-prefix: NettyServerBizHandler
  46.       share-boss-worker: false
  47.       client-selector-thread-prefix: NettyClientSelector
  48.       client-selector-thread-size: 1
  49.       client-worker-thread-prefix: NettyClientWorkerThread
  50.       worker-thread-size: default
  51.       boss-thread-size: 1
  52.     type: TCP
  53.     server: NIO
  54.     heartbeat: true
  55.     serialization: seata
  56.     compressor: none
  57.     enable-client-batch-send-request: true
复制代码
关于配置文件内容参数比较多,我们需要掌握核心部分:
  1. seata_transaction: default:事务分组,前面的seata_transaction可以自定义,通过事务分组很方便找到集群节点信息。
  2. tx-service-group: seata_transaction:指定应用的事务分组,和上面定义的分组前部分保持一致。
  3. default: 192.168.200.129:8091:服务地址,seata-server服务地址。
复制代码
注意:
现在配置信息都是托管到nacos中的,所以可以直接将配置存储到nacos中
hailtaxi-order

hailtaxi-driver

3)代理数据源
通过代理数据源可以保障事务日志数据和业务数据能同步,关于代理数据源早期需要手动创建,但是随着Seata版本升级,不同版本实现方案不一样了,下面是官方的介绍:
  1. 1.1.0: seata-all取消属性配置,改由注解@EnableAutoDataSourceProxy开启,并可选择jdk proxy或者cglib proxy
  2. 1.0.0: client.support.spring.datasource.autoproxy=true
  3. 0.9.0: support.spring.datasource.autoproxy=true
复制代码
我们当前的版本是1.3.0,所以我们创建代理数据源只需要在启动类上添加@EnableAutoDataSourceProxy注解即可,
在hailtaxi-order及hailtaxi-driver的启动类上分别添加该注解:
  1. @SpringBootApplication
  2. @EnableDiscoveryClient
  3. @EnableFeignClients(basePackages = {"com.itheima.driver.feign"})
  4. @EnableAutoDataSourceProxy
  5. public class OrderApplication {
  6. }
复制代码
4)全局事务控制
打车成功创建订单是由客户发起,在hailtaxi-order中执行,并且feign调用hailtaxi-driver,所以hailtaxi-order是全局事务入口,我们在OrderInfoServiceImpl.addOrder()方法上添加@GlobalTransactional,那么此时该方法就是全局事务的入口,
  1. @Override
  2. @GlobalTransactional
  3. public OrderInfo addOrder() {
  4.     //修改司机信息  司机ID=1
  5.     Driver driver = driverFeign.status("1",2);
  6.     //创建订单
  7.     OrderInfo orderInfo = new OrderInfo("No"+((int)(Math.random()*10000)), (int)(Math.random()*100), new Date(), "深圳北站", "罗湖港", driver);
  8.     int count = orderInfoMapper.add(orderInfo);
  9.     System.out.println("====count="+count);
  10.     return orderInfo;
  11. }
复制代码
5)分布式事务测试
1、测试正常情况,启动测试
将id=1的司机状态手动改为1,然后进行测试
2、异常测试,在hailtaxi-order的service方法中添加一个异常,
  1. @Override
  2. @GlobalTransactional
  3. public OrderInfo addOrder() {
  4.     //修改司机信息  司机ID=1
  5.     Driver driver = driverFeign.status("1",2);
  6.     //创建订单
  7.     OrderInfo orderInfo = new OrderInfo("No"+((int)(Math.random()*10000)), (int)(Math.random()*100), new Date(), "深圳北站", "罗湖港", driver);
  8.     int count = orderInfoMapper.add(orderInfo);
  9.     System.out.println("====count="+count);
  10.     //模拟异常
  11.     int i = 1 / 0;
  12.     return orderInfo;
  13. }
复制代码
测试前,将id=1的司机状态手动改为1,将订单表清空,再次测试,看状态是否被更新,订单有没有添加,以此验证分布式事务是否控制成功!
4.2 Seata TCC模式

一个分布式的全局事务,整体是 两阶段提交 的模型。全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:

根据两阶段行为模式的不同,我们将分支事务划分为 Automatic (Branch) Transaction ModeManual (Branch) Transaction Mode.
AT 模式(参考链接 TBD)基于 支持本地 ACID 事务关系型数据库
相应的,TCC 模式,不依赖于底层数据资源的事务支持:
所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。
TCC实现原理:
  1. 有一个 TCC 拦截器,它会封装 Confirm 和 Cancel 方法作为资源(用于后面 TC 来 commit 或 rollback 操作)
  2. 封装完,它会本地缓存到 RM (缓存的是方法的描述信息),可以简单认为是放到一个 Map 里面
  3. 当 TC 想调用的时候,就可以从 Map 里找到这个方法,用反射调用就可以了
  4. 另外,RM 不光是注册分支事务(分支事务是注册到 TC 里的 GlobalSession 中的)
  5. 它还会把刚才封装的资源里的重要属性(事务ID、归属的事务组等)以资源的形式注册到 TC 中的 RpcContext
  6. 这样,TC 就知道当前全局事务都有哪些分支事务了(这都是分支事务初始化阶段做的事情)
  7. 举个例子:RpcContext里面有资源 123,但是 GlobalSession 里只有分支事务 12
  8. 于是 TC 就知道分支事务 3 的资源已经注册进来了,但是分支事务 3 还没注册进来
  9. 这时若 TM 告诉 TC 提交或回滚,那 GlobalSession 就会通过 RpcContext 找到 1 和 2 的分支事务的位置(比如该调用哪个方法)
  10. 当 RM 收到提交或回滚后,就会通过自己的本地缓存找到对应方法,最后通过反射或其他机制去调用真正的 Confirm 或 Cancel
复制代码
5 Seata注册中心

参看:https://github.com/seata/seata/tree/1.3.0/script 可以看到seata支持多种注册中心!
5.1 服务端注册中心配置

服务端注册中心(位于seata-server的registry.conf配置文件中的registry.type参数),为了实现seata-server集群高可用不会使用file类型,一般会采用第三方注册中心,例如zookeeper、redis、eureka、nacos等。
我们这里使用nacos,seata-server的registry.conf配置如下:
由于我们是基于docker启动的seata,故可以直接进入到容器内部修改配置文件/resources/registry.conf
  1. registry {
  2.   # file ...nacos ...eureka...redis...zk...consul...etcd3...sofa
  3.   type = "nacos"
  4.   nacos {
  5.     application = "seata-server"
  6.     serverAddr = "192.168.200.129:8848"
  7.     group = "SEATA_GROUP"
  8.     namespace = "1ebba5f6-49da-40cc-950b-f75c8f7d07b3"
  9.     cluster = "default"
  10.     username = "nacos"
  11.     password = "nacos"
  12.   }
  13. }
复制代码
此时我们再重新启动容器,访问:http://192.168.200.129:8848/nacos 看seata是否已注册到nacos中

5.2 客户端注册中心配置

项目中,我们需要使用注册中心,添加如下配置即可(在nacos配置中心的hailtaxi-order.yaml和hailtaxi-driver-dev.yaml都修改)
参看:https://github.com/seata/seata/tree/1.3.0/script
  1.   registry:
  2.     type: nacos
  3.     nacos:
  4.       application: seata-server
  5.       server-addr: 192.168.200.129:8848
  6.       group : "SEATA_GROUP"
  7.       namespace: 1ebba5f6-49da-40cc-950b-f75c8f7d07b3
  8.       username: "nacos"
  9.       password: "nacos"
复制代码
此时就可以注释掉配置中的default.grouplist="192.168.200.129:8091"

完整配置如下:
  1. seata:
  2. enabled: true
  3. application-id: ${spring.application.name}
  4. tx-service-group: my_seata_group
  5. enable-auto-data-source-proxy: true
  6. use-jdk-proxy: false
  7. excludes-for-auto-proxying: firstClassNameForExclude,secondClassNameForExclude
  8. client:
  9. rm:
  10. async-commit-buffer-limit: 1000
  11. report-retry-count: 5
  12. table-meta-check-enable: false
  13. report-success-enable: false
  14. saga-branch-register-enable: false
  15. lock:
  16.   retry-interval: 10
  17.   retry-times: 30
  18.   retry-policy-branch-rollback-on-conflict: true
  19. tm:
  20. degrade-check: false
  21. degrade-check-period: 2000
  22. degrade-check-allow-times: 10
  23. commit-retry-count: 5
  24. rollback-retry-count: 5
  25. undo:
  26. data-validation: true
  27. log-serialization: jackson
  28. log-table: undo_log
  29. only-care-update-columns: true
  30. log:
  31. exceptionRate: 100
  32. service:
  33. vgroup-mapping:
  34. my_seata_group: default
  35. #grouplist:
  36. #default: 192.168.200.129:8091
  37. enable-degrade: false
  38. disable-global-transaction: false
  39. transport:
  40. shutdown:
  41. wait: 3
  42. thread-factory:
  43. boss-thread-prefix: NettyBoss
  44. worker-thread-prefix: NettyServerNIOWorker
  45. server-executor-thread-prefix: NettyServerBizHandler
  46. share-boss-worker: false
  47. client-selector-thread-prefix: NettyClientSelector
  48. client-selector-thread-size: 1
  49. client-worker-thread-prefix: NettyClientWorkerThread
  50. worker-thread-size: default
  51. boss-thread-size: 1
  52. type: TCP
  53. server: NIO
  54. heartbeat: true
  55. serialization: seata
  56. compressor: none
  57. enable-client-batch-send-request: true
  58. registry:
  59. type: nacos
  60. nacos:
  61. application: seata-server
  62. server-addr: 192.168.200.129:8848
  63. group : "SEATA_GROUP"
  64. namespace: 1ebba5f6-49da-40cc-950b-f75c8f7d07b3
  65. username: "nacos"
  66. password: "nacos"     
复制代码
测试:
启动服务再次测试,查看分布式事务是否仍然能控制住!!!
6 Seata高可用

seata-server 目前使用的是一个单节点,能否抗住高并发是一个值得思考的问题。生产环境项目几乎都需要确保能扛高并发、具备高可用的能力,因此生产环境项目一般都会做集群。
上面配置也只是将注册中心换成了nacos,而且是单机版的,如果要想实现高可用,就得实现集群,集群就需要做一些动作来保证集群节点间的数据同步(会话共享)等操作
我们需要准备2个seata-server节点,并且seata-server的事务日志存储模式,共支持3种方式,
1):file【集群不可用】
2):redis
3):db
我们这里选择redis存储会话信息实现共享。
1、启动第二个seata-server节点
  1. docker run --name seata-server-n2 -p 8092:8092 -d -e SEATA_IP=192.168.200.129 -e SEATA_PORT=8092  --restart=on-failure seataio/seata-server:1.3.0
复制代码
2、进入容器修改配置文件 registry.conf,添加注册中心的配置
  1. registry {
  2.   # file ...nacos ...eureka...redis...zk...consul...etcd3...sofa
  3.   type = "nacos"
  4.   nacos {
  5.     application = "seata-server"
  6.     serverAddr = "192.168.200.129:8848"
  7.     group = "SEATA_GROUP"
  8.     namespace = "1ebba5f6-49da-40cc-950b-f75c8f7d07b3"
  9.     cluster = "default"
  10.     username = "nacos"
  11.     password = "nacos"
  12.   }
  13. }
复制代码
3、修改seata-server 事务日志的存储模式,resources/file.conf 改动如下:
我们采用基于redis来存储集群每个节点的事务日志,通过docker允许一个redis
  1. docker run --name redis6.2 --restart=on-failure -p 6379:6379 -d redis:6.2
复制代码
然后修改seata-server的file.conf,修改如下:
  1. ## transaction log store, only used in seata-server
  2. store {
  3.   ## store mode: file...db...redis
  4.   mode = "redis"
  5.   ## file store property
  6.   file {
  7.     ## store location dir
  8.     dir = "sessionStore"
  9.     # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
  10.     maxBranchSessionSize = 16384
  11.     # globe session size , if exceeded throws exceptions
  12.     maxGlobalSessionSize = 512
  13.     # file buffer size , if exceeded allocate new buffer
  14.     fileWriteBufferCacheSize = 16384
  15.     # when recover batch read size
  16.     sessionReloadReadSize = 100
  17.     # async, sync
  18.     flushDiskMode = async
  19.   }
  20.   ## database store property
  21.   db {
  22.     ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
  23.     datasource = "druid"
  24.     ## mysql/oracle/postgresql/h2/oceanbase etc.
  25.     dbType = "mysql"
  26.     driverClassName = "com.mysql.jdbc.Driver"
  27.     url = "jdbc:mysql://127.0.0.1:3306/seata"
  28.     user = "mysql"
  29.     password = "mysql"
  30.     minConn = 5
  31.     maxConn = 30
  32.     globalTable = "global_table"
  33.     branchTable = "branch_table"
  34.     lockTable = "lock_table"
  35.     queryLimit = 100
  36.     maxWait = 5000
  37.   }
  38.   ## redis store property
  39.   redis {
  40.     host = "192.128.200.129"
  41.     port = "6379"
  42.     password = ""
  43.     database = "0"
  44.     minConn = 1
  45.     maxConn = 10
  46.     queryLimit = 100
  47.   }
  48. }
复制代码
如果基于DB来存储seata-server的事务日志数据,则需要创建数据库seata,表信息如下:
https://github.com/seata/seata/blob/1.3.0/script/server/db/mysql.sql
修改完后重启
注意:另一个seata-server节点也同样需要修改其存储事务日志的模式
4、再次启动服务测试,查看分布式事务是否依然能控制成功!
本文由传智教育博学谷 - 狂野架构师教研团队发布
如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力
转载请注明出处!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4