6

Springcloud2021+Nacos2.2+Dubbo3+Seata1.6实现分布式事务 - 白嫖老郭

 1 year ago
source link: https://www.cnblogs.com/gtnotgod/p/17535837.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

示例代码地址:https://gitee.com/gtnotgod/Springcloud-alibaba.git
更详细参考Gitee完整的项目:https://gitee.com/gtnotgod/Springcloud-alibaba.git

官网下载Nacos

https://nacos.io/zh-cn/index.html
image

压缩包解压:

image

配置Nacos:**/nacos/conf/application.properties

#*************** Spring Boot Related Configurations ***************#
### Default web context path:
server.servlet.contextPath=/nacos
### Include message field
server.error.include-message=ALWAYS
### Default web server port:
server.port=8848
### Metrics for elastic search
management.metrics.export.elastic.enabled=false
#management.metrics.export.elastic.host=http://localhost:9200
#*************** Access Log Related Configurations ***************#
### If turn on the access log:
server.tomcat.accesslog.enabled=true
### The access log pattern:
server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}i %{Request-Source}i
### The directory of access log:
server.tomcat.basedir=file:.
#*************** Access Control Related Configurations ***************#
### If enable spring security, this option is deprecated in 1.2.0:
#spring.security.enabled=false
### The ignore urls of auth
nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-ui/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/**
### The auth system to use, currently only 'nacos' and 'ldap' is supported:
nacos.core.auth.system.type=nacos
### If turn on auth system: ### 开启鉴权
nacos.core.auth.enabled=true
### Turn on/off caching of auth information. By turning on this switch, the update of auth information would have a 15 seconds delay.
nacos.core.auth.caching.enabled=true
### 关闭使用user-agent判断服务端请求并放行鉴权的功能
nacos.core.auth.enable.userAgentAuthWhite=false
### Since 1.4.1, worked when nacos.core.auth.enabled=true and nacos.core.auth.enable.userAgentAuthWhite=false.
### The two properties is the white list for auth and used by identity the request from other server.
nacos.core.auth.server.identity.key=nacos
nacos.core.auth.server.identity.value=nacos
### The token expiration in seconds:
nacos.core.auth.plugin.nacos.token.cache.enable=false
nacos.core.auth.plugin.nacos.token.expire.seconds=18000
### The default token (Base64 String):
nacos.core.auth.default.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
### 2.1.0 版本后
nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
nacos.istio.mcp.server.enabled=false

启动Nacos:进入目录/nacos/bin

startup.cmd -m standalone

启动完成,

image

访问: http://localhost:8848/nacos

image

访问:账号密码 nacos nacos

image

官网下载Seata

https://seata.io/zh-cn/index.html

image

本地解压

image

初始化数据库:进入seata\script\server\db

image

配置Naco配置中心和Nacos注册中心,Mysql数据库

seata\conf\application.yml


server:
  port: 7091

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${user.home}/logs/seata
  extend:
    logstash-appender:
      destination: 127.0.0.1:4560
    kafka-appender:
      bootstrap-servers: 127.0.0.1:9092
      topic: logback_to_logstash

console:
  user:
    username: seata
    password: seata

seata:
  service:
    vgroup-mapping:
      my-seata-group: default
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group : "SEATA_GROUP"
      namespace: "70180ace-e644-4a10-b590-e6a6003b1bbe"
      username: "nacos"
      password: "nacos"
      data-id: seataServer.properties
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group : "SEATA_GROUP"
      namespace: "70180ace-e644-4a10-b590-e6a6003b1bbe"
      username: "nacos"
      password: "nacos"
  store:
    # support: file 、 db 、 redis
    # 注意数据库版本为5.7.26 , 使用8.0.12时报错Could not retrieve transation read-only status server
    mode: db
    db:
      datasource: druid
      db-type: mysql
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/seata1.6.1?rewriteBatchedStatements=true&useUnicode=true
      user: root
      password: root
      min-conn: 5
      max-conn: 100
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table
      distributed-lock-table: distributed_lock
      query-limit: 100
      max-wait: 5000
#  server:
#    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login

启动Seata ,双击

image

出现这个

image

Nacos服务列表

image

出现这个
image

Nacos控制台配置 tx-service-group

image

出现这个
image

springcloud项目集成Nacos和Seata

项目公共依赖:

公共依赖lombok、web、MySQL、mybatisplus、dynamicDataSource、knife4j、bootstrap、loadbalancer

 <properties>
        <mybatis-plus.version>3.5.1</mybatis-plus.version>
        <com.alibaba.druid.version>1.2.11</com.alibaba.druid.version>
        <nacos-client.version>2.0.4</nacos-client.version>
        <fastJson-version>2.0.18</fastJson-version>
    </properties>

    <dependencies>
        <!--lombok-实体类简化依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--web项目驱动-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--Mysql数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${com.alibaba.druid.version}</version>
        </dependency>
        <!--Mybatis  ORM相关依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <!--Mybatis-plus 代码生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatisplus.verison}</version>
        </dependency>
        <!-- mybatis-plus默认模板引擎-->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>${velocity.engine.version}</version>
        </dependency>
        <!--Mybatis-plus 多数据源-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>${mybatisplus.verison}</version>
        </dependency>
        <!--Swagger2-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>${knife4j.version}</version>
        </dependency>
        <!-- bootstrap最高级启动配置读取 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <!--新版的移除了Ribbon的负载策略,所需改用新版的loadbalancer-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastJson-version}</version>
        </dependency>
    </dependencies>

项目一 AppUserManage :10085

 <dependencies>

        <!--分布式事务解决方案(阿里巴巴seata)-->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>${seata.version}</version>
        </dependency>

        <!--If your project base on `Spring Boot`, you can directly use the following dependencies-->
        <!--Notice: `seata-spring-boot-starter` has included `seata-all` dependency-->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>${seata.version}</version>
        </dependency>
        <!--分布式事务解决方案(阿里巴巴seata)-->
        <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>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-all</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--微服务项目公共依赖lombok、web、MySQL、mybatisplus、dynamicDataSource、knife4j、bootstrap、loadbalancer-->
        <dependency>
            <groupId>com.gton</groupId>
            <artifactId>common-dependce</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!--nacos服务发现-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--Nacos配置中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--Springcloud微服务启动-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
        <!--全局最高配置加载-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>3.1.1</version>
        </dependency>

        <!--Dubbo的RPC框架-->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
        <!--Springboot的dubbo适配-->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
        </dependency>
        <!--Dubbo适配naocs-->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-nacos</artifactId>
        </dependency>
        <!--nacos2的客户端-->
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--   redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--Redis使用公共依赖-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
    </dependencies>

核心配置 bootstrap.yml

# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
# Nacos 配置中心的namespace。需要注意,如果使用 public 的 namcespace ,请不要填写这个值,直接留空即可
# spring.cloud.nacos.config.namespace=
spring:
  application:
    #服务自动发现并注册,不需要name
    name: user-manager
  cloud:
    alibaba:
      seata:
        tx-service-group: my-seata-group
    nacos:
      discovery:
        server-addr: ${spring.cloud.nacos.server-addr}  # 设置配置中心服务端地址
        group: AlibabaCloud
        username: nacos
        password: nacos
        namespace: 70180ace-e644-4a10-b590-e6a6003b1bbe
      config:
        username: ${spring.cloud.nacos.discovery.username}    # Nacos认证信息用户名
        password: ${spring.cloud.nacos.discovery.password}     # Nacos认证信息密码
        context-path: /nacos    # Nacos根路径
        enabled: true
        server-addr: ${spring.cloud.nacos.server-addr}
        group: ${spring.cloud.nacos.discovery.group}
        namespace: 70180ace-e644-4a10-b590-e6a6003b1bbe
        file-extension: properties
      server-addr: 127.0.0.1:8848 # 设置配置中心服务端地址
seata:
  enabled: true
  application-id: ${spring.application.name}
  # 客户端和服务端在同一个事务组; Seata 事务组编号,用于 TC 集群名, 一定要和 config.tx(nacos) 中配置的相同
  tx-service-group: my-seata-group
  # 自动数据源代理
  enable-auto-data-source-proxy: true
  # 数据源代理模式(分布式事务方案)
  data-source-proxy-mode: AT
  # 事务群组,配置项值为TC集群名,需要与服务端保持一致
  service:
    vgroup-mapping:
      my-seata-group: default
  #整合nacos配置中心
  config:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.server-addr}
      group: SEATA_GROUP
      namespace: 70180ace-e644-4a10-b590-e6a6003b1bbe
      data-id: seataServer.properties
      username: nacos
      password: nacos
  #整合nacos注册中心
  registry:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.server-addr}
      group: SEATA_GROUP
      namespace: 70180ace-e644-4a10-b590-e6a6003b1bbe
      # 默认TC集群名
      cluster: default
      # 服务名,与服务端中registry.conf配置要一致
      application: seata-server
      username: nacos
      password: nacos
dubbo:
  # 配置元数据中心
  metadata-report:
    address: nacos://127.0.0.1:8848?username=${dubbo.metadata-report.username}&password=${dubbo.metadata-report.password}
    username: nacos
    password: nacos
    parameters:
      namespace: 70180ace-e644-4a10-b590-e6a6003b1bbe
    retry-times: 30  #重试次数,默认100
    cycle-report: false #关闭定时刷新
  application:
    name: dubbo-consumer
    # 禁用QOS同一台机器可能会有端口冲突现象
    qos-enable: false
    qos-accept-foreign-ip: false
    service-discovery:
      migration: FORCE_APPLICATION # FORCE_APPLICATION,只消费应用级地址,如无地址则报错,单订阅 3.x 地址
  protocol:
    name: dubbo
    port: -1
  scan:
    base-packages: com.gton.router.impl
  cloud:
    subscribed-services: consumer
  registry:
    address: nacos://127.0.0.1:8848?username=${dubbo.metadata-report.username}&password=${dubbo.metadata-report.password}
    parameters:
      namespace: 70180ace-e644-4a10-b590-e6a6003b1bbe
  consumer:
    check: false
package com.gton;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @description: APP-Usermanage服务
 * @author: GuoTong
 * @createTime: 2022-09-24 13:48
 * @since JDK 1.8 OR 11
 **/
