Spring Cloud Alibaba 整合 Seata 實現分散式事務

2023-03-02 18:02:33

Spring Boot單體服務中,新增@Transactional註解就能實現事務。在單體服務中,執行事務都是在同一個資料庫下進行。但是隨著業務越來越複雜,資料量越來越大會進行分庫分表。在微服務場景下,每個服務都有自己的資料庫。之前的單體事務無法處理跨庫的事務,這個時候就需要使用分散式事務。

前面 Seata 環境搭建 介紹了seata的安裝,安裝後就需要結合實戰專案介紹分散式事務的應用。

版本

  • spring-cloud-starter-alibaba:2.1.1.RELEASE
  • spring-cloud-alibaba-dependencies:2.1.1.RELEASE
  • Seata Server 1.5.2
  • Nacos Server:2.0.1

不同的版本實現可能不太相同,儘量保持版本一致。

效果展現

使用者下單的業務邏輯,整個邏輯有兩個微服務提供支援:

  • 倉庫服務:給對應的商品扣減庫存
  • 訂單服務:建立訂單

使用者下單購買商品。

  • 先建立訂單,建立訂單之後,再扣庫存。
  • 如果庫存不夠,就回滾訂單。

搭建服務

專案結構

有三個專案,倉庫服務stock、訂單服務orderseata服務。seata服務先呼叫訂單服務,然後呼叫倉庫服務。

maven 依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.1.1.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
<!--Spring Cloud Alibaba-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.1.1.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>$Greenwich.SR3</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
<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>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--seata-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
</dependency>

三個專案的依賴都是一樣的。

yml 設定

seata需要分別設定設定中心註冊中心

  • 設定中心內部放置著各種組態檔,你可以通過自己所需進行獲取設定載入到對應的使用者端.比如Seata Client端(TM,RM),Seata Server(TC),會去讀取全域性事務開關,事務對談儲存模式等資訊。

  • 註冊中心可以說是微服務架構中的」通訊錄「,它記錄了服務和服務地址的對映關係。在分散式架構中,服務會註冊到這裡,當服務需要呼叫其它服務時,就到這裡找到服務的地址,進行呼叫.比如Seata Client端(TM,RM),發現Seata Server(TC)叢集的地址,彼此通訊.

從官網檔案找到Nacos 設定中心,在application.yml加入對應的設定:

seata:
  config:
    type: nacos
    nacos:
      server-addr: xxxx
      group: SEATA_GROUP
      namespace: xxxxxx
      username: nacos
      password: nacos

groupnamespaceNacos控制中心頁面可以找到,分別對應下圖:

Nacos註冊中心同樣可以獲取如下設定:

seata:
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group : SEATA_GROUP
      namespace: xxxxx
      username: nacos
      password: nacos

application對應的是Seata Server設定的服務名。Seata Server將自己封裝成一個微服務註冊到Nacos註冊中心。其他設定和設定中心設定一致。

倉庫服務

全部貼程式碼,文章也比較繁瑣。所以使用介面的方式,簡單宣告程式碼即可。

倉庫扣減庫存:

public interface StockService {

    /**
     * 減庫存
     * @param id    商品id
     * @param num   扣減數量
     */
    void reduceStock(Long id, BigDecimal num);
}

訂單服務

建立訂單:

public interface OrderService {

    /**
     * 建立訂單
     * @param num     下單數量
     * @param price   商品價格
     * @param goodsId 商品id
     */
    Order create(BigDecimal num,BigDecimal price,Long goodsId);
}

下單操作:

先建立訂單,然後扣減庫存

@GlobalTransactional(rollbackFor = Exception.class)
public void order(Long goodsId) {
    orderService.create(BigDecimal.TEN,BigDecimal.TEN,goodsId);
    stockService.reduceStock(goodsId,BigDecimal.TEN);
}

資料不回滾

呼叫下單服務,庫存不夠,報錯了,但是建立訂單不會回滾。

分析原因

事務沒有回滾,從官網範例下載到本地執行,卻可以回滾,對比兩者控制檯輸出之後,發現官網範例使用了mybatis-plus自動新增了DataSourceProxy資料來源代理。

所以需要設定seata代理資料來源。

設定資料來源代理

設定Druid資料來源代理:

@Configuration
public class DataSourceProxyConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath*:/mapper/*.xml"));
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();
    }

}

同時需要關閉資料來源自動代理:

seata.enable-auto-data-source-proxy: false

再呼叫下單介面,倉庫報錯報錯,建立訂單回滾。訂單服務控制檯也提示了資料回滾:

2023-03-01 23:49:32.162  WARN 66509 --- [nio-8040-exec-3] c.a.c.seata.web.SeataHandlerInterceptor  : xid in change during RPC from 192.168.31.115:8091:1864853653168447501 to null
2023-03-01 23:49:33.653  INFO 66509 --- [h_RMROLE_1_3_24] i.s.c.r.p.c.RmBranchRollbackProcessor    : rm handle branch rollback process:xid=192.168.31.115:8091:1864853653168447501,branchId=1864853653168447502,branchType=AT,resourceId=jdbc:mysql://34.80.215.211:3306/test,applicationData=null
2023-03-01 23:49:33.653  INFO 66509 --- [h_RMROLE_1_3_24] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 192.168.31.115:8091:1864853653168447501 1864853653168447502 jdbc:mysql://34.80.215.211:3306/test
2023-03-01 23:49:36.005  INFO 66509 --- [h_RMROLE_1_3_24] i.s.r.d.undo.AbstractUndoLogManager      : xid 192.168.31.115:8091:1864853653168447501 branch 1864853653168447502, undo_log deleted with GlobalFinished
2023-03-01 23:49:36.393  INFO 66509 --- [h_RMROLE_1_3_24] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked

PhaseTwo_Rollbacked表示兩階段回滾。

報錯 can not get cluster name

控制檯報錯:

can not get cluster name in registry config 'service.vgroupMapping.nacos-provide-order-seata-service-group', please make sure registry config correct
can not get cluster name in registry config 'service.vgroupMapping.nacos-provide-order-seata-service-group', please make sure registry config correct
can not get cluster name in registry config 'service.vgroupMapping.nacos-provide-order-seata-service-group', please make sure registry config correct

服務在Nacos設定中心找不到service.vgroupMapping.nacos-provide-order-seata-service-group,這是在找分組事務,官網檔案有如下介紹:

設定中心有service.vgroupMapping.default_tx_group組態檔,根據上圖講解,需要在application.yml設定:

seata:
   tx-service-group: default_tx_group

設定後還是報錯。定位到報錯的原始碼,向上調式原始碼,在application.yml設定:

spring:
  cloud:
    alibaba:
      seata:
        tx-service-group: default_tx_group

總結

本文介紹了Spring Cloud整合分散式事務seta,主要有:

  • 新增相關依賴
  • 設定application.yml設定,主要新增nacos設定中心和註冊中心的設定。
  • 實現一個下單服務,先建立訂單,然後扣減庫存。庫存不夠,建立訂單回滾。

搭建服務完成之後,事務不回滾,對比官網範例專案。需要新增資料來源代理,同時關閉據源自動代理。分散式事務就生效了。

控制檯一直報錯can not get cluster name in registry config,通過偵錯原始碼,找到問題的根源,這裡學到了通過原始碼解決問題。以前前段時間一直在看原始碼,這次通過原始碼解決問題,努力還是會有收穫的

原始碼