自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

SpringBoot+Dubbo+Seata 輕松搞定分布式事務(wù)提交數(shù)據(jù)不一致難題!

云計(jì)算 分布式
如果當(dāng)前的服務(wù)工程采用的是 openFeign 來實(shí)現(xiàn)服務(wù)遠(yuǎn)程調(diào)用,也可以通過集成spring-cloud-starter-alibaba-seata依賴包實(shí)現(xiàn)分布式事務(wù)操作,其實(shí)現(xiàn)原理也是在遠(yuǎn)程調(diào)用的請(qǐng)求頭部中插入全局事務(wù) ID,依次傳遞到下游服務(wù)中,從而保證全局事務(wù)的統(tǒng)一提交和回滾操作。

一、背景介紹

在上篇文章中,我們對(duì) Seata 的架構(gòu)設(shè)計(jì)、部署方式以及使用操作做了一個(gè)簡(jiǎn)單的介紹,相信大家對(duì)它已經(jīng)有了初步的了解。

我們知道,在現(xiàn)有的 Spring Cloud 體系中,有兩種技術(shù)方式可以實(shí)現(xiàn)服務(wù)的遠(yuǎn)程調(diào)用。

  • 方式一:通過 Http 工具向目標(biāo)服務(wù)接口發(fā)起遠(yuǎn)程調(diào)用,比如OpenFeign、Http Client等工具。
  • 方式二:通過 Dubbo 工具向目標(biāo)服務(wù)接口發(fā)起遠(yuǎn)程調(diào)用,由于 Dubbo 采用 TCP 協(xié)議進(jìn)行通信,相對(duì) HTTP 方式來說,通信效率會(huì)更高一些,應(yīng)用也更廣泛

由于國(guó)內(nèi)很多的項(xiàng)目采用 Dubbo 來實(shí)現(xiàn)服務(wù)的遠(yuǎn)程調(diào)用,下面我們以此為例,詳細(xì)的介紹一下如何將 Dubbo 服務(wù)接入 Seata 來實(shí)現(xiàn)分布式事務(wù)操作。

二、方案實(shí)踐

我們以之前的工程為例,對(duì)其進(jìn)行適度改造,改造后服務(wù)之間的交互流程可以用如下圖來簡(jiǎn)要概括。

具體的實(shí)施過程如下。

2.1、創(chuàng)建服務(wù)接口

首先,創(chuàng)建一個(gè)簡(jiǎn)單的 Maven 工程,命名為seata-dubbo-api,將需要對(duì)外暴露的服務(wù)接口寫入到這里。示例接口如下:

public interface StockApi {

    /**
     * 庫存扣減
     * @param productCode
     * @param count
     * @return
     */
    boolean deduct(String productCode, int count);
}

服務(wù)接口創(chuàng)建完成之后,接下來我們?cè)賮韯?chuàng)建庫存服務(wù)和訂單服務(wù)。

2.2、創(chuàng)建庫存服務(wù)

然后,建一個(gè) Spring Boot 工程,命名為seata-dubbo-stock,并在pom.xml中引入相關(guān)的依賴內(nèi)容,示例如下:

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <spring-boot.version>2.2.5.RELEASE</spring-boot.version>
    <spring-cloud.version>Hoxton.SR3</spring-cloud.version>
    <spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
</properties>

<dependencies>
    <!-- SpringBoot web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--mysql 驅(qū)動(dòng)-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.0.0</version>
    </dependency>
    <!-- Nacos 服務(wù)發(fā)現(xiàn) -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- Dubbo -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-dubbo</artifactId>
    </dependency>
    <!-- seata 分布式事務(wù)組件 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    </dependency>
    <!-- 關(guān)聯(lián)構(gòu)建的api包 -->
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>seata-dubbo-api</artifactId>
        <version>3.0-SNAPSHOT</version>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <!-- 引入 springBoot 版本號(hào) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- 引入 spring cloud 版本號(hào) -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- 引入 spring cloud alibaba 適配的版本號(hào) -->
        <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>
    </dependencies>
</dependencyManagement>

接著,創(chuàng)建一個(gè)application.properties文件并配置相關(guān)配置項(xiàng),示例如下:

spring.application.name=seata-dubbo-stock
server.port=9002

