分布式事务解决方案之seata集成nacos测试(AT模式)

1.分布式事务解决方案之seata集成nacos测试(AT模式)

如果没有环境,可以查看,这篇文章seata1.3.0整合nacos搭建

1.简单介绍一下seata的AT模式(数据库隔离级别为: 读未提交)

AT模式:

  • 一阶段:2个微服务的数据库(Resource Manager 资源管理器,下面2个服务的事务简称为tx1 和tx2),会向seata服务器(Transaction Coordinator,事务协调器)注册一个分支事务会生成一个全局锁以Xid为识别,当双方代码执行完,无异常后,开始申请第二阶段的资源提交,并且锁定当前数据库资源

  • 二阶段:此时tx1会优先尝试获取全局锁(拿不到 全局锁 ,不能提交本地事务),当tx1开启本地事务,拿到全局锁,提交本地事务,然后tx2开始获取全局锁,提交本地事务。如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录

    (这里主要查看数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较)

弊端:从第二阶段可以看到,tx1提交事务之后,tx2才能开始提交事务,这里会形成一个阻塞现象,当有多个服务同时注册到一个分支事务,提交顺序会出现:tx1提交完之后,tx2提交,然后tx3…,tx4…并且AT模式会代理你的数据库

2. 微服务服务的搭建

服务统一整合到nacos配置中心和注册中心

