项目
博客
文档
归档
资源链接
关于我
项目
博客
文档
归档
资源链接
关于我
29| SpringCloudAlibaba微服务整合分布式事务Seata实战
2024-08-13
·
·
原创
·
·
本文共 892个字,预计阅读需要 3分钟。
### 新版本-分布式事务框架Seata 服务端部署安装最佳实践 * 基于AT模式 * 创建undo_log表, 每个库都需要 * https://seata.apache.org/zh-cn/docs/dev/mode/at-mode/ ```sql -- 注意此处0.7.0+ 增加字段 context CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; ``` * 下载部署Seata的TC服务端 * https://seata.apache.org/zh-cn/unversioned/download/seata-server * 我们下载1.3就行,不要下载最新的(除非对新的很有把握不出问题) * Linux/Mac/Windows服务器安装 * 解压 * 修改jvm内存(默认是2g,防止内存不够) * ./seata-server.sh 启动,默认是8091端口(记得防火墙开放端口,也可以nohup守护进程启动) * TC需要存储全局事务和分支事务的记录,支持三种存储模式 * file模式 (默认):性能高, 适合单机模式,在内存中读写,并持久化到本地文件中 * 在 bin/sessionStore/root.data文件 * db模式 :性能较差,适合tc集群模式 * redis模式:性能教高,适合tc集群模式 * 问题: * seata 在 JDK11下运行报错 * 解决: 下载下来的seata 默认没有存放日志文件的目录, 手动创建seata/logs/seata_gc.log 目录和文件 ### SpringCloudAlibaba微服务整合Seata分布式事务框架 #### 用户注册同时发放注册优惠券 * common项目添加依赖 * 出现的问题:no available service 'null' found, please make sure registry config correct * 安装的服务端版本必须要和你客户端的版本保持一样 ```xml
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
io.seata
seata-spring-boot-starter
io.seata
seata-spring-boot-starter
1.3.0
``` * 各个微服务配置文件修改:配置规则 - tx-service-group对应的值: `项目名-group`, 与**vgroup-mapping下**对应一致 - grouplist对应的值: 与最后xx-group对应一致 ```yaml #nla-user-service服务配置seata seata: tx-service-group: ${spring.application.name}-group service: grouplist: nla: 127.0.0.1:8091 vgroup-mapping: nla-user-service-group: nla #nla-coupon-service服务配置seata seata: tx-service-group: ${spring.application.name}-group service: grouplist: nla: 127.0.0.1:8091 vgroup-mapping: nla-coupon-service-group: nla ``` * 注释掉全局异常 ```java //@ControllerAdvice //public class CustomExceptionHandler .. ``` * 开发测试接口 User服务中通过FeignClient来调用新用户注册发放优惠券接口 ```java @FeignClient(name = "nla-coupon-service") public interface CouponFeignService { @PostMapping("/cop/coupon/v1/new_user_coupon") JsonData addNewUserCoupon(@RequestBody NewUserCouponRequest newUserCouponRequest); } ``` 补充用户注册:初始化福利信息 ```java /** * 用户注册,初始化福利信息 * * @param userEntity 用户对象信息 */ private void userRegisterInitTask(UserEntity userEntity) { log.info("初始化福利信息 TODO... {}", userEntity); NewUserCouponRequest request = new NewUserCouponRequest(); request.setName(userEntity.getName()); request.setUserId(userEntity.getId()); JsonData jsonData = couponFeignService.addNewUserCoupon(request); // if(jsonData.getCode()!=0){ // throw new RuntimeException("发放优惠券异常"); // } log.info("发放新用户注册优惠券:{},结果:{}",request.toString(),jsonData.toString()); } ``` 用户组成添加异常:模拟异常 ```java @Override @GlobalTransactional public JsonData register(UserRegisterRequest registerRequest) { // ... //新用户注册成功,初始化信息,发放福利等 userRegisterInitTask(userEntity); //模拟异常 int b = 1/0; return JsonData.buildSuccess(); } ``` * 测试分布式事务 * TM入口service的方法增加 @GlobalTransactional 注解,同时在启动类开启事务注解@EnableTransactionManagement ### Seata分布式事务undo_log表-分析AT模式执行机制回顾 * AT * AT模式可以应对大多数的业务场景,并且基本可以做到无业务入侵、开发者无感知 * 用户只需关心自己的 业务SQL. AT 模式分为两个阶段,可以认为是2PC * 一阶段:执行用户SQL ```java Seata 会拦截“业务 SQL”,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据 在业务数据更新之后,再将其保存成“after image”,最后生成行锁 以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性 ``` - 二阶段:`Seata`框架自动生成提交或者回滚 二阶段提交: 因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将阶段一保存的快照数据和行锁删掉,完成数据清理即可。 二阶段回滚: 还原业务数据, 回滚方式便是用“before image”还原业务数据; 但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image” 如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理 * undo_log表的rollback_info字段 ```json {"@class":"io.seata.rm.datasource.undo.BranchUndoLog","xid":"192.168.0.115:8091:107926818928988160","branchId":107926819939815424,"sqlUndoLogs":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.undo.SQLUndoLog","sqlType":"INSERT","tableName":"user","beforeImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords$EmptyTableRecords","tableName":"user","rows":["java.util.ArrayList",[]]},"afterImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords","tableName":"user","rows":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Row","fields":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"id","keyType":"PRIMARY_KEY","type":-5,"value":["java.math.BigInteger",26]},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"name","keyType":"NULL","type":12,"value":null},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"pwd","keyType":"NULL","type":12,"value":"34234234"},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"head_img","keyType":"NULL","type":12,"value":null},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"slogan","keyType":"NULL","type":12,"value":null},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"sex","keyType":"NULL","type":-6,"value":1},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"points","keyType":"NULL","type":4,"value":0},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"create_time","keyType":"NULL","type":93,"value":["java.sql.Timestamp",[1614166959000,0]]},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"mail","keyType":"NULL","type":12,"value":"2343223@qq.com"},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"secret","keyType":"NULL","type":12,"value":"111"}]]}]]}}]]} ``` ### 全局异常下微服务整合Seata分布式事务失效解决方案 * 问题: 微服务场景下,配置了统一全局异常处理,导致seata在AT模式下无法正常回滚问题 * 如果使用Feign 配置了容错类(fallback)或者容错工厂(fallbackFactory),也是一样的问题 * 原因:服务A调用服务B, 服务B发生异常,由于全局异常处理的存在(@ControllerAdvice), seata 无法拦截到B服务的异常,从而导致分布式事务未生效 * 解决思路 ```java 配置了全局异常处理,所以rpc一定会有返回值, 所以在每个全局事务方法最后, 需要判断rpc是否发生异常 发生异常则抛出 RuntimeException或者子类 ``` * 方式一:RPC接口不配置全局异常 * 方式二:利用AOP切面解决 * 方式三:程序代码各自判断RPC响应码是否正常,再抛出异常 ### 高并发下分布式事务下的总结和思考 * 分布式事务解决方案很多,XA的2PC、TCC、MQ事务消息等 * 框架也有Seata, 同时支持多种方式模式 * 重点 ```java 不管选哪一种方案,在项目中应用都要谨慎再思考,除特定的数据强一致性场景外,能不用尽量就不要用 因为无论它们性能如何优越,一旦项目链路加入分布式事务,整体效率会几倍的下降,在高并发情况下弊端尤为明显 ``` * 任何多链路的操作,换个方案或者换个思路,可以避免使用分布式事务(接下去大课就是) * 下单商品库存锁定 * 下单优惠券记录锁定 * 总之 * 分布式事务和分布式锁一样,能不用就不用 * 实在要用,使用优先是 柔性事务,实在无法满足再考虑 刚性事务 * 分布式锁也是,尽量降低锁的粒度