一、背景在上一节中,我们了解了Seata的集群部署。本篇我们使用SpringBoot集成Seata实现分布式事务功能。这里使用了Seata的AT。模型。2、实现功能我们有两个服务,账户服务account-service和订单服务order-service,在订单服务中调用账户服务。订单服务中调用账户服务是通过RestTemplate实现的。测试场景:1.账户服务正常,订单服务正常。结果:账户服务正常扣款,并生成订单。2、账户服务正常,订单服务正常。整个分布式事务出现异常。结果:帐户服务没有扣除,也没有生成订单。三、各服务用到的技术1、账户服务SpringBoot、Seata、Mybatis、nacos、druid2、订单服务SpringBoot、Seata、Mybatis、nacos、Hikari其中,SpringBoot通过seata-spring-boot-starter集成了Seata。是的,不要用seata-all来实现。四、服务实现1、账户服务实现账户服务,提供简单的账户余额扣除功能,比较简单。注意事项:1.开启自动数据源代理。2.引入druid不需要自动配置数据源。三、注意事务分组1、导入jar包这里只介绍几个核心包,其余的包下面就不一一列举了,比如mybatis等。注意与seata的集成使用的是seata-spring-boot-starterio.seataseata-spring-boot-starter1.4.2com.alibabadruid-spring-boot-starter1.2.4com.alibaba.nacosnacos-client1.3.22.项目配置3.建表语句createdatabaseseata_account;useseata_account;createtableaccount(idintunsignedauto_increment主键注释'主键',namevarchar(20)comment'用户名',balancebigintcomment'账户余额,单位积分')engine=InnoDBcomment'账户表';insertintoaccount(id,name,balance)values(1,'张三',100000);如果不存在则创建表`undo_log`(`branch_id`BIGINTNOTNULLCOMMENT'分支事务id',`xid`VARCHAR(128)NOTNULLCOMMENT'全局事务id',`context`VARCHAR(128)NOTNULLCOMMENT'undo_log上下文,比如序列化',`rollback_info`LONGBLOBNOTNULLCOMMENT'回滚信息',`log_status`INT(11)NOTNULLCOMMENT'0:正常状态,1:防御状态',`log_created`DATETIME(6)NOTNULLCOMMENT'创建日期时间',`log_modified`DATETIME(6)NOTNULLCOMMENT'modifydatetime',UNIQUEKEY`ux_undo_log`(`xid`,`branch_id`))ENGINE=InnoDBCOMMENT='ATtransactionmodeundotable';每个业务库必须存在一个undo_log表2.订单服务实现提供了生成订单和扣除账户余额的接口注意事项:1.订单服务关闭默认数据源代理,自行配置数据源代理。2、使用Hikari数据源来实现,因为使用的是AT模式,所以需要使用DataSourceProxy来代理数据源。3、订单服务使用RestTemplate调用账户服务,所以需要手动配置RestTemplate的拦截器来实现xid的传递。4.seata1.4.2有一个bug。如果业务表中的数据类型是datetime类型,undolog可能序列化不成功,可以使用timestamp等方式处理。5、业务库中需要存在undo_log表。1、引入jar包这里不引入druid。注意与seata的集成使用seata-spring-boot-starterio.seataseata-spring-boot-starter1.4.2com.alibaba.nacosnacos-client1.3.22.项目配置3.配置数据源代理包com.huan.seata.config;importcom.zaxxer.hikari.HikariDataSource;importio.seata.rm.datasource.DataSourceProxy;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.autoconfigure.jdbc.DataSourceProperties;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importjavax.sql.DataSource;/***@authorhuan.fu2021/9/24-10:34am*/@ConfigurationpublicclassDataSourceConfig{@AutowiredprivateDataSourceProperties数据源属性;@BeanpublicDataSourcedataSourceProxy(){HikariDataSourcehikariDataSource=newHikariDataSource();hikariDataSource.setJdbcUrl(dataSourceProperties.getUrl());hikariDataSource.setUsername(dataSourceProperties.getUsernameP());;hikariDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());返回新的DataSourceProxy(hikariDataSource);}}AT模式下,数据源代理必须是DataSourceProxy4.配置RestTemplate传递xid5,@GlobalTransactional分布式事务@Service@RequiredArgsConstructor@Slf4jpublicclassBusinessServiceImplimplementsBusinessService{privatefinalOrderServiceorderService;私有最终RestTemplaterestTemplate;@Override@GlobalTransactional(rollbackForvoAcgerOrIdaccountid.class)pcount,Longamount,booleanhasException){System.out.println("createAccountOrder:"+RootContext.getXID());//1.远程借记账户余额remoteDebit(accountId,amount);//2.下订单orderService。创建订单(accountId,金额);if(hasException){thrownewRuntimeException("发生异常,分布式事物需要滚动");}}privatevoidremoteDebit(IntegeraccountId,Longamount){Stringurl="http://localhost:50001/account/debit?id="+accountId+"&amount="+amount;字符串结果=restTemplate.getForObject(url,String.class);log.info("远程库存扣减结果:[{}]",result);}}3.事务组需要和配置中心对应这里用订单服务来演示如何对应配置中心的各个服务事务分组可能不一样,但是需要和配置中心对应。例如:order-service中的配置组为:seata.tx-service-group=tx_order_service_group配置中心必须有service.vgroupMapping.tx_order_service_group=default配置项,默认为cluster,在server配置文件中指定5.演示1、没有异常访问:http://localhost:50002/createOrder?accountId=1&amount=10&hasException=false正常创建订单并扣除余额。2、访问异常:http://localhost:50002/createOrder?accountId=1&amount=10&hasException=true不生成订单,不扣余额。六、可能遇到的问题1、使用Nacos作为Seata配置中心时,项目启动报错,找不到服务。如何排除故障,如何处理?A:异常:io.seata.common.exception.FrameworkException:cannotregisterRM,err:cannotconnecttoservices-server.查看nacos配置列表,查看seata配置是否导入成功查看nacos服务列表,serverAddr是否注册成功查看客户端registry.conf中namespace,填写registry.nacos.namespace和config.nacos.namespace带有nacos的命名空间ID,默认为"",server端对应client端,命名空间是public的。nacos的保留控件。如果需要创建自己的命名空间,最好不要和public重名。给nacos上的服务列表取一个在实际业务场景中具有特定语义的名称。serverAddr地址对应的IP地址应该是seata启动时指定的IP地址,如:shseata-server.sh-p8091-h122.51.204.197-m文件查看seata/conf/nacos-config.txt事务组service.vgroupMapping.trade_group=default配置与项目组配置名一致telnetip查看端口是否开放,防火墙状态2、使用AT模式有哪些注意事项?必须使用代理数据源,代理数据源有3种方式:依赖seata-spring-boot-starter时,代理数据源自动代理,无需额外处理。依赖seata-all时,使用@EnableAutoDataSourceProxy(since1.1.0)注解,注解参数可以选择jdk代理或者cglib代理。依赖seata-all时,也可以手动使用DatasourceProxy封装DataSource。配置GlobalTransactionScanner,使用seata-all时需要手动配置,使用seata-spring-boot-starter时不需要额外处理。业务表必须包含单列主键。如果有复合主键,请参考问题13。每个业务库都必须包含一个undo_log表。如果与分库分表组件配合使用,则分库不分表。跨微服务链路的事务需要支持相应的RPC框架,seata-all目前支持的有:ApacheDubbo、AlibabaDubbo、sofa-RPC、Motan、gRpc、httpClient,SpringCloud支持请参考spring-cloud-alibaba-seata。其他自研框架、异步模型、消息消费事务模型请自行组合API支持。目前AT模式支持的数据库有:MySQL、Oracle、PostgreSQL、TiDB。在使用注解开启分布式事务时,如果默认的服务提供者端加入了事务的消费者端,则提供者不需要标注注解。但是provider也需要相应的依赖和配置,注解只能省略。在使用注解启动分布式事务时,如果需要回滚事务,则必须向事务的发起者抛出异常,由事务发起者的@GlobalTransactional注解感知。Provide直接抛出异常或者定义一个错误码,让消费者在抛出异常前判断。3、使用Spring@Transactional注解的AT模式需要注意什么?@Transactional可以与DataSourceTransactionManager和JTATransactionManager结合使用,分别表示本地事务和XA分布式事务。它通常用于与本地交易相结合。与本地事务结合时,@Transactional和@GlobalTransaction一起使用。@Transactional只能位于与@GlobalTransaction标记相同的方法层或位于@GlobalTransaction标记方法的内层。这里分布式事务的概念大于本地事务。如果在外层标记@Transactional,则分布式事务将被提交为空。当提交@Transactional对应的connection时,会报全局事务正在提交或者全局事务的xid不存在。4、脏数据无法回滚,因为数据库会自动更新时间戳。由于业务提交,seata记录当前镜像后,数据库再次更新时间戳,导致镜像验证失败。解决方案一:关闭数据库的时间戳自动更新。数据的时间戳更新,比如修改和创建时间,是在代码层面维护的。比如MybatisPlus可以做自动填充。方案二:update语句不把没有更新的字段放到update语句中。5.Seata使用注册中心注册的地址有什么限制?Seata注册中心无法注册0.0.0.0或127.0.0.1地址。当自动注册为上述地址时,可通过启动参数-h或容器环境变量SEATA_IP指定。当注册地址与业务服务不在同一网络时,可以指定NAT_IP或公网IP作为注册地址,但需要保证注册中心的健康检查探针畅通。以上问题来自seata官网:http://seata.io/zh-cn/docs/overview/faq.html7.代码地址代码地址:https://gitee.com/huan1993/spring-cloud-parent/tree/master/seata/seata-springboot-mybatis