SpringBoot+Dubbo+Seata分布式事務(wù)實戰(zhàn)

前言

Seata 是 阿里巴巴開源的分布式事務(wù)中間件,以高效并且對業(yè)務(wù)0侵入的方式,解決微服務(wù)場景下面臨的分布式事務(wù)問題。

事實上,官方在GitHub已經(jīng)給出了多種環(huán)境下的Seata應(yīng)用示例項目,地址:https://github.com/seata/seata-samples。

為什么筆者要重新寫一遍呢,主要原因有兩點:

  • 官網(wǎng)代碼示例中,依賴太多,分不清哪些有什么作用
  • Seata相關(guān)資料較少,筆者在搭建的過程中,遇到了一些坑,記錄一下

一、環(huán)境準(zhǔn)備

本文涉及軟件環(huán)境如下:

  • SpringBoot 2.1.6.RELEASE
  • Dubbo 2.7.1
  • Mybatis 3.5.1
  • Seata 0.6.1
  • Zookeeper 3.4.10

1、業(yè)務(wù)場景

為了簡化流程,我們只需要訂單和庫存兩個服務(wù)。創(chuàng)建訂單的時候,調(diào)用庫存服務(wù),扣減庫存。

涉及的表設(shè)計如下:

CREATE TABLE `t_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_no` varchar(255) DEFAULT NULL,
  `user_id` varchar(255) DEFAULT NULL,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT '0',
  `amount` double(14,2) DEFAULT '0.00',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8;

CREATE TABLE `t_storage` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `commodity_code` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

另外還需要一個回滾日志表:

CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  `context` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8;

2、Seata下載安裝

打開https://github.com/seata/seata/releases,目前最新版本是v0.6.1

下載解壓后,到seata-server-0.6.1\distribution\bin目錄下可以看到seata-server.bat和seata-server.sh,選擇一個雙擊執(zhí)行。

不出意外的話,當(dāng)你看到-Server started ...等字樣,就正常啟動了。

3、Maven依賴

由于是Dubbo項目,我們先引入Dubbo相關(guān)依賴。

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.7.1</version>
</dependency>
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>2.7.1</version>
</dependency>

Dubbo的服務(wù)要注冊到Zookeeper,引入curator客戶端。

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.13.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.13.0</version>
</dependency>

最后,引入Seata。

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId>
    <version>0.6.1</version>
</dependency>

當(dāng)然了,還有其他的如Mybatis、mysql-connector等就不粘了,自行引入即可。

二、項目配置

1、application.properties

這里只需要配置數(shù)據(jù)庫連接信息和Dubbo相關(guān)信息即可。

server.port=8011

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/seata
spring.datasource.username=root
spring.datasource.password=root

dubbo.application.name=order-service
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20881
dubbo.consumer.timeout=9999999
dubbo.consumer.check=false

2、數(shù)據(jù)源

Seata 是通過代理數(shù)據(jù)源實現(xiàn)事務(wù)分支,所以需要先配置一個數(shù)據(jù)源的代理,否則事務(wù)不會回滾。

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

注意,這里的DataSourceProxy類位于io.seata.rm.datasource包內(nèi)。

3、Seata配置

還需要配置全局事務(wù)掃描器。有兩個參數(shù),一個是應(yīng)用名稱,一個是事務(wù)分組。

@Bean
public GlobalTransactionScanner globalTransactionScanner() {
    return new GlobalTransactionScanner("springboot-order", "my_test_tx_group");
}

事實上,關(guān)于Seata事務(wù)的一系列初始化工作都在這里完成。

4、配置注冊中心

Seata連接到服務(wù)器的時候需要一些配置項,這時候有一個registry.conf文件可以指定注冊中心和配置文件是什么。

這里有很多可選性,比如file、nacos 、apollo、zk、consul。

后面4個都是業(yè)界成熟的配置注冊中心產(chǎn)品,為啥還有個file呢?

官方的初衷是在不依賴第三方配置注冊中心的基礎(chǔ)上快速集成測試seata功能,但是file類型本身不具備注冊中心的動態(tài)發(fā)現(xiàn)和動態(tài)配置功能。

registry.conf文件內(nèi)容如下:

registry {
  type = "file"
  file {
    name = "file.conf"
  }
}
config {
  # file、nacos 、apollo、zk、consul
  type = "file"
  file {
    name = "file.conf"
  }
}

如果你選擇了file類型,通過name屬性指定了file.conf,這個文件中指定了客戶端或服務(wù)器的配置信息。比如傳輸協(xié)議、服務(wù)器地址等。