2.1统一依赖

     <properties>
        <java.version>1.8</java.version>
        <spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
        <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
        <spring-boot-starter.version>2.2.10.RELEASE</spring-boot-starter.version>
    </properties>
  <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>18.12</version>
        </dependency>
    </dependencies>

 <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${
     spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${
     spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>${
     spring-boot-starter.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

2.2单个服务依赖(这里添加alibaba的seata使用1.3.0的,因为服务端也使用的seata1.3.0)

 <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
         <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.20</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.0</version>
        </dependency>
 </dependencies>

2.3统一的服务的配置文件

nacos上面的seata.yml的内容:

seata:
  client:
    rm:
      async-commit-buffer-limit: 1000
      report-retry-count: 5
      table-meta-check-enable: false
      report-success-enable: false
      saga-branch-register-enable: false
      saga-json-parser: fastjson
      lock:
        retry-interval: 10
        retry-times: 30
        retry-policy-branch-rollback-on-conflict: true
    tm:
      commit-retry-count: 5
      rollback-retry-count: 5
      default-global-transaction-timeout: 60000
      degrade-check: false
      degrade-check-period: 2000
      degrade-check-allow-times: 10
    undo:
      data-validation: true
      log-serialization: jackson
      log-table: undo_log
      only-care-update-columns: true
    log:
      exceptionRate: 100
  service:
    enable-degrade: false
    disable-global-transaction: false
  transport:
    shutdown:
      wait: 3
    thread-factory:
      boss-thread-prefix: NettyBoss
      worker-thread-prefix: NettyServerNIOWorker
      server-executor-thread-prefix: NettyServerBizHandler
      share-boss-worker: false
      client-selector-thread-prefix: NettyClientSelector
      client-selector-thread-size: 1
      client-worker-thread-prefix: NettyClientWorkerThread
      worker-thread-size: default
      boss-thread-size: 1
    type: TCP
    server: NIO
    heartbeat: true
    serialization: seata
    compressor: none
    enable-client-batch-send-request: true
  #seata的配置使用file指的是使用nacos注册中心上面的seata.yml配置  
  registry:
    type: file
    load-balance: RandomLoadBalance
    load-balance-virtual-nodes: 10   

二个服务的bootstrap.properties

spring.profiles.active=dev

spring.cloud.nacos.config.server-addr=ip:8848
#统一使用nacos上面的seata.yml配置文件的内容
spring.cloud.nacos.config.extension-configs[0].data-id=seata.yml
spring.cloud.nacos.config.extension-configs[0].group=dev
spring.cloud.nacos.config.extension-configs[0].refresh=true

2个服务都需要执行的sql

CREATE TABLE `表名`.`undo_log`  (
  `branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
  `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id',
  `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'undo_log context,such as serialization',
  `rollback_info` longblob NOT NULL COMMENT 'rollback info',
  `log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',
  `log_created` datetime(6) NOT NULL COMMENT 'create datetime',
  `log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
  UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Dynamic;

2.4第一个搭建

数据库sql

CREATE TABLE `表名`.`sys_user`  (
  `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  `ip` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  `user_alias` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  `create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  `modify_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = DYNAMIC;
2.4.1 application-dev.yml:
server:
  port: 12000
spring:
  application:
    name:sys-service
  main:
    allow-bean-definition-overriding: true
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://ip:3306/表名?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: 账号
    password: 密码
    #初始化时建立物理连接的个数
    initialSize: 2
    #最小连接池数量
    minIdle: 2
    #最大连接池数量 maxIdle已经不再使用
    maxActive: 2
    #获取连接时最大等待时间,单位毫秒
    maxWait: 60000
    #申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
    testWhileIdle: true
    #既作为检测的间隔时间又作为testWhileIdel执行的依据
    timeBetweenEvictionRunsMillis: 60000
    #销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接
    minEvictableIdleTimeMillis: 30000
    #用来检测连接是否有效的sql 必须是一个查询语句
    #mysql中为 select 'x'
    #oracle中为 select 1 from dual
    validationQuery: select 'x'
    #申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
    testOnBorrow: false
    #归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
    testOnReturn: false
    #是否缓存preparedStatement,mysql5.5+建议开启
    poolPreparedStatements: true
    #当值大于0时poolPreparedStatements会自动修改为true
    maxPoolPreparedStatementPerConnectionSize: 20
    #配置扩展插件
    filters: stat,wall,slf4j,config
    #通过connectProperties属性来打开mergeSql功能;慢SQL记录
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;config.decrypt=true;config.decrypt.key=公钥
    #合并多个DruidDataSource的监控数据
    use-global-data-source-stat: true
mybatis-plus:
  mapper-locations: classpath*:/mapper/*.xml
  #实体扫描,多个package用逗号或者分号分隔
  typeAliasesPackage: com.violet.sys.entity
  # 日志配置
  configuration:
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    #    属性为true来开启驼峰功能。
    map-underscore-to-camel-case: true
  ##是否控制台 print mybatis-plus 的 LOGO
  global-config:
    banner: false
feign:
  hystrix:
    enabled: true
hystrix:
  command:
    default:  #default全局有效
      execution:
        timeout:
          #是否开启超时熔断
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 6000 #断路器超时时间,默认1000ms
    HystrixCommonKey:  #HystrixCommandKey:要单独设置超时时间的方法
      execution:
        timeout:
          #是否开启超时熔断,默认true
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 10000 #断路器超时时间  
seata:
  enabled: true
  application-id: {spring.application.name}
  tx-service-group: sys_tx_dev  
  enable-auto-data-source-proxy: true  #这里使用自动注册
  data-source-proxy-mode: AT
  use-jdk-proxy: false
  excludes-for-auto-proxying: firstClassNameForExclude,secondClassNameForExclude
  service:
    vgroupMapping:
      sys_tx_dev: dev
    grouplist:
      dev: 127.0.0.1:8091

注意事项这里是seata的事务分组第一要和seata.vgroupMapping相对应,grouplist是注册那个seata服务的地址ip

分布式事务解决方案之seata集成nacos测试(AT模式)_第1张图片


2.4.2代码

Controller:

package com.violet.sys.controller;

import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.violet.sys.entity.SysUser;
import com.violet.sys.feign.NettyFeignService;
import com.violet.sys.service.SysUserService;

import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("seata")
@Api(tags={
     "分布式事务"})
public class SeataController {
     
    @Autowired
    private SysUserService sysUserService;
    @Autowired
    NettyFeignService nettyFeignService;
    
    private Logger logger = LoggerFactory.getLogger(SeataController.class);

    @RequestMapping(value = "/testTransactional", method = RequestMethod.GET)
    @ApiOperation(value = "测试分布式事务", notes = "接口描述", httpMethod = "GET")
    @ApiOperationSupport(author = "violet")
    //GlobalTransactional调用方一定要加GlobalTransactional
    @GlobalTransactional  
    public String testTransactional() throws NullPointerException {
     
        SysUser sysUser = new SysUser();
        sysUser.setUserName("1112");
        sysUserService.save(sysUser);
        String success = nettyFeignService.test("success");
        //故意放一个异常
        int i = 1/0;
        return success;
    }
}

feign

@FeignClient(name  = "netty-service",fallback = HystrixMessage.class)
public interface NettyFeignService {
     

    @GetMapping("/user/test")
    String test(@RequestParam("name") String name);
}

然后启动上面添加@EnableFeignClients

2.5第二服务器的application-dev.yml

server:
  port: 13000
spring:
  application:
    name:netty-service
  main:
    allow-bean-definition-overriding: true
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://ip:3306/表名?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: 账号
    password: 密码
    #初始化时建立物理连接的个数
    initialSize: 2
    #最小连接池数量
    minIdle: 2
    #最大连接池数量 maxIdle已经不再使用
    maxActive: 2
    #获取连接时最大等待时间,单位毫秒
    maxWait: 60000
    #申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
    testWhileIdle: true
    #既作为检测的间隔时间又作为testWhileIdel执行的依据
    timeBetweenEvictionRunsMillis: 60000
    #销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接
    minEvictableIdleTimeMillis: 30000
    #用来检测连接是否有效的sql 必须是一个查询语句
    #mysql中为 select 'x'
    #oracle中为 select 1 from dual
    validationQuery: select 'x'
    #申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
    testOnBorrow: false
    #归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
    testOnReturn: false
    #是否缓存preparedStatement,mysql5.5+建议开启
    poolPreparedStatements: true
    #当值大于0时poolPreparedStatements会自动修改为true
    maxPoolPreparedStatementPerConnectionSize: 20
    #配置扩展插件
    filters: stat,wall,slf4j,config
    #通过connectProperties属性来打开mergeSql功能;慢SQL记录
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;config.decrypt=true;config.decrypt.key=公钥
    #合并多个DruidDataSource的监控数据
    use-global-data-source-stat: true
mybatis-plus:
  mapper-locations: classpath*:/mapper/*.xml
  #实体扫描,多个package用逗号或者分号分隔
  typeAliasesPackage: com.violet.sys.entity
  # 日志配置
  configuration:
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    #    属性为true来开启驼峰功能。
    map-underscore-to-camel-case: true
  #是否控制台 print mybatis-plus 的 LOGO
  global-config:
    banner: false
feign:
  hystrix:
    enabled: true
hystrix:
  command:
    default:  #default全局有效
      execution:
        timeout:
          #是否开启超时熔断
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 6000 #断路器超时时间,默认1000ms
    HystrixCommonKey:  #HystrixCommandKey:要单独设置超时时间的方法
      execution:
        timeout:
          #是否开启超时熔断,默认true
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 10000 #断路器超时时间    
seata:
  enabled: true
  application-id: {spring.application.name}
  tx-service-group: netty_tx_dev  
  enable-auto-data-source-proxy: true  #这里使用自动注册
  data-source-proxy-mode: AT
  use-jdk-proxy: false
  excludes-for-auto-proxying: firstClassNameForExclude,secondClassNameForExclude
  service:
    vgroupMapping:
      netty_tx_dev: dev
    grouplist:
      dev: 127.0.0.1:8091

数据库sql

CREATE TABLE `表名`.`friend`  (
  `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `userid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户id',
  `friends_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '好友id',
  `comments` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
  `createtime` date NULL DEFAULT NULL COMMENT '添加好友日期',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `UK`(`userid`, `friends_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
2.5.1 代码

controller:


import com.netty.violetnetty.entity.Friend;
import com.netty.violetnetty.service.IFriendService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 

* 前端控制器 *

* * @author violet * @since 2020-06-27 */
@RestController @RequestMapping("/user") public class FiendController { @Autowired FriendService friendService; @GetMapping("/test") //这里也可以添加GlobalTransactional ,当该服务报错会使用本地事务回滚,如果远程方报错会使用seata管理的事务回滚 @Transactional public String test(@RequestParam("name") String name) { Friend friend = new Friend(); friend.setComments("name"); this.friendService.save(friend); return name; } }

然后启动上面添加@EnableFeignClients

3. 启动服务 查看seata 注册日志

查看seata日志会发现已经注册到了seata中,并且开启了数据库代理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i9gHADoy-1601021032484)(C:\Users\VIolet\AppData\Roaming\Typora\typora-user-images\image-20200925155145564.png)]

4.调用接口

调用http://127.0.0.1:12000/seata/testTransactional

分布式事务解决方案之seata集成nacos测试(AT模式)_第2张图片

查看2个服务器的日志你会发现,他们的xid相同,并且都执行的回滚,数据库数据未添加,到这里AT模式就搭建完了,并且seata也会刷新日志出现相同的xid
在这里插入图片描述

2.为什么通过feign能执行分布式事务?

解析: 前面说过和最后的日志查看你会发现主要是通过xid来进行回滚,开启事务,整合seata依赖后,seata会拦截feign或者restTemplate的请求将请求方的Xid放到头中,到了服务方同样会拦截请求判断头中是否存在Xid,如果存在会开启事务并且绑定Xid,可以通过 RootContext.getXID() 获得当前方的绑定的Xid

你可能感兴趣的:(分布式事务,seata,java,分布式,java)