当前位置:   article > 正文

springboot研究十一:springcloud+eureka整合seata-AT模式_springcloud + springboot + seata + eureka

springcloud + springboot + seata + eureka

目录

环境搭建

测试

总结 


今天继续给大家分享一下阿里的分布式事务中间件seata的使用,跟上篇文章《springboot研究十:springboot多数据源整合分布式事务中间件seata》不一样的是,上篇文章是单服务绑定多数据源的分布式情况,而本文基于微服务下每个服务绑定一个数据源的场景,服务之间依靠eureka客户端feign进行通信。

注:seata有三种模式,AT模式、TCC模式和saga模式,上篇文章和本篇文章介绍的都是AT模式。感兴趣的同学可以参考官网进行了解这三种模式:

http://seata.io/en-us/docs/dev/mode/at-mode.html

环境搭建

还是先说一下本文使用的实验环境:
springboot:2.1.6.RELEASE
orm框架:mybatis
数据库:mysql
数据库连接池:HikariCP
seata server:1.3.0
springcloud:Greenwich.SR2
整个项目的架构如下: 

可以看到,项目中有order-server,account-server,storage-server这3个服务,每个服务绑定自己的数据库。这3个服务都注册到eureka上面,同时也都注册TM到seata-server。seata-server也注册到了eureka上。
首先我们启动eureka,这里监听8889端口,yml文件配置如下:

  1. server:
  2. port: 8889
  3. spring:
  4. application:
  5. name: eureka-server
  6. #Eureka实例名,集群中根据这里相互识别
  7. eureka:
  8. instance:
  9. hostname: localhost
  10. #客户端
  11. client:
  12. #是否开启注册服务,因为这里如果为true表示自己注册自己,而自己就是一个服务注册方,没必要自己注册自己
  13. register-with-eureka: false
  14. #是否拉取服务列表,这里我只提供服务给别的服务。
  15. fetch-registry: false
  16. #注册中心地址
  17. service-url:
  18. defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  19. #服务端:
  20. server:
  21. enable-self-preservation: false

 接着我们启动seata server,由于seata server也要注册到eureka上面,我们需要修改seata server,把registry.conf文件修改为如下:

  1. registry {
  2. # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  3. type = "eureka"
  4. #省略无关的代码
  5. eureka {
  6. #我本地配置的eureka地址
  7. serviceUrl = "http://192.168.59.1:8889/eureka"
  8. application = "seata-server"
  9. weight = "1"
  10. }
  11. #省略其他代码

执行如下命令启动seata server:

./seata-server.sh -p 8091 -h 127.0.0.1 -m file

 配置3个微服务,这里我们以order-server为例,先看yml文件,核心配置就是eureka、feign、mybatis和数据源,代码如下:

  1. #配置eureka
  2. eureka:
  3. instance:
  4. hostname: localhost
  5. prefer-ip-address: true
  6. client:
  7. serviceUrl:
  8. defaultZone: http://${eureka.instance.hostname}:8889/eureka/
  9. feign:
  10. hystrix:
  11. enabled: false
  12. client:
  13. config:
  14. default:
  15. connectTimeout: 5000
  16. readTimeout: 10000
  17. logging:
  18. level:
  19. io:
  20. seata: info
  21. #配置mybatis
  22. mybatis:
  23. mapperLocations: classpath:mapper/*.xml
  24. typeAliasesPackage: io.seata.sample.entity
  25. server:
  26. port: 8180
  27. spring:
  28. application:
  29. name: order-server
  30. cloud:
  31. alibaba:
  32. seata:
  33. tx-service-group: my_test_tx_group
  34. #配置数据源
  35. datasource:
  36. driver-class-name: com.mysql.jdbc.Driver
  37. password: 123456
  38. jdbcUrl: jdbc:mysql://192.168.59.1:3306/seata_order?useAffectedRows=true&serverTimezone=UTC&characterEncoding=utf-8
  39. username: root
  40. registry.conf文件,最核心的就是type使用eureka,然后配置eureka地址和应用名称,部分代码如下:
  41. registry {
  42. # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  43. type = "eureka"
  44. #省略其他代码
  45. eureka {
  46. serviceUrl = "http://localhost:8889/eureka"
  47. application = "order-server"
  48. weight = "1"
  49. }
  50. #省略其他代码
  51. file.conf文件只要改一下service就可以了,里面配置seata server地址,代码如下:
  52. service {
  53. #transaction service group mapping
  54. vgroupMapping.my_test_tx_group = "seata-server"
  55. #only support when registry.type=file, please don't set multiple addresses
  56. default.grouplist = "192.168.59.132:8091"
  57. #degrade, current not support
  58. enableDegrade = false
  59. #disable seata
  60. disableGlobalTransaction = false
  61. }

 用这种方式配置account-server和storage-server,之后把3个服务都起来,启动成功后,访问eureka页面,地址如下:

http://localhost:8889/

 这时我们能看到加上seata server一共4个服务都已经启动成功了,见下图:

 下面我贴一下整个项目的sql语句:

  1. #########################seata_order
  2. use database seata_order;
  3. CREATE TABLE `orders` (
  4. `id` mediumint(11) NOT NULL AUTO_INCREMENT,
  5. `user_id` int(11) DEFAULT NULL,
  6. `product_id` int(11) DEFAULT NULL,
  7. `COUNT` int(11) DEFAULT NULL COMMENT '数量',
  8. `pay_amount` decimal(10,2) DEFAULT NULL,
  9. `status` varchar(100) DEFAULT NULL,
  10. `add_time` datetime DEFAULT CURRENT_TIMESTAMP,
  11. `last_update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  12. PRIMARY KEY (`id`)
  13. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
  14. #########################seata_pay库
  15. use database seata_pay;
  16. DROP TABLE account;
  17. CREATE TABLE `account` (
  18. `id` BIGINT(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  19. `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
  20. `total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
  21. `used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用余额',
  22. `balance` DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度',
  23. `last_update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  24. PRIMARY KEY (`id`)
  25. ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
  26. INSERT INTO `seata_pay`.`account` (`id`, `user_id`, `total`, `used`, `balance`) VALUES ('1', '1', '1000', '0', '100');
  27. #########################seata_storage库
  28. use database seata_storage;
  29. CREATE TABLE `storage` (
  30. `id` BIGINT(11) NOT NULL AUTO_INCREMENT,
  31. `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
  32. `total` INT(11) DEFAULT NULL COMMENT '总库存',
  33. `used` INT(11) DEFAULT NULL COMMENT '已用库存',
  34. `residue` INT(11) DEFAULT NULL COMMENT '剩余库存',
  35. PRIMARY KEY (`id`)
  36. ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
  37. INSERT INTO `seata_storage`.`storage` (`id`, `product_id`, `total`, `used`, `residue`) VALUES ('1', '1', '100', '0', '100');
  38. #########################下面的undo_log表前面三个库都需要创建
  39. CREATE TABLE `undo_log` (
  40. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  41. `branch_id` bigint(20) NOT NULL,
  42. `xid` varchar(100) NOT NULL,
  43. `context` varchar(128) NOT NULL,
  44. `rollback_info` longblob NOT NULL,
  45. `log_status` int(11) NOT NULL,
  46. `log_created` datetime NOT NULL,
  47. `log_modified` datetime NOT NULL,
  48. PRIMARY KEY (`id`),
  49. UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
  50. ) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8

至此,整个环境就搭建完成了,接着我们进行测试。我们先看下account和storage两张表的数据,其他表都是空,如下图:
account表:

storage表:

 

测试

现在我们开始进行测试,有2个场景,正常commit和场景和异常rollback场景。
我们向order-server发一个post请求,content如下:

  1. {
  2. "userId":1,
  3. "productId":1,
  4. "count":1,
  5. "money":1,
  6. "payAmount":50
  7. }

这时order-server执行成功,日志如下:

  1. 2020-08-15 16:31:53.704 INFO 57868 --- [nio-8181-exec-1] i.s.sample.service.AccountServiceImpl : ------->扣减账户开始account中
  2. 2020-08-15 16:31:54.141 INFO 57868 --- [nio-8181-exec-1] i.s.sample.service.AccountServiceImpl : ------->扣减账户结束account中
  3. 2020-08-15 16:31:54.141 INFO 57868 --- [nio-8181-exec-1] i.s.sample.service.AccountServiceImpl : 修改订单状态开始
  4. 2020-08-15 16:31:54.309 INFO 57868 --- [nio-8181-exec-1] c.netflix.config.ChainedDynamicProperty : Flipping property: order-server.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
  5. 2020-08-15 16:31:54.344 INFO 57868 --- [nio-8181-exec-1] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-order-server
  6. 2020-08-15 16:31:54.345 INFO 57868 --- [nio-8181-exec-1] c.netflix.loadbalancer.BaseLoadBalancer : Client: order-server instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=order-server,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
  7. 2020-08-15 16:31:54.352 INFO 57868 --- [nio-8181-exec-1] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
  8. 2020-08-15 16:31:54.385 INFO 57868 --- [nio-8181-exec-1] c.netflix.config.ChainedDynamicProperty : Flipping property: order-server.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
  9. 2020-08-15 16:31:54.386 INFO 57868 --- [nio-8181-exec-1] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client order-server initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=order-server,current list of Servers=[10.192.86.60:8180],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
  10. },Server stats: [[Server:10.192.86.60:8180; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
  11. ]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@5fa120e1
  12. 2020-08-15 16:31:54.562 INFO 57868 --- [nio-8181-exec-1] i.s.sample.service.AccountServiceImpl : 修改订单状态结束:订单状态修改成功
  13. 2020-08-15 16:31:54.578 WARN 57868 --- [nio-8181-exec-1] c.a.c.seata.web.SeataHandlerInterceptor : xid in change during RPC from 192.168.59.132:8091:37575557075451904 to null
  14. 2020-08-15 16:31:55.166 INFO 57868 --- [ch_RMROLE_1_1_8] i.s.c.r.p.c.RmBranchCommitProcessor : rm client handle branch commit process:xid=192.168.59.132:8091:37575557075451904,branchId=37575566823014400,branchType=AT,resourceId=jdbc:mysql://192.168.59.1:3306/seata_pay,applicationData=null
  15. 2020-08-15 16:31:55.169 INFO 57868 --- [ch_RMROLE_1_1_8] io.seata.rm.AbstractRMHandler : Branch committing: 192.168.59.132:8091:37575557075451904 37575566823014400 jdbc:mysql://192.168.59.1:3306/seata_pay null
  16. 2020-08-15 16:31:55.170 INFO 57868 --- [ch_RMROLE_1_1_8] io.seata.rm.AbstractRMHandler : Branch commit result: PhaseTwo_Committed
  17. 2020-08-15 16:31:55.356 INFO 57868 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: order-server.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647

这时我们再看下数据库的数据,如下图:

order表:

account表:

storage表:

 容易迷惑我们的是,order表的pay_amount值是45.5,而不是50,这是因为account-server执行完成后,又调用了order-server,执行了更新操作,代码如下:

  1. public void decrease(Long userId, BigDecimal payAmount) {
  2. LOGGER.info("------->扣减账户开始account中");
  3. //模拟超时异常,全局事务回滚
  4. //try {
  5. // Thread.sleep(30*1000);
  6. //} catch (InterruptedException e) {
  7. // e.printStackTrace();
  8. //}
  9. accountDao.decrease(userId,payAmount);
  10. LOGGER.info("------->扣减账户结束account中");
  11. //修改订单状态,此调用会导致调用成环
  12. LOGGER.info("修改订单状态开始");
  13. String mes = orderApi.update(userId, payAmount.multiply(new BigDecimal("0.09")),0);
  14. LOGGER.info("修改订单状态结束:{}",mes);
  15. }

 第二个场景的测试,我们把上面的代码模拟超时异常这段放开,用debug模式进行,继续发送前面一样的请求,可以看到seata_order库中的undo_log,如下图:

等超时后,事务进行了回滚。

总结 

可以看到,本篇文章介绍的场景跟上篇差不多,本质都是基于undo_log的交易补偿,这也是AT模式的特点。但是本文的环境比上节复杂很多,这并不是seata本身造成的,而是微服务的拆分带来的系统架构的复杂性。

 

源码地址:

  1. https://github.com/jinjunzhu/springcloud-eureka-feign-mybatis-seata.git
  2. https://github.com/jinjunzhu/eurekaserver.git

 

欢迎关注个人公众号,共同学习,共同成长

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/165675
推荐阅读
相关标签
  

闽ICP备14008679号