@SpringBootApplication
@EnableDiscoveryClient //服务发现客户端
public class AppUserManage {

    public static void main(String[] args) {
        /**
         * Description:
         * 数据库连接全部使用mybatis-plus安全加密,加密密匙:
         * 在启动参数 (Program arguments)  --mpw.key=4b57e89bac82a797
         */
        try {
            SpringApplication.run(AppUserManage.class, args);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试接口 包含:Dubbo的RPC调用,Seata分布式事务的测试


/**
 * @description:
 * @author: GuoTong
 * @createTime: 2022-09-24 14:37
 * @since JDK 1.8 OR 11
 **/
@RestController
@Api(tags = "测试接口")
@SwaggerScanClass
@Slf4j
public class HelloController {

    @Value("${user.name:zhangsan}")
    private String name;

    @Value("${user.age:100}")
    private int age;


    @Autowired
    private GpLoginService gpLoginService;


    /**
     * Description: Dubbo RPC调用服务方
     *
     * @author: GuoTong
     * @date: 2022-09-24 17:50:08
     * @param null
     * @return:
     */
    @Autowired
    private DubboRPCThirdSysRouterService dubboRPCThirdSysRouterService;


    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    @ApiOperation(value = "hello", notes = "json格式")
    @ApiResponse(code = 200, message = "返回的是json格式", response = Resp.class)
    public Resp hello() {
        Map<String, Object> data = new HashMap<>();
        data.put("call-UrI", "hello");
        data.put("port", "8089");
        data.put("service-Name", "cloud-nacos");
        data.put("server-type", "nacos");
        data.put("server-addr", "127.0.0.1:8848");
        data.put("swagger-Uri", "doc.html");
        data.put("nacos-properties-name", name);
        data.put("nacos-properties-age", age);
        return Resp.Ok(data);
    }


    @RequestMapping(value = "/dubbo_get_thirdsysrouter", method = RequestMethod.GET)
    @ApiOperation(value = "usermanager服务调用thirdsysrouter", notes = "json格式")
    @ApiResponse(code = 200, message = "返回的是json格式", response = Resp.class)
    public Resp getThirdSysRouterInDubbo(HttpServletRequest request) {
        String traceId = MDC.get(ContextCommonMsg.TRACE_ID);
        String username = request.getParameter("username");
        if (StringUtils.isEmpty(username)) {
            username = "郭童";
        }
        return dubboRPCThirdSysRouterService.getUserManagerService_Hello(traceId, username);
    }


    @GlobalTransactional
    @RequestMapping(value = "/dubbo_seata_thirdsysrouter", method = RequestMethod.GET)
    @ApiOperation(value = "usermanager服务调用thirdsysrouter分布式事务", notes = "json格式")
    @ApiResponse(code = 200, message = "返回的是json格式", response = Resp.class)
    public Resp getThirdSysRouterInDubboBySeata(HttpServletRequest request) {
        String traceId = MDC.get(ContextCommonMsg.TRACE_ID);
        String username = request.getParameter("username");
        if (StringUtils.isEmpty(username)) {
            username = "郭童";
        }
        // 插入本地数据库
        GpLogin gpLogin = new GpLogin().
                setUsername("GlobalTransactional" + username)
                .setPassword("dubbo_seata_thirdsysrouter")
                .setWelcomeName("分布式事务" + traceId);
        gpLoginService.save(gpLogin);
        log.info("第一段插入数据库study库成功" + gpLogin.getId());
        Resp resp = dubboRPCThirdSysRouterService.databaseUpdate(gpLogin);
        log.info("分布式事务----结束,第二段插入数据库study02-id={},响应码=" + resp.getCode(), resp.getData());
        int j = 100 / 0;
        return resp;
    }
}

Seata的核心注解: 全局AT的undolog依赖关系型数据库的事务 开启@GlobalTransactional

Dubbo的核心注解: 调用方:@DubboReference 被调方: @DubboService

省略。。。。。。。。

项目二 ThirdSysRouterApp :8099

配合+依赖和项目一类似,使用不同的数据库和一张表。。。。。。。。。

项目一的Dubbo调用项目二的方法

/**
 * @description: 基于dubbo实现远程过程调用
 * @author: GuoTong
 * @createTime: 2022-09-24 17:40
 * @since JDK 1.8 OR 11
 **/
@Service
@DubboService
@Slf4j
public class DubboRouterService implements RemoteUserManageService {

    @Autowired
    private GotoUserManagerService gotoUserManagerService;

    @Autowired
    private StudentService studentService;

    /**
     * Description: Dubbo的消息提供方  @DubboService
     *
     * @author: GuoTong
     * @date: 2022-09-24 17:42:36
     */

    @Override
    public Resp<Map<String, Object>> getUserManagerService_Hello(String TraceId, String userName) {
        log.info("[{}]dubbo 方式请求进入了!!!!", TraceId);
        Resp<Map<String, Object>> mapData = gotoUserManagerService.createMapData();
        Map<String, Object> data = mapData.getData();
        data.put("TraceId", TraceId);
        data.put("userName", userName);
        return mapData;
    }

    @Override
    public Resp databaseUpdate(Object gpLogin) {
        Student gpLogin2 = null;
        if (gpLogin instanceof GpLogin) {
            Long id = ((GpLogin) gpLogin).getId();
            if (id != null && id > 0) {
                // 插入本地数据库
                gpLogin2 = new Student().
                        setUsername("thirdsysrouter")
                        .setPassword("thirdsysrouter")
                        .setPwdShow("分布式事务thirdsysrouter");
                studentService.save(gpLogin2);
                // 执照异常,利用分布式事务回滚  int i = 100 / 0;
            }
        }
        return gpLogin2 != null ? Resp.Ok(gpLogin2.getId()) : Resp.error("利用分布式事务");
    }
}

启动两个项目演示

image

输入项目一调用项目二的SwaggerUI接口测试地址

http://localhost:10085/doc.html#/default/测试接口/getThirdSysRouterInDubboBySeataUsingGET

image

分析响应结果,由于手动制造了异常,被全局异常捕获,响应结果成功

image

查看A库A表和B库B表是否插入成功

A库A表没有插入成功:分布式事务关键字的一条记录

image

B库B表没有插入成功:分布式事务关键字的一条记录

image

数据库结果是成功的,同时回滚了。。。

A服务请求进入。执行了A库A表插入成功

image

继续日志Dubbo 去RPC调用B服务了

2023-07-07 18:23:43.322 INFO 14568 --- [io-10085-exec-9] c.g.s.DubboRPCThirdSysRouterService : [7db0c75b-8a65-4d31-84cb-6cdd14d580a4] [Seata]:dubbo RPC方式请求发送出去!!!!

B服务执行方法;B库B表插入成功响应-->A系统

image

A系统收到B'系统的响应:

image

A系统收到B'系统的响应继续走:触发除零异常

image

A系统:触发全局异常处理

image
image

中间触发了这几个Seata二阶段提交的关键字日志

image

这时候意思很明显 失败回滚;去掉除零异常再测试

代码优化一下:由前端传入参数控制是否触发异常区回滚

image

测试:界面返回操作成功

image

测试:数据库是否操作成功
B系统:

image

A系统

image

验证通过。。。

再测试失败全局事物回滚::

image

结束。。。。。。。。。。。。

更详细参考Gitee完整的项目:https://gitee.com/gtnotgod/Springcloud-alibaba.git


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK