• Seata服务的搭建、Seata AT模式演示


    Seata 快速开始

    一、数据库中添加回滚日志表UNDO_LOG:

    1.  UNDO_LOG必须在每个业务数据库中出现,用于保存回滚操作数据。
    2. 全局事务提交时,对应的UNDO_LOG记录直接删除。
    3. 全局事务回滚时,通过该表回滚到以前的数据,并删除UNDO_LOG记录。

    Seata的UNDO_LOG表和数据库的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. `ext` varchar(100) DEFAULT NULL,
    11. PRIMARY KEY (`id`),
    12. UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    13. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

    二、搭建Seata的TC 

    1.在官网下载seata-server包,解压。下载地址

    2.tc服务在管理分布式事务时,需要记录事务相关数据到数据库中,你需要提前创建好这些表,创建一个seata的数据库,新增如下表。

    • global_table:全局事务表
    • branch_table:分支信息表
    • lock_table:加锁的表
    1. -- 全局事务表--
    2. CREATE TABLE IF NOT EXISTS `global_table`
    3. (
    4. `xid` VARCHAR(128) NOT NULL,
    5. `transaction_id` BIGINT,
    6. `status` TINYINT NOT NULL,
    7. `application_id` VARCHAR(32),
    8. `transaction_service_group` VARCHAR(32),
    9. `transaction_name` VARCHAR(128),
    10. `timeout` INT,
    11. `begin_time` BIGINT,
    12. `application_data` VARCHAR(2000),
    13. `gmt_create` DATETIME,
    14. `gmt_modified` DATETIME,
    15. PRIMARY KEY (`xid`),
    16. KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
    17. KEY `idx_transaction_id` (`transaction_id`)
    18. ) ENGINE = INNODB DEFAULT CHARSET = utf8;
    19. -- 事务分支表 --
    20. CREATE TABLE IF NOT EXISTS `branch_table`
    21. (
    22. `branch_id` BIGINT NOT NULL,
    23. `xid` VARCHAR(128) NOT NULL,
    24. `transaction_id` BIGINT,
    25. `resource_group_id` VARCHAR(32),
    26. `resource_id` VARCHAR(256),
    27. `branch_type` VARCHAR(8),
    28. `status` TINYINT,
    29. `client_id` VARCHAR(64),
    30. `application_data` VARCHAR(2000),
    31. `gmt_create` DATETIME(6),
    32. `gmt_modified` DATETIME(6),
    33. PRIMARY KEY (`branch_id`),
    34. KEY `idx_xid` (`xid`)
    35. ) ENGINE = INNODB DEFAULT CHARSET = utf8;
    36. -- 锁定表--
    37. CREATE TABLE IF NOT EXISTS `lock_table`
    38. (
    39. `row_key` VARCHAR(128) NOT NULL,
    40. `xid` VARCHAR(128),
    41. `transaction_id` BIGINT,
    42. `branch_id` BIGINT NOT NULL,
    43. `resource_id` VARCHAR(256),
    44. `table_name` VARCHAR(32),
    45. `pk` VARCHAR(36),
    46. `gmt_create` DATETIME,
    47. `gmt_modified` DATETIME,
    48. PRIMARY KEY (`row_key`),
    49. KEY `idx_branch_id` (`branch_id`)
    50. ) ENGINE = INNODB DEFAULT CHARSET = utf8;
    51. -- seata新版本加的锁表--
    52. CREATE TABLE IF NOT EXISTS `distributed_lock`
    53. (
    54. `lock_key` CHAR(20) NOT NULL,
    55. `lock_value` VARCHAR(20) NOT NULL,
    56. `expire` BIGINT,
    57. PRIMARY KEY (`lock_key`)
    58. ) ENGINE = INNODB
    59. DEFAULT CHARSET = utf8mb4;
    60. INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
    61. INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
    62. INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
    63. INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);

    前面的UNDO_LOG表是加在业务数据库中的。每个业务数据库都要有UNDO_LOG

    seata1.4.2之后,需要回滚的表日期类型不能使用datetime,可以使用timestamp

    3.修改seata服务的配置文件file.conf(可以不做这一步,可以将这些配置放在远程配置中心)

    4.修改配置文件registry.conf

    这里的namespace和group项一定要和nacos的一致

    5. 在registry.conf配置中还可以修改Seata-server的配置中心,默认是以file文件进行存储的,也就是我们上面设置的file.conf,现在我顺便把配置放到nacos上

    1. #config部分
    2. nacos {
    3. application = "seata-server"
    4. serverAddr = "127.0.0.1:8848"
    5. group = "SEATA_GROUP"
    6. namespace = "a971c0d9-771c-4c21-b4f3-3c04c499d831"
    7. cluster = "default"
    8. username = "nacos"
    9. password = "nacos"
    10. }

     6.下载nacos-config脚本和config.txt点击进入下载页,这两个就相当于seata的全部配置,比file.conf更全面。

    • nacos-config.shnacos-config.py选择一个:在seata目录下新建 script 目录,将 nacos-config.sh 放入script 目录下

    • config.txt:该文件存放在将seata目录下,与conf、lib目录同级,seata的非常全的配置内容,可通过nacos-config.sh脚本推送到nacos配置中心

    • 修改config.txt的内容

    •  打开git bashlinux类命令行,执行sh脚本(注意脚本是否有执行的权限),导入config.txt的配置到nacos配置中心里 
    1. # -h 主机,你可以使用localhost,-p 端口号 ,-t 命名空间ID,-u 用户名,-w 密码
    2. sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA-GROUP -t a971c0d9-771c-4c21-b4f3-3c04c499d831 -u nacos -w nacos

    seata配置文件非常之多,建议新建一个命名空间单独存放 

     

     7.启动seata-server.bat,并查看nacos服务,若seata服务注册成功,表示注册中心和配置中心成功

    三、微服务集成Seata-Server

    我们的代码逻辑为:用户购买某件商品,首先查看库存是否充足,如果充足的话,再远程调用订单服务,在数据库中新建一个订单。

    扣减库存的service代码如下:

     创建订单的service代码如下:

    Seata执行流程(AT模式)

    事务执行成功的情况:

     事务执行失败的情况:

     要点说明:

    1.每个RM使用DataSourceProxy连接数据库,其目的是使用ConnectionProxy,使用数据源和数据连接代理的目的就是在第一阶段将UNDO_LOG和业务数据放在一个本地事务提交,这样就保存了只要有业务操作就一定有UNDO_LOG。

    2.在第一阶段UNDO_LOG中存放了数据修改前和修改后的值,为事务回滚作好准备,所以第一阶段完成就已经将分支事务提交,也就释放了锁资源

    3.TM开启全局事务开始,将XID全局事务id放在事务上下文中,通过feign调用也将XID传入下游分支事务,每个分支事务将自己的Branch ID分支事务ID与XID关联

    4.第二阶段全局事务提交,TC会通知各个分支参与者提交分支事务,在第一阶段就已经提交了分支事务,这里各参与者只需要删除UNDO_LOG即可,并且可以异步执行,第二阶段很快可以完成。

    5.第二阶段全局事务回滚,TC会通知各个分支参与者回滚分支事务,通过XID和Branch lD找到相应的回滚日志,通过回滚日志生成反向的SQL并执行,以完成分支事务回滚到之前的状态,如果回滚失败则会重试回滚操作。

    Seata工程构建

    1.根据前面的步骤将我们的Seata-Server项目启动起来,也就是启动TC

    2.在需要使用到分布式事务的微服务中引入Seata依赖,也就是注册TM和RM

    1. <dependency>
    2. <groupId>com.alibaba.cloudgroupId>
    3. <artifactId>spring-cloud-starter-alibaba-seataartifactId>
    4. <exclusions>
    5. <exclusion>
    6. <groupId>io.seatagroupId>
    7. <artifactId>seata-spring-boot-starterartifactId>
    8. exclusion>
    9. exclusions>
    10. dependency>
    11. <dependency>
    12. <groupId>io.seatagroupId>
    13. <artifactId>seata-spring-boot-starterartifactId>
    14. <version>1.4.2version>
    15. dependency>

    3.配置微服务配置文件,让微服务能通过注册中心找到seata-server

    1. seata:
    2. enabled: true
    3. application-id: ${spring.application.name}
    4. #事务群组(可以每个应用独立取名,也可以使用相同的名字),根据这个获取tc服务的cluster名称。事务组=全局事务
    5. #"default_tx_group"是默认值,如果改了这里,server配置下的 `server.vgroupMapping.xxxx`也要跟着一起改,否则会注册不到 “seata-server”导致报错。
    6. tx-service-group: default_tx_group
    7. config:
    8. type: nacos
    9. #需要和server在同一个注册中心下
    10. nacos:
    11. namespace: a971c0d9-771c-4c21-b4f3-3c04c499d831
    12. server-addr: 127.0.0.1:8848
    13. #需要和server端的registryconfig一致
    14. group: SEATA-GROUP
    15. username: nacos
    16. password: nacos
    17. registry:
    18. type: nacos
    19. nacos:
    20. #需要和server端保持一致,即servernacos中的名称,默认为seata-server
    21. application: seata-server
    22. server-addr: 127.0.0.1:8848
    23. group: DEFAULT_GROUP
    24. namespace: public
    25. username: nacos
    26. password: nacos

    4.TM方法上加上@GlobalTransactional注解。

    测试

    1.订单服务出错回滚全局事务

    如果我们将订单服务代码改为:

     那么只会扣减库存,而不会生成订单

     这两个不同的服务都存在本地事务,在本地事务中如果出现异常,那么就会回滚本地事务,且controller层会捕捉异常,不让异常抛给用户,所以订单服务即使发生异常回滚了,扣减库存的服务是感知不到的。

    现在我们将更改扣减库存的服务,使得订单创建失败,也不会扣减库存

     可以使用状态码来判断远程调用是否成功或失败,判断是否需要提交或回滚

    测试后,库存和订单数据都一致。因为全局事务回滚了。

     这个例子不是很明显,因为即使是本地事务回滚,也会将扣减库存的服务回滚,发挥不了分布式事务的优势。请看下面的例子。

    2.扣减库存服务出错回滚订单创建

    将创建订单的服务变为正常的:

     扣减库存的服务代码最后会抛出一个异常:

     如果没有分布式事务,那么就会发生库存没扣,但是订单却创建了的情况。但我们现在有全局事务,如果发生错误,TC会将所有的RM回滚,包括远端的服务。

    如下这是订单服务的结果:

     虽然它本来在本地已经创建了一个订单,且提交了,但是TC在检测到一个RM发生错误时就会通知它,让它根据UNDO_LOG进行回滚操作。

    总结:

    • 在需要发起全局事务的service方法上添加注解 @GlobalTransactional
    • 使用Fiegn、restTemplate等方式发送请求,提供方只添加@Transactional保证本地事务

    注意问题:

    1.seata通过线程变量XID(RootContext.getXID() )判断TC与RM是否在同一事务下,现支持使用restTemplate与Feign方式发送请求自动携带xid到被调用方,使用其他方式可将xid放入请求头中,key为"TX_XID",RM会在请求头中自动获取。若采用其他方式需自行保证xid的传递。

    2.被调用方产生异常却没有回滚:当被调用方RM产生异常时,为了调用方TM可以正确接收到异常状态码,使Feign能抛出异常发起全局事务回滚,RM最好不要添加异常处理去拦截异常。

    Seata的TCC模式

    TCC模式主要是要注意幂等性、悬挂、空回滚这三个问题。使用TCC就需要改变我们的原先的代码逻辑,我们需要先构思出整体的流程:

    扣减库存的服务:

    1. try
    2. 悬挂校验
    3. 校验库存
    4. 减库存
    5. confirm:
    6. cancel:
    7. 幂等校验
    8. 空回滚处理
    9. 加上库存

    创建订单服务:

    1. try:无
    2. confirm:
    3. 幂等校验
    4. 创建订单
    5. cancel:无
    1. @LocalTCC
    2. public interface ProductService {
    3. @TwoPhaseBusinessAction(name = "buy", commitMethod = "confirm", rollbackMethod = "cancel")
    4. boolean buy(@BusinessActionContextParameter(paramName = "id") Integer id);
    5. boolean confirm(BusinessActionContext ctx);
    6. boolean cancel(BusinessActionContext ctx);
    7. }

    具体的流程比较复杂,可以新建几个表来解决幂等性、空回滚、悬挂问题

  • 相关阅读:
    windows环境下安装RabbitMQ
    Linux 网络配置
    [Python] 集合操作及方法总结
    加强堆结构说明
    未来装备探索:数字孪生装备
    新手入门深度学习 | 3-6:优化器optimizers
    LeetCode75——Day19
    opensuse server连接wifi
    python面向对象的编程---类
    STM32的中断
  • 原文地址:https://blog.csdn.net/weixin_45902285/article/details/126561282