1.簡介
本文主要介紹SpringBoot2.1.5 + Dubbo 2.7.3 + Mybatis 3.4.2 + Nacos 1.1.3 +Seata 0.8.0整合來實現Dubbo分佈式事務管理,使用Nacos 作爲 Dubbo和Seata的註冊中心和配置中心,使用 MySQL 數據庫和 MyBatis來操作數據。
如果你還對SpringBoot
、Dubbo
、Nacos
、Seata
、Mybatis
不是很瞭解的話,這裏我爲大家整理個它們的官網網站,如下
-
SpringBoot:https://spring.io/projects/spring-boot
在這裏我們就不一個一個介紹它們是怎麼使用和原理,詳細請學習官方文檔,在這裏我將開始對它們進行整合,完成一個簡單的案例,來讓大家瞭解Seata
來實現Dubbo
分佈式事務管理的基本流程。
2.環境準備
2.1 下載nacos並安裝啓動
nacos下載:https://github.com/alibaba/nacos/releases/tag/1.1.3
Nacos 快速入門:https://nacos.io/en-us/docs/quick-start.html
sh startup.sh -m standalone
在瀏覽器打開Nacos web 控制檯:http://192.168.10.200:8848/nacos/index.html
輸入nacos的賬號和密碼 分別爲nacos:nacos
這是後naocs 就正常啓動了。
2.2 下載seata0.8.0 並安裝啓動
2.2.1 在 Seata Release 下載最新版的 Seata Server 並解壓得到如下目錄:
.
├──bin
├──conf
├──file_store
└──lib
2.2.2 修改 conf/registry.conf 配置,
目前seata支持如下的file、nacos 、apollo、zk、consul的註冊中心和配置中心。這裏我們以nacos
爲例。
將 type 改爲 nacos
registry {
# file nacos
type = "nacos"
nacos {
serverAddr = "192.168.10.200"
namespace = "public"
cluster = "default"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul
type = "nacos"
nacos {
serverAddr = "192.168.10.200"
namespace = "public"
cluster = "default"
}
file {
name = "file.conf"
}
}
- serverAddr = “192.168.10.200” :nacos 的地址
- namespace = “public” :nacos的命名空間默認爲
public
- cluster = “default” :集羣設置未默認
default
2.2.3 修改 conf/nacos-config.txt配置
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.thread-factory.boss-thread-prefix=NettyBoss
transport.thread-factory.worker-thread-prefix=NettyServerNIOWorker
transport.thread-factory.server-executor-thread-prefix=NettyServerBizHandler
transport.thread-factory.share-boss-worker=false
transport.thread-factory.client-selector-thread-prefix=NettyClientSelector
transport.thread-factory.client-selector-thread-size=1
transport.thread-factory.client-worker-thread-prefix=NettyClientWorkerThread
transport.thread-factory.boss-thread-size=1
transport.thread-factory.worker-thread-size=8
transport.shutdown.wait=3
service.vgroup_mapping.order-service-seata-service-group=default
service.vgroup_mapping.account-service-seata-service-group=default
service.vgroup_mapping.storage-service-seata-service-group=default
service.vgroup_mapping.business-service-seata-service-group=default
service.enableDegrade=false
service.disable=false
service.max.commit.retry.timeout=-1
service.max.rollback.retry.timeout=-1
client.async.commit.buffer.limit=10000
client.lock.retry.internal=10
client.lock.retry.times=30
store.mode=db
store.file.dir=file_store/data
store.file.max-branch-session-size=16384
store.file.max-global-session-size=512
store.file.file-write-buffer-cache-size=16384
store.file.flush-disk-mode=async
store.file.session.reload.read_size=100
store.db.driver-class-name=com.mysql.jdbc.Driver
store.db.datasource=dbcp
store.db.db-type=mysql
store.db.url=jdbc:mysql://192.168.10.200:3306/seata?useUnicode=true
store.db.user=lidong
store.db.password=cwj887766@@
store.db.min-conn=1
store.db.max-conn=3
store.db.global.table=global_table
store.db.branch.table=branch_table
store.db.query-limit=100
store.db.lock-table=lock_table
recovery.committing-retry-period=1000
recovery.asyn-committing-retry-period=1000
recovery.rollbacking-retry-period=1000
recovery.timeout-retry-period=1000
transaction.undo.data.validation=true
transaction.undo.log.serialization=jackson
transaction.undo.log.save.days=7
transaction.undo.log.delete.period=86400000
transaction.undo.log.table=undo_log
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registry-type=compact
metrics.exporter-list=prometheus
metrics.exporter-prometheus-port=9898
這裏主要修改瞭如下幾項:
- store.mode :存儲模式 默認file 這裏我修改爲db 模式 ,並且需要兩個表
global_table
和branch_table
- store.db.driver-class-name: 默認沒有,會報錯。添加了
com.mysql.jdbc.Driver
- store.db.datasource=dbcp :數據源 dbcp
- store.db.db-type=mysql : 存儲數據庫的類型爲
mysql
- store.db.url=jdbc:mysql://192.168.10.200:3306/seata?useUnicode=true : 修改爲自己的數據庫
url
、port
、數據庫名稱
- store.db.user=lidong :數據庫的賬號
- store.db.password=cwj887766@@ :數據庫的密碼
- service.vgroup_mapping.order-service-seata-service-group=default
- service.vgroup_mapping.account-service-seata-service-group=default
- service.vgroup_mapping.storage-service-seata-service-group=default
- service.vgroup_mapping.business-service-seata-service-group=default
也可以在 Nacos 配置頁面添加,data-id 爲 service.vgroup_mapping.${YOUR_SERVICE_NAME}-seata-service-group, group 爲 SEATA_GROUP, 如果不添加該配置,啓動後會提示no available server to connect
注意: 配置文件末尾有空行,需要刪除,否則會提示失敗,儘管實際上是成功的
global_table
的表結構
CREATE TABLE `global_table` (
`xid` varchar(128) NOT NULL,
`transaction_id` bigint(20) DEFAULT NULL,
`status` tinyint(4) NOT NULL,
`application_id` varchar(64) DEFAULT NULL,
`transaction_service_group` varchar(64) DEFAULT NULL,
`transaction_name` varchar(64) DEFAULT NULL,
`timeout` int(11) DEFAULT NULL,
`begin_time` bigint(20) DEFAULT NULL,
`application_data` varchar(2000) DEFAULT NULL,
`gmt_create` datetime DEFAULT NULL,
`gmt_modified` datetime DEFAULT NULL,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
branch_table
的表結構
CREATE TABLE `branch_table` (
`branch_id` bigint(20) NOT NULL,
`xid` varchar(128) NOT NULL,
`transaction_id` bigint(20) DEFAULT NULL,
`resource_group_id` varchar(32) DEFAULT NULL,
`resource_id` varchar(256) DEFAULT NULL,
`lock_key` varchar(128) DEFAULT NULL,
`branch_type` varchar(8) DEFAULT NULL,
`status` tinyint(4) DEFAULT NULL,
`client_id` varchar(64) DEFAULT NULL,
`application_data` varchar(2000) DEFAULT NULL,
`gmt_create` datetime DEFAULT NULL,
`gmt_modified` datetime DEFAULT NULL,
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2.4 將 Seata 配置添加到 Nacos 中
使用命令如下
cd conf
sh nacos-config.sh localhost
成功後會提示
init nacos config finished, please start seata-server
在 Nacos 管理頁面應該可以看到有 59 個 Group 爲SEATA_GROUP的配置
2.2.5 啓動 Seata Server
使用db 模式啓動
cd ..
sh ./bin/seata-server.sh
這時候在 Nacos 的服務列表下面可以看到一個名爲serverAddr的服務
3 案例分析
參考官網中用戶購買商品的業務邏輯。整個業務邏輯由4個微服務提供支持:
- 庫存服務:扣除給定商品的存儲數量。
- 訂單服務:根據購買請求創建訂單。
- 帳戶服務:借記用戶帳戶的餘額。
- 業務服務:處理業務邏輯。
請求邏輯架構
3.1 github地址
springboot-dubbo-seata:https://github.com/lidong1665/springboot-dubbo-seata
-
samples-common :公共模塊
-
samples-account :用戶賬號模塊
-
samples-order :訂單模塊
-
samples-storage :庫存模塊
-
samples-business :業務模塊
3.2 賬戶服務:AccountDubboService
/**
* @Author: lidong
* @Description 賬戶服務接口
* @Date Created in 2019/9/5 16:37
*/
public interface AccountDubboService {
/**
* 從賬戶扣錢
*/
ObjectResponse decreaseAccount(AccountDTO accountDTO);
}
3.3 訂單服務:OrderDubboService
/**
* @Author: lidong
* @Description 訂單服務接口
* @Date Created in 2019/9/5 16:28
*/
public interface OrderDubboService {
/**
* 創建訂單
*/
ObjectResponse<OrderDTO> createOrder(OrderDTO orderDTO);
}
3.4 庫存服務:StorageDubboService
/**
* @Author: lidong
* @Description 庫存服務
* @Date Created in 2019/9/5 16:22
*/
public interface StorageDubboService {
/**
* 扣減庫存
*/
ObjectResponse decreaseStorage(CommodityDTO commodityDTO);
}
3.5 業務服務:BusinessService
/**
* @Author: lidong
* @Description
* @Date Created in 2019/9/5 17:17
*/
public interface BusinessService {
/**
* 出處理業務服務
* @param businessDTO
* @return
*/
ObjectResponse handleBusiness(BusinessDTO businessDTO);
}
業務邏輯的具體實現主要體現在 訂單服務的實現和業務服務的實現
訂單服務的實現
@Service
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements ITOrderService {
@Reference(version = "1.0.0")
private AccountDubboService accountDubboService;
/**
* 創建訂單
* @Param: OrderDTO 訂單對象
* @Return: OrderDTO 訂單對象
*/
@Override
public ObjectResponse<OrderDTO> createOrder(OrderDTO orderDTO) {
ObjectResponse<OrderDTO> response = new ObjectResponse<>();
//扣減用戶賬戶
AccountDTO accountDTO = new AccountDTO();
accountDTO.setUserId(orderDTO.getUserId());
accountDTO.setAmount(orderDTO.getOrderAmount());
ObjectResponse objectResponse = accountDubboService.decreaseAccount(accountDTO);
//生成訂單號
orderDTO.setOrderNo(UUID.randomUUID().toString().replace("-",""));
//生成訂單
TOrder tOrder = new TOrder();
BeanUtils.copyProperties(orderDTO,tOrder);
tOrder.setCount(orderDTO.getOrderCount());
tOrder.setAmount(orderDTO.getOrderAmount().doubleValue());
try {
baseMapper.createOrder(tOrder);
} catch (Exception e) {
response.setStatus(RspStatusEnum.FAIL.getCode());
response.setMessage(RspStatusEnum.FAIL.getMessage());
return response;
}
if (objectResponse.getStatus() != 200) {
response.setStatus(RspStatusEnum.FAIL.getCode());
response.setMessage(RspStatusEnum.FAIL.getMessage());
return response;
}
response.setStatus(RspStatusEnum.SUCCESS.getCode());
response.setMessage(RspStatusEnum.SUCCESS.getMessage());
return response;
}
}
整個業務的實現邏輯
@Service
@Slf4j
public class BusinessServiceImpl implements BusinessService{
@Reference(version = "1.0.0")
private StorageDubboService storageDubboService;
@Reference(version = "1.0.0")
private OrderDubboService orderDubboService;
private boolean flag;
/**
* 處理業務邏輯
* @Param:
* @Return:
*/
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-gts-seata-example")
@Override
public ObjectResponse handleBusiness(BusinessDTO businessDTO) {
log.info("開始全局事務,XID = " + RootContext.getXID());
ObjectResponse<Object> objectResponse = new ObjectResponse<>();
//1、扣減庫存
CommodityDTO commodityDTO = new CommodityDTO();
commodityDTO.setCommodityCode(businessDTO.getCommodityCode());
commodityDTO.setCount(businessDTO.getCount());
ObjectResponse storageResponse = storageDubboService.decreaseStorage(commodityDTO);
//2、創建訂單
OrderDTO orderDTO = new OrderDTO();
orderDTO.setUserId(businessDTO.getUserId());
orderDTO.setCommodityCode(businessDTO.getCommodityCode());
orderDTO.setOrderCount(businessDTO.getCount());
orderDTO.setOrderAmount(businessDTO.getAmount());
ObjectResponse<OrderDTO> response = orderDubboService.createOrder(orderDTO);
//打開註釋測試事務發生異常後,全局回滾功能
// if (!flag) {
// throw new RuntimeException("測試拋異常後,分佈式事務回滾!");
// }
if (storageResponse.getStatus() != 200 || response.getStatus() != 200) {
throw new DefaultException(RspStatusEnum.FAIL);
}
objectResponse.setStatus(RspStatusEnum.SUCCESS.getCode());
objectResponse.setMessage(RspStatusEnum.SUCCESS.getMessage());
objectResponse.setData(response.getData());
return objectResponse;
}
}
3.6 使用seata的分佈式事務解決方案處理dubbo的分佈式事務
我們只需要在業務處理的方法handleBusiness
添加一個註解 @GlobalTransactional
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-gts-seata-example")
@Override
public ObjectResponse handleBusiness(BusinessDTO businessDTO) {
}
timeoutMills
: 超時時間name
:事務名稱
3.7 準備數據庫
注意: MySQL必須使用InnoDB engine
.
創建數據庫 並導入數據庫腳本
DROP DATABASE IF EXISTS seata;
CREATE DATABASE seata;
USE seata;
DROP TABLE IF EXISTS `t_account`;
CREATE TABLE `t_account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`amount` double(14,2) DEFAULT '0.00',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_account
-- ----------------------------
INSERT INTO `t_account` VALUES ('1', '1', '4000.00');
-- ----------------------------
-- Table structure for t_order
-- ----------------------------
DROP TABLE IF EXISTS `t_order`;
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=64 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_order
-- ----------------------------
-- ----------------------------
-- Table structure for t_storage
-- ----------------------------
DROP TABLE IF EXISTS `t_storage`;
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;
-- ----------------------------
-- Records of t_storage
-- ----------------------------
INSERT INTO `t_storage` VALUES ('1', 'C201901140001', '水杯', '1000');
-- ----------------------------
-- Table structure for undo_log
-- 注意此處0.3.0+ 增加唯一索引 ux_undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) 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,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of undo_log
-- ----------------------------
SET FOREIGN_KEY_CHECKS=1;
會看到如上的4個表
+-------------------------+
| Tables_in_seata |
+-------------------------+
| t_account |
| t_order |
| t_storage |
| undo_log |
+-------------------------+
這裏爲了簡化我將這個三張表創建到一個庫中,使用是三個數據源來實現。
3.8 我們以賬號服務samples-account
爲例 ,分析需要注意的配置項目
3.8.1 引入的依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<artifactId>springboot-dubbo-seata</artifactId>
<packaging>pom</packaging>
<name>springboot-dubbo-seata</name>
<groupId>io.seata</groupId>
<version>1.0.0</version>
<description>samples-account</description》
<properties>
<springboot.verison>2.1.5.RELEASE</springboot.verison>
<java.version>1.8</java.version>
<druid.version>1.1.10</druid.version>
<mybatis.version>1.3.2</mybatis.version>
<mybatis-plus.version>2.3</mybatis-plus.version>
<nacos.version>0.2.3</nacos.version>
<lombok.version>1.16.22</lombok.version>
<dubbo.version>2.7.3</dubbo.version>
<nacos-client.verison>1.1.3</nacos-client.verison>
<seata.version>0.8.0</seata.version>
<netty.version>4.1.32.Final</netty.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${springboot.verison}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${springboot.verison}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${springboot.verison}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<artifactId>spring</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-config-spring -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-config-spring</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.seata/seata-all -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>${seata.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>${nacos-client.verison}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.boot/nacos-config-spring-boot-starter -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
<version>${nacos.version}</version>
<exclusions>
<exclusion>
<artifactId>nacos-client</artifactId>
<groupId>com.alibaba.nacos</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${springboot.verison}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
注意:
seata-all
: 這個是seata 所需的主要依賴dubbo-spring-boot-starter
: springboot dubbo的依賴
其他的就不一一介紹,其他的一目瞭然,就知道是幹什麼的。
3.8.2 application.properties配置
server.port=8102
spring.application.name=dubbo-account-example
#====================================Dubbo配置===============================================
dubbo.application.id= dubbo-account-example
dubbo.application.name= dubbo-account-example
dubbo.protocol.id=dubbo
dubbo.protocol.name=dubbo
dubbo.registry.id=dubbo-account-example-registry
dubbo.registry.address=nacos://192.168.10.200:8848
dubbo.protocol.port=20880
dubbo.application.qosEnable=false
dubbo.config-center.address=nacos://192.168.10.200:8848
dubbo.metadata-report.address=nacos://192.168.10.200:8848
#====================================mysql 配置============================================
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.10.200:3306/seata?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
spring.datasource.username=lidong
spring.datasource.password=cwj887766@@
#=====================================mybatis 配置======================================
mybatis.mapper-locations=classpath*:/mapper/*.xml
3.8.3 registry.conf 配置 (naocs的配置)
registry {
# file nacos
type = "nacos"
nacos {
serverAddr = "192.168.10.200"
namespace = "public"
cluster = "default"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul
type = "nacos"
file {
name = "file.conf"
}
nacos {
serverAddr = "192.168.10.200"
namespace = "public"
cluster = "default"
}
}
3.8.5 SeataAutoConfig 配置
package io.seata.samples.integration.account.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import io.seata.spring.annotation.GlobalTransactionScanner;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
/**
* @Author: llidong
* @Description seata global configuration
* @Date Created in 2019/9/05 10:28
*/
@Configuration
public class SeataAutoConfig {
/**
* autowired datasource config
*/
@Autowired
private DataSourceProperties dataSourceProperties;
/**
* init durid datasource
*
* @Return: druidDataSource datasource instance
*/
@Bean
@Primary
public DruidDataSource druidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(dataSourceProperties.getUrl());
druidDataSource.setUsername(dataSourceProperties.getUsername());
druidDataSource.setPassword(dataSourceProperties.getPassword());
druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
druidDataSource.setInitialSize(0);
druidDataSource.setMaxActive(180);
druidDataSource.setMaxWait(60000);
druidDataSource.setMinIdle(0);
druidDataSource.setValidationQuery("Select 1 from DUAL");
druidDataSource.setTestOnBorrow(false);
druidDataSource.setTestOnReturn(false);
druidDataSource.setTestWhileIdle(true);
druidDataSource.setTimeBetweenEvictionRunsMillis(60000);
druidDataSource.setMinEvictableIdleTimeMillis(25200000);
druidDataSource.setRemoveAbandoned(true);
druidDataSource.setRemoveAbandonedTimeout(1800);
druidDataSource.setLogAbandoned(true);
return druidDataSource;
}
/**
* init datasource proxy
* @Param: druidDataSource datasource bean instance
* @Return: DataSourceProxy datasource proxy
*/
@Bean
public DataSourceProxy dataSourceProxy(DruidDataSource druidDataSource){
return new DataSourceProxy(druidDataSource);
}
/**
* init mybatis sqlSessionFactory
* @Param: dataSourceProxy datasource proxy
* @Return: DataSourceProxy datasource proxy
*/
@Bean
public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSourceProxy);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:/mapper/*.xml"));
return factoryBean.getObject();
}
/**
* init global transaction scanner
*
* @Return: GlobalTransactionScanner
*/
@Bean
public GlobalTransactionScanner globalTransactionScanner(){
return new GlobalTransactionScanner("account-gts-seata-example", "account-service-seata-service-group");
}
}
其中:
@Bean
public GlobalTransactionScanner globalTransactionScanner(){
return new GlobalTransactionScanner("account-gts-seata-example", "account-service-seata-service-group");
}
GlobalTransactionScanner
: 初始化全局的事務掃描器
/**
* Instantiates a new Global transaction scanner.
*
* @param applicationId the application id
* @param txServiceGroup the default server group
*/
public GlobalTransactionScanner(String applicationId, String txServiceGroup) {
this(applicationId, txServiceGroup, DEFAULT_MODE);
}
- applicationId :爲應用id 這裏我傳入的是
account-gts-seata-example
- txServiceGroup: 默認server的分組 這裏我傳入的是
account-service-seata-service-group
這個和我們前面在nacos 配置的是保存一致。 - DEFAULT_MODE:默認的事務模式 爲AT_MODE + MT_MODE
3.8.6 AccountExampleApplication 啓動類的配置
package io.seata.samples.integration.account;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
@SpringBootApplication(scanBasePackages = "io.seata.samples.integration.account")
@MapperScan({"io.seata.samples.integration.account.mapper"})
@EnableDubbo(scanBasePackages = "io.seata.samples.integration.account")
public class AccountExampleApplication {
public static void main(String[] args) {
SpringApplication.run(AccountExampleApplication.class, args);
}
}
-
@EnableDubbo
等同於@DubboComponentScan
和@EnableDubboConfig
組合 -
@DubboComponentScan
掃描 classpaths 下類中添加了@Service
和@Reference
將自動注入到spring beans中。 -
@EnableDubboConfig 掃描的dubbo的外部化配置。
4 啓動所有的sample模塊
啓動 samples-account
、samples-order
、samples-storage
、samples-business
並且在nocos的控制檯查看註冊情況: http://192.168.10.200:8848/nacos/#/serviceManagement
我們可以看到上面的服務都已經註冊成功。
5 測試
5. 1 發送一個下單請求
使用postman 發送 :http://localhost:8104/business/dubbo/buy
參數:
{
"userId":"1",
"commodityCode":"C201901140001",
"name":"fan",
"count":50,
"amount":"100"
}
返回
{
"status": 200,
"message": "成功",
"data": null
}
這時候控制檯:
2019-09-05 12:17:34.097 INFO 21860 --- [nio-8104-exec-4] i.s.s.i.c.controller.BusinessController : 請求參數:BusinessDTO(userId=1, commodityCode=C201901140001, name=fan, count=50, amount=100)
2019-09-05 12:17:34.146 INFO 21860 --- [nio-8104-exec-4] i.seata.tm.api.DefaultGlobalTransaction : Begin new global transaction [192.168.10.200:8091:2021380638]
2019-09-05 12:17:34.150 INFO 21860 --- [nio-8104-exec-4] i.s.s.i.c.service.BusinessServiceImpl : 開始全局事務,XID = 192.168.10.200:8091:2021380638
2019-09-05 12:17:36.966 INFO 21860 --- [nio-8104-exec-4] i.seata.tm.api.DefaultGlobalTransaction : [192.168.10.200:8091:2021380638] commit status:Committed
事務提交成功,
我們來看一下數據庫數據變化
t_account
t_order
t_storage
數據沒有問題。
5.2 測試回滾
我們samples-business
將BusinessServiceImpl
的handleBusiness2
下面的代碼去掉註釋
if (!flag) {
throw new RuntimeException("測試拋異常後,分佈式事務回滾!");
}
使用postman 發送 :http://localhost:8104/business/dubbo/buy2
.響應結果:
{
"timestamp": "2019-09-05T04:29:34.178+0000",
"status": 500,
"error": "Internal Server Error",
"message": "測試拋異常後,分佈式事務回滾!",
"path": "/business/dubbo/buy"
}
控制檯
2019-09-05 12:29:32.836 INFO 17264 --- [nio-8104-exec-2] i.s.s.i.c.controller.BusinessController : 請求參數:BusinessDTO(userId=1, commodityCode=C201901140001, name=fan, count=50, amount=100)
2019-09-05 12:29:32.843 INFO 17264 --- [nio-8104-exec-2] i.s.common.loader.EnhancedServiceLoader : load ContextCore[null] extension by class[io.seata.core.context.ThreadLocalContextCore]
2019-09-05 12:29:32.848 INFO 17264 --- [nio-8104-exec-2] i.s.common.loader.EnhancedServiceLoader : load TransactionManager[null] extension by class[io.seata.tm.DefaultTransactionManager]
2019-09-05 12:29:32.849 INFO 17264 --- [nio-8104-exec-2] io.seata.tm.TransactionManagerHolder : TransactionManager Singleton io.seata.tm.DefaultTransactionManager@461585ac
2019-09-05 12:29:32.859 INFO 17264 --- [nio-8104-exec-2] i.s.common.loader.EnhancedServiceLoader : load LoadBalance[null] extension by class[io.seata.discovery.loadbalance.RandomLoadBalance]
2019-09-05 12:29:32.893 INFO 17264 --- [nio-8104-exec-2] i.seata.tm.api.DefaultGlobalTransaction : Begin new global transaction [192.168.10.200:8091:2021380674]
2019-09-05 12:29:32.897 INFO 17264 --- [nio-8104-exec-2] i.s.s.i.c.service.BusinessServiceImpl : 開始全局事務,XID = 192.168.10.200:8091:2021380674
2019-09-05 12:29:34.143 INFO 17264 --- [nio-8104-exec-2] i.seata.tm.api.DefaultGlobalTransaction : [192.168.10.200:8091:2021380674] rollback status:Rollbacked
2019-09-05 12:29:34.158 ERROR 17264 --- [nio-8104-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: 測試拋異常後,分佈式事務回滾!] with root cause
java.lang.RuntimeException: 測試拋異常後,分佈式事務回滾!
at io.seata.samples.integration.call.service.BusinessServiceImpl.handleBusiness(BusinessServiceImpl.java:60) ~[classes/:na]
at io.seata.samples.integration.call.service.BusinessServiceImpl$$FastClassBySpringCGLIB$$2ab3d645.invoke(<generated>) ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at io.seata.spring.annotation.GlobalTransactionalInterceptor$1.execute(GlobalTransactionalInterceptor.java:104) ~[seata-all-0.8.0.jar:0.8.0]
at io.seata.tm.api.TransactionalTemplate.execute(TransactionalTemplate.java:64) ~[seata-all-0.8.0.jar:0.8.0]
at io.seata.spring.annotation.GlobalTransactionalInterceptor.handleGlobalTransaction(GlobalTransactionalInterceptor.java:101) ~[seata-all-0.8.0.jar:0.8.0]
at io.seata.spring.annotation.GlobalTransactionalInterceptor.invoke(GlobalTransactionalInterceptor.java:76) ~[seata-all-0.8.0.jar:0.8.0]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at io.seata.samples.integration.call.service.BusinessServiceImpl$$EnhancerBySpringCGLIB$$cb43f7ab.handleBusiness(<generated>) ~[classes/:na]
at io.seata.samples.integration.call.controller.BusinessController.handleBusiness(BusinessController.java:37) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_144]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_144]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_144]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_144]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.19.jar:9.0.19]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:836) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1747) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.19.jar:9.0.19]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_144]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_144]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.19.jar:9.0.19]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_144]
我們查看數據庫數據,已經回滾,和上面的數據一致。
到這裏一個簡單的案例基本就分析結束。感謝你的學習。