service {
  #vgroup->rgroup
  vgroup_mapping.my_test_tx_group = "default"
  #only support single node
  default.grouplist = "127.0.0.1:8091"
  #degrade current not support
  enableDegrade = false
  #disable
  disable = false
}

三、業(yè)務(wù)代碼

1、庫存服務(wù)

在庫存服務(wù)中,拿到商品編碼和購買總個數(shù),扣減即可。

<update id="decreaseStorage">
    update t_storage set count = count-${count} where commodity_code = #{commodityCode}
</update>

然后用Dubbo將庫存服務(wù)扣減接口暴露出去。

2、訂單服務(wù)

在訂單服務(wù)中,先扣減庫存,再創(chuàng)建訂單。最后拋出異常,然后去數(shù)據(jù)庫檢查事務(wù)是否回滾。

@GlobalTransactional
public void createOrder(OrderDTO orderDTO) {

    System.out.println("開始全局事務(wù)。XID="+RootContext.getXID());
    StorageDTO storageDTO = new StorageDTO();
    storageDTO.setCount(orderDTO.getCount());
    storageDTO.setCommodityCode(orderDTO.getCommodityCode());
    
    //1、扣減庫存
    storageDubboService.decreaseStorage(storageDTO);
    
    //2、創(chuàng)建訂單
    orderDTO.setId(order_id.incrementAndGet());
    orderDTO.setOrderNo(UUID.randomUUID().toString());
    Order order = new Order();
    BeanUtils.copyProperties(orderDTO,order);
    orderMapper.createOrder(order);

    throw new RuntimeException("分布式事務(wù)異常..."+orderDTO.getOrderNo());
}

值得注意的是,在訂單服務(wù)事務(wù)開始的方法上,需要標(biāo)注@GlobalTransactional。另外,在庫存服務(wù)的方法里,不需要此注解,事務(wù)會通過Dubbo進(jìn)行傳播。

四、注意事項

1、數(shù)據(jù)源

請切記,Seata 是通過代理數(shù)據(jù)源實現(xiàn)事務(wù)分支,一定不要忘記配置數(shù)據(jù)源代理。

2、主鍵自增

在數(shù)據(jù)庫中,表里的主鍵ID字段都是自增的。如果你的字段不是自增的,那么在Mybatis的insert SQL中,要將列名寫完整。

比如我們可以這樣寫SQL:

INSERT INTO table_name VALUES (值1, 值2,....)

那么這時候就要寫成:

INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)

3、序列化問題

在訂單表中,amount字段類型為double。在seata0.6.1版本中,默認(rèn)的序列化方式為fastjson,但它會將這個字段序列化成bigdecimal類型,會導(dǎo)致后面類型不匹配。

但是在后續(xù)的seata0.7.0版本中(還未發(fā)布),已經(jīng)將默認(rèn)的序列化方式改為了jackson。

不過無需擔(dān)心,這個問題一般不會出現(xiàn)。筆者是因為引錯了一個包,才導(dǎo)致發(fā)現(xiàn)這問題。

4、本文代碼

本文示例代碼在:https://github.com/taoxun/springboot-dubbo-zookeeper-seata。

5、其他

歡迎有問題及時交流~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在本篇文章中我們在SpringCloud環(huán)境下通過使用Seata來模擬用戶購買商品時由于用戶余額不足導(dǎo)致本次訂單提...
    恒宇少年閱讀 28,289評論 5 53
  • 分布式事務(wù)的問題,在微服務(wù)架構(gòu)中一直是難題。單體應(yīng)用實現(xiàn)本地事務(wù)即可,到了分布式環(huán)境,情況就變得復(fù)雜。一個請求可能...
    aoho閱讀 2,397評論 0 2
  • 最近蔡康永的一段話很火,不知道你們看了沒。 “所謂的情商高,不是迎合別人,而是關(guān)注自己”。 看到這句話的時候我心里...
    邏輯世界里的瘋子閱讀 336評論 1 1
  • 前段時間《延禧攻略》大火,拖了這么久今天我終于看完了,最后一集傅恒死后拖海蘭察給魏姐帶話,“這輩子我守夠...
    陳行之閱讀 218評論 0 0
  • 基因剪刀,咔嚓咔嚓 剪出兩個女孩兒,露露,娜娜 基因剪刀,咔嘰咔嘰 所有科幻結(jié)局,源頭開啟 手握基因剪刀,仿佛上帝...
    夢里以西閱讀 501評論 6 20

友情鏈接更多精彩內(nèi)容