# 添加數(shù)據(jù)源配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/seata-stock
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

# 配置mybatis全局配置文件掃描
mybatis.config-locatinotallow=classpath:mybatis/mybatis-config.xml
# 配置mybatis的xml配置文件掃描目錄
mybatis.mapper-locatinotallow=classpath:mybatis/mapper/*.xml

# 設(shè)置Nacos的服務(wù)地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

# 指定 Dubbo 服務(wù)實(shí)現(xiàn)類的掃描基準(zhǔn)包
dubbo.scan.base-packages=com.example.cloud.nacos.dubbo.seata
# 指定 Dubbo 服務(wù)暴露的協(xié)議
dubbo.protocol.name=dubbo
# 指定 Dubbo 服務(wù)協(xié)議端口,-1 表示自增端口,從 20880 開始
dubbo.protocol.port=-1
# 指定 Dubbo 服務(wù)注冊(cè)中心
dubbo.registry.address=nacos://${spring.cloud.nacos.discovery.server-addr}

# 添加Seata 配置項(xiàng)
# Seata 應(yīng)用編號(hào),默認(rèn)為spring.application.name
seata.application-id=seata-dubbo-stock
# Seata 事務(wù)組編號(hào),用于 TC 集群名
seata.tx-service-group=my_test_tx_group
# Seata 服務(wù)配置項(xiàng),配置對(duì)應(yīng)的虛擬組和分組的映射,其中127.0.0.1:8091為 seata 服務(wù)端的監(jiān)聽端口
seata.service.vgroup-mapping.my_test_tx_group=default
seata.service.grouplist.default=127.0.0.1:8091

再然后,創(chuàng)建一個(gè) Dubbo 服務(wù)并實(shí)現(xiàn)上文創(chuàng)建的服務(wù)接口,示例如下:

@com.alibaba.dubbo.config.annotation.Service
publicclass StockApiImpl implements StockApi {

    @Autowired
    private StockService stockService;

    @Override
    public boolean deduct(String productCode, int count) {
        try {
            stockService.deduct(productCode, count);
            // 正??鄢龓齑?,返回 true
            returntrue;
        } catch (Exception e) {
            // 失敗扣除庫存,返回 false
            returnfalse;
        }
    }
}

其中service和mapper層代碼和之前的庫存服務(wù)工程一樣,在此就不再重復(fù)粘貼了。

最后,創(chuàng)建一個(gè)服務(wù)啟動(dòng)類并添加@EnableDiscoveryClient注解,以便將服務(wù)注冊(cè)到 Nacos。

@EnableDiscoveryClient
@MapperScan("com.example.cloud.nacos.dubbo.seata")
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

將服務(wù)啟動(dòng)起來,在瀏覽器中訪問http://127.0.0.1:8848/nacos,如果不出意外的話,在 Nacos 服務(wù)列表可以看到注冊(cè)的 dubbo 服務(wù)。

2.3、創(chuàng)建訂單服務(wù)

訂單服務(wù)的創(chuàng)建過程與上文類似。

創(chuàng)建一個(gè) Spring Boot 工程,命名為seata-dubbo-order,其pom.xml所需要的依賴內(nèi)容和服務(wù)啟動(dòng)類,與上文完全一致,在此就不重復(fù)粘貼了。

其中的web、service和mapper層代碼和之前的訂單服務(wù)工程也完全一致,在此就不再重復(fù)粘貼了。

下面,我們重點(diǎn)對(duì)OrderService服務(wù)進(jìn)行改造,將通過 HTTP 工具調(diào)用遠(yuǎn)程服務(wù)接口的邏輯移除,改成用 Dubbo 方式實(shí)現(xiàn)服務(wù)的遠(yuǎn)程調(diào)用,示例如下:

@Component
publicclass OrderService {

    @Autowired
    private OrderMapper orderMapper;


    @com.alibaba.dubbo.config.annotation.Reference
    private StockApi stockApi;

    @GlobalTransactional
    public void create(String userId, String productCode, int orderCount) throws Exception {
        // 通過dubbo服務(wù),實(shí)現(xiàn)遠(yuǎn)程扣減庫存
        stockApi.deduct(productCode, orderCount);

        Order order = new Order();
        order.setUserId(userId);
        order.setProductCode(productCode);
        order.setCount(orderCount);
        order.setMoney(orderCount * 100);
        // 創(chuàng)建訂單
        orderMapper.insert(order);
    }
}

與上文類似,在application.properties配置文件中添加相關(guān)的配置項(xiàng),示例如下:

spring.application.name=seata-dubbo-order
server.port=9001

# 添加數(shù)據(jù)源配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/seata-stock
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

# 配置mybatis全局配置文件掃描
mybatis.config-locatinotallow=classpath:mybatis/mybatis-config.xml
# 配置mybatis的xml配置文件掃描目錄
mybatis.mapper-locatinotallow=classpath:mybatis/mapper/*.xml

# 設(shè)置Nacos的服務(wù)地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

# 指定 Dubbo 服務(wù)實(shí)現(xiàn)類的掃描基準(zhǔn)包
dubbo.scan.base-packages=com.example.cloud.nacos.dubbo.seata
# 指定 Dubbo 服務(wù)暴露的協(xié)議
dubbo.protocol.name=dubbo
# 指定 Dubbo 服務(wù)協(xié)議端口,-1 表示自增端口,從 20880 開始
dubbo.protocol.port=-1
# 指定 Dubbo 服務(wù)注冊(cè)中心
dubbo.registry.address=nacos://${spring.cloud.nacos.discovery.server-addr}
# 關(guān)閉dubbo客戶端服務(wù)有效性檢查
dubbo.consumer.check=false

# 添加Seata 配置項(xiàng)
# Seata 應(yīng)用編號(hào),默認(rèn)為spring.application.name
seata.application-id=seata-dubbo-order
# Seata 事務(wù)組編號(hào),用于 TC 集群名
seata.tx-service-group=my_test_tx_group
# Seata 服務(wù)配置項(xiàng),配置對(duì)應(yīng)的虛擬組和分組的映射,其中127.0.0.1:8091為 seata 服務(wù)端的監(jiān)聽端口
seata.service.vgroup-mapping.my_test_tx_group=default
seata.service.grouplist.default=127.0.0.1:8091

將服務(wù)啟動(dòng),再次訪問http://127.0.0.1:8848/nacos的服務(wù)列表,可以看到seata-dubbo-order也成功注冊(cè)到服務(wù)中心,界面如下。

2.4、服務(wù)測(cè)試

最后,我們還是一起來驗(yàn)證一下如下兩種情況,看看是否能如期實(shí)現(xiàn)。

分布式事務(wù)正常提交

分布式事務(wù)異?;貪L

2.4.1、分布式事務(wù)正常提交

首先,重新初始化數(shù)據(jù)庫,數(shù)據(jù)庫中原始數(shù)據(jù)情況如下。

  • seata-stock庫中的庫存數(shù)據(jù)

  • seata-order庫中的訂單數(shù)據(jù)

接著,在瀏覽器中訪問http://127.0.0.1:9001/order/create?userId=張三&productCode=wahaha&orderCount=1,它會(huì)執(zhí)行如下兩個(gè)動(dòng)作:

  • 第一個(gè):調(diào)用庫存服務(wù),將產(chǎn)品產(chǎn)品編碼為wahaha的庫存減 1;
  • 第二個(gè):如果庫存扣減成功,插入一條產(chǎn)品編碼為wahaha數(shù)量為 1 的訂單信息;

發(fā)起接口請(qǐng)求后,再次回看數(shù)據(jù)庫,看看目標(biāo)數(shù)據(jù)表中的數(shù)據(jù)情況。

  • seata-stock庫中的庫存數(shù)據(jù)

  • seata-order庫中的訂單數(shù)據(jù)

從數(shù)據(jù)結(jié)果來看,與預(yù)期一致。

我們還可以通過查看服務(wù)的日志信息,來觀察分支事務(wù)的操作情況。

其中Branch commit result信息代表分支事務(wù)的二階段操作。

2.4.2、分布式事務(wù)異?;貪L

測(cè)試完正常流程之后,下面我們?cè)賮眚?yàn)證一下異常流程。

修改OrderService類中create()方法代碼,在創(chuàng)建訂單完成之后,試圖拋出異常,測(cè)試一下扣減的庫存數(shù)據(jù)是否能正?;貪L。

首先,我們還是對(duì)數(shù)據(jù)庫中原始數(shù)據(jù)進(jìn)行截個(gè)圖。

  • seata-stock庫中的庫存數(shù)據(jù)

  • seata-order庫中的訂單數(shù)據(jù)

然后,再次在瀏覽器中訪問http://127.0.0.1:9001/order/create?userId=張三&productCode=wahaha&orderCount=1。

預(yù)期的結(jié)果是:兩個(gè)庫的數(shù)據(jù)應(yīng)該都不會(huì)發(fā)生變化!

再次回看數(shù)據(jù)庫,觀察目標(biāo)數(shù)據(jù)表中的數(shù)據(jù)情況。

  • seata-stock庫中的庫存數(shù)據(jù)

  • seata-order庫中的訂單數(shù)據(jù)

數(shù)據(jù)在5秒之內(nèi)是執(zhí)行成功的,為了便于觀察數(shù)據(jù)變化,我們?cè)谏衔膾伄惓5奈恢猛nD了 5 秒。

過 5 秒后,再次回看數(shù)據(jù)庫表中的數(shù)據(jù)情況,結(jié)果如下。

  • seata-stock庫中的庫存數(shù)據(jù)

  • seata-order庫中的訂單數(shù)據(jù)

從數(shù)據(jù)最終結(jié)果來看,與預(yù)期是一致的。

在瀏覽器中訪問http://127.0.0.1:7091,登陸 Seata TC Server 服務(wù)監(jiān)控臺(tái),還可以看到全局事務(wù)的注冊(cè)信息和狀態(tài)。

三、Seata 服務(wù)地址配置化

隨著 Seata 的集群部署數(shù)量的增加,微服務(wù)中的Seata 服務(wù)地址配置可能會(huì)越來越臃腫,此時(shí)我們可能希望借助服務(wù)注冊(cè)中心來加載 Seata TC Server 的地址,這個(gè)時(shí)候如何實(shí)現(xiàn)呢?

正如之前我們所介紹的,Seata TC Server 對(duì)主流的注冊(cè)中心也提供了集成,Seata 客戶端可以通過注冊(cè)中心獲取 Seata TC Server 所在的服務(wù)實(shí)例。

引入注冊(cè)中心之后,Seata 的交互流程可以用如下圖來概括。

下面我們以服務(wù)注冊(cè)中心 Nacos 為例,簡(jiǎn)單的介紹一下它的配置方式。

3.1、Seata 服務(wù)端配置方式

打開 Seata 安裝包中conf/application.example.yml文件,找到store.registry相關(guān)配置屬性。

將其復(fù)制出來,然后拷貝到conf/application.yml文件中。

最后,重啟 Seata TC Server 服務(wù)即可。

訪問 nacos 的服務(wù)控制臺(tái),如果看到 Seata 服務(wù),說明服務(wù)注冊(cè)成功了。

3.2、Seata 客戶端端配置方式

以seata-dubbo-order服務(wù)為例,修改application.properties配置文件中seata相關(guān)的配置項(xiàng),示例如下:

# Seata 應(yīng)用編號(hào),默認(rèn)為spring.application.name
seata.application-id=seata-dubbo-stock
# Seata 事務(wù)組編號(hào),用于 TC 集群名
seata.tx-service-group=my_test_tx_group
# Seata 服務(wù)配置項(xiàng),配置對(duì)應(yīng)的虛擬組和分組的映射,此處必須填寫default
seata.service.vgroup-mapping.my_test_tx_group=default
# 設(shè)置 seata 注冊(cè)中心類型為nacos,默認(rèn)為 file
seata.registry.type=nacos
# 設(shè)置 seata 服務(wù)端中配置 nacos 相關(guān)信息
seata.registry.nacos.applicatinotallow=seata-server
seata.registry.nacos.server-addr=127.0.0.1:8848
seata.registry.nacos.group=SEATA_GROUP

此處的調(diào)整主要是增加 seata 的注冊(cè)中心配置,客戶端通過配置的注冊(cè)中心來獲取 Seata TC Server 服務(wù)實(shí)例地址。

最后將相關(guān)的服務(wù)進(jìn)行重啟,再次在瀏覽器中訪問http://127.0.0.1:9001/order/create?userId=張三&productCode=wahaha&orderCount=1。

不出意外的話,數(shù)據(jù)測(cè)試結(jié)果與上文一致。

3.3、錯(cuò)誤排查

如果測(cè)試中遇到類似如下異常信息。

這種情況通常是 seata 客戶端版本與服務(wù)端版本不兼容導(dǎo)致的,可以嘗試升級(jí) seata 客戶端版本,以便與 seata 服務(wù)端進(jìn)行適配。

以本文工程為例,Seata TC Server 服務(wù)端采用的1.5.2版本,而 Seata 客戶端采用的是1.1.0版本,可見兩者版本相差太大,當(dāng)發(fā)起接口請(qǐng)求時(shí)就出現(xiàn)了上文的錯(cuò)誤信息。

通過查閱版本號(hào)適配情況,Seata 客戶端的1.3.0版本可以與 Seata 服務(wù)端進(jìn)行兼容,因此可以直接升級(jí)spring-cloud-alibaba的版本號(hào),示例如下:

<!--原來是 2.2.1.RELEASE版本,將其升級(jí)為 2.2.3.RELEASE-->
<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>

由于spring-cloud-alibaba的2.2.3.RELEASE版本中集成的seata客戶端版本號(hào)為1.3.0,當(dāng)重啟服務(wù)再次發(fā)起接口請(qǐng)求時(shí),一切恢復(fù)正常。

因此當(dāng)代碼和配置都沒有問題時(shí),服務(wù)無法啟動(dòng)或者運(yùn)行錯(cuò)誤,通常情況與版本號(hào)有很大的關(guān)系??梢詸z查一下工程中的版本號(hào)與官方要求的版本號(hào)是否出現(xiàn)不兼容現(xiàn)象。

四、小結(jié)

最后總結(jié)一下,本文主要圍繞 dubbo 整合 seata 實(shí)現(xiàn)服務(wù)分布式事務(wù)操作做了一次知識(shí)內(nèi)容的總結(jié)和整理,內(nèi)容比較多,如果有描述不對(duì)的地方,歡迎大家留言指出。

如果當(dāng)前的服務(wù)工程采用的是 openFeign 來實(shí)現(xiàn)服務(wù)遠(yuǎn)程調(diào)用,也可以通過集成spring-cloud-starter-alibaba-seata依賴包實(shí)現(xiàn)分布式事務(wù)操作,其實(shí)現(xiàn)原理也是在遠(yuǎn)程調(diào)用的請(qǐng)求頭部中插入全局事務(wù) ID,依次傳遞到下游服務(wù)中,從而保證全局事務(wù)的統(tǒng)一提交和回滾操作。

五、參考

1、https://seata.apache.org/zh-cn/docs/overview/what-is-seata/

2、https://www.iocoder.cn/Seata/install/

3、https://www.iocoder.cn/Spring-Boot/Seata/

責(zé)任編輯:武曉燕 來源: 潘志的技術(shù)筆記
相關(guān)推薦

2024-06-04 10:58:30

2022-06-27 08:21:05

Seata分布式事務(wù)微服務(wù)

2022-06-21 08:27:22

Seata分布式事務(wù)

2023-07-25 09:52:00

本地事務(wù)宕機(jī)

2024-05-11 07:37:43

數(shù)據(jù)Redis策略

2022-03-24 07:51:27

seata分布式事務(wù)Java

2017-06-20 09:42:52

網(wǎng)絡(luò)安全法數(shù)據(jù)隱私法網(wǎng)絡(luò)安全

2024-08-19 09:05:00

Seata分布式事務(wù)

2022-07-10 20:24:48

Seata分布式事務(wù)

2022-06-27 08:36:27

分布式事務(wù)XA規(guī)范

2025-05-09 01:04:00

2021-08-06 08:33:27

Springboot分布式Seata

2021-06-16 08:33:02

分布式事務(wù)ACID

2025-04-03 09:51:37

2021-11-22 16:30:30

分布式一致性分布式系統(tǒng)

2018-07-15 08:18:44

緩存數(shù)據(jù)庫數(shù)據(jù)

2021-04-23 08:15:51

Seata XA AT

2024-10-09 14:14:07

2025-04-30 10:44:02

2023-11-06 13:15:32

分布式事務(wù)Seata
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)