SpringBoot+Dubbo+Seata分佈式事務實戰

前言

Seata 是 阿里巴巴開源的分佈式事務中間件,以高效而且對業務0侵入的方式,解決微服務場景下面臨的分佈式事務問題。node

事實上,官方在GitHub已經給出了多種環境下的Seata應用示例項目,地址:https://github.com/seata/seata-samplesmysql

爲何筆者要從新寫一遍呢,主要緣由有兩點:git

  • 官網代碼示例中,依賴太多,分不清哪些有什麼做用
  • Seata相關資料較少,筆者在搭建的過程當中,遇到了一些坑,記錄一下

1、環境準備

本文涉及軟件環境以下:github

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

一、業務場景

爲了簡化流程,咱們只須要訂單和庫存兩個服務。建立訂單的時候,調用庫存服務,扣減庫存。spring

涉及的表設計以下:sql

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;
複製代碼

二、Seata下載安裝

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

下載解壓後,到seata-server-0.6.1\distribution\bin目錄下能夠看到seata-server.bat和seata-server.sh,選擇一個雙擊執行。json

不出意外的話,當你看到-Server started ...等字樣,就正常啓動了。springboot

三、Maven依賴

因爲是Dubbo項目,咱們先引入Dubbo相關依賴。

<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的服務要註冊到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>
複製代碼

固然了,還有其餘的如Mybatis、mysql-connector等就不粘了,自行引入便可。

2、項目配置

一、application.properties

這裏只須要配置數據庫鏈接信息和Dubbo相關信息便可。

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
複製代碼

二、數據源

Seata 是經過代理數據源實現事務分支,因此須要先配置一個數據源的代理,不然事務不會回滾。

@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
	return new DataSourceProxy(dataSource);
}
複製代碼

注意,這裏的DataSourceProxy類位於io.seata.rm.datasource包內。

三、Seata配置

還須要配置全局事務掃描器。有兩個參數,一個是應用名稱,一個是事務分組。

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

事實上,關於Seata事務的一系列初始化工做都在這裏完成。

四、配置註冊中心

Seata鏈接到服務器的時候須要一些配置項,這時候有一個registry.conf文件能夠指定註冊中心和配置文件是什麼。

這裏有不少可選性,好比file、nacos 、apollo、zk、consul

後面4個都是業界成熟的配置註冊中心產品,爲啥還有個file呢?

官方的初衷是在不依賴第三方配置註冊中心的基礎上快速集成測試seata功能,可是file類型自己不具有註冊中心的動態發現和動態配置功能。

registry.conf文件內容以下:

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

若是你選擇了file類型,經過name屬性指定了file.conf,這個文件中指定了客戶端或服務器的配置信息。好比傳輸協議、服務器地址等。

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
}
複製代碼

3、業務代碼

一、庫存服務

在庫存服務中,拿到商品編碼和購買總個數,扣減便可。

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

而後用Dubbo將庫存服務扣減接口暴露出去。

二、訂單服務

在訂單服務中,先扣減庫存,再建立訂單。最後拋出異常,而後去數據庫檢查事務是否回滾。

@GlobalTransactional
public void createOrder(OrderDTO orderDTO) {

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

	throw new RuntimeException("分佈式事務異常..."+orderDTO.getOrderNo());
}
複製代碼

值得注意的是,在訂單服務事務開始的方法上,須要標註@GlobalTransactional。另外,在庫存服務的方法裏,不須要此註解,事務會經過Dubbo進行傳播。

4、注意事項

一、數據源

請切記,Seata 是經過代理數據源實現事務分支,必定不要忘記配置數據源代理。

二、主鍵自增

在數據庫中,表裏的主鍵ID字段都是自增的。若是你的字段不是自增的,那麼在Mybatis的insert SQL中,要將列名寫完整。

好比咱們能夠這樣寫SQL:

INSERT INTO table_name VALUES (值1, 值2,....)
複製代碼

那麼這時候就要寫成:

INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)
複製代碼

三、序列化問題

在訂單表中,amount字段類型爲double。在seata0.6.1版本中,默認的序列化方式爲fastjson,但它會將這個字段序列化成bigdecimal類型,會致使後面類型不匹配。

可是在後續的seata0.7.0版本中(還未發佈),已經將默認的序列化方式改成了jackson

不過無需擔憂,這個問題通常不會出現。筆者是由於引錯了一個包,才致使發現這問題。

四、本文代碼

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

五、其餘

歡迎有問題及時交流~

相關文章
相關標籤/搜索