Springcloud2021+Nacos2.2+Dubbo3+Seata1.6實現分散式事務

2023-07-07 21:01:47

範例程式碼地址:https://gitee.com/gtnotgod/Springcloud-alibaba.git
更詳細參考Gitee完整的專案:https://gitee.com/gtnotgod/Springcloud-alibaba.git

官網下載Nacos

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

壓縮包解壓:

設定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

啟動完成,

存取: http://localhost:8848/nacos

存取:賬號密碼 nacos nacos

官網下載Seata

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

本地解壓

初始化資料庫:進入seata\script\server\db

設定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 ,雙擊


出現這個

Nacos服務列表


出現這個

Nacos控制檯設定 tx-service-group


出現這個

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("利用分散式事務");
    }
}

啟動兩個專案演示

輸入專案一呼叫專案二的SwaggerUI介面測試地址

http://localhost:10085/doc.html#/default/測試介面/getThirdSysRouterInDubboBySeataUsingGET

執行呼叫

分析響應結果,由於手動製造了異常,被全域性異常捕獲,響應結果成功

檢視A庫A表和B庫B表是否插入成功

A庫A表沒有插入成功:分散式事務關鍵字的一條記錄

B庫B表沒有插入成功:分散式事務關鍵字的一條記錄

資料庫結果是成功的,同時回滾了。。。

檢視紀錄檔

A服務請求進入。執行了A庫A表插入成功

繼續紀錄檔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系統

A系統收到B'系統的響應:

A系統收到B'系統的響應繼續走:觸發除零異常

A系統:觸發全域性例外處理

中間觸發了這幾個Seata二階段提交的關鍵字紀錄檔

這時候意思很明顯 失敗回滾;去掉除零異常再測試

程式碼優化一下:由前端傳入引數控制是否觸發異常區回滾

測試:介面返回操作成功

測試:資料庫是否操作成功
B系統:

A系統

驗證通過。。。

再測試失敗全域性事物回滾::


結束。。。。。。。。。。。。

更詳細參考Gitee完整的專案:https://gitee.com/gtnotgod/Springcloud-alibaba.git