SpringCloud整合Seata實(shí)現(xiàn)分布式事務(wù)通過nacos實(shí)現(xiàn)注冊和配置
環(huán)境:springboot2.3.11.RELEASE + spring cloud Hoxton.SR8 + spring cloud alibaba 2.2.5.RELEASE + seata1.3.0
前提:安裝并啟動了nacos服務(wù)
Seata注冊中心及配置中心說明
配置中心
什么是配置中心?配置中心可以說是一個"大衣柜",內(nèi)部放置著各種配置文件,你可以通過自己所需進(jìn)行獲取配置加載到對應(yīng)的客戶端.比如Seata Client端(TM,RM),Seata Server(TC),會去讀取全局事務(wù)開關(guān),事務(wù)會話存儲模式等信息.
Seata的配置中心與Spring cloud的配置中心區(qū)別是?在廣義上來說,并無區(qū)別,只不過Spring cloud的配置中心僅是作用于它們自身的組件,而Seata的配置中心也是一樣是作用于Seata自身.(注:Spring cloud的配置中心與Seata無關(guān))
注冊中心
什么是注冊中心?注冊中心可以說是微服務(wù)架構(gòu)中的”通訊錄“,它記錄了服務(wù)和服務(wù)地址的映射關(guān)系。在分布式架構(gòu)中,服務(wù)會注冊到這里,當(dāng)服務(wù)需要調(diào)用其它服務(wù)時,就到這里找到服務(wù)的地址,進(jìn)行調(diào)用.比如Seata Client端(TM,RM),發(fā)現(xiàn)Seata Server(TC)集群的地址,彼此通信.
Seata的注冊中心與Dubbo,Spring cloud的注冊中心區(qū)別是?在廣義上來說,并無區(qū)別,只不過Dubbo與Spring cloud的注冊中心僅是作用于它們自身的組件,而Seata的注冊中心也是一樣是作用于Seata自身.(注:Dubbo與Spring cloud的注冊中心與Seata無關(guān))
Seata服務(wù)配置
1、在%SEATA_HOME%目錄下新建config.txt文件
service.vgroupMapping.dt-group=default
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&serverTimezone=GMT%2B8
store.db.user=root
store.db.password=xxxxxx
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
2、執(zhí)行腳本
通過如下地址下載腳本執(zhí)行
在windows系統(tǒng)下可以通過 git bash 來執(zhí)行shell腳本
sh nacos-config.sh -h nacos的ip -p nacos端口 -g nacos配置文件的組 -t 你的namespace號(若是public可省略此選項(xiàng)) -u nacos用戶名 -w nacos密碼
3、數(shù)據(jù)庫初始化腳本
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;
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(96),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
完成以上步驟后查看nacos配置
接下來進(jìn)行項(xiàng)目開發(fā)及配置
項(xiàng)目結(jié)構(gòu)
圖片
兩個子模塊:
dt-account-service 用戶模塊
dt-storage-service 庫存模塊
父工程依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>${seata.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
account帳號模塊
核心配置文件
spring:
cloud:
nacos:
password: nacos
username: nacos
discovery:
server-addr: 118.24.111.33:8848
namespace: ""
group: dt-group
---
seata:
tx-service-group: dt-group
registry:
type: nacos
nacos:
application: seata-server #這里要與seata中的registry.conf配置的application一致
group: dt-group
namespace: ""
server-addr: 118.24.111.33:8848
username: nacos
password: nacos
config:
type: nacos
nacos:
namespace: ""
group: dt-group
server-addr: 118.24.111.33:8848
username: nacos
password: nacos
AccountService服務(wù)類
@Service
public class AccountService {
private static final String ERROR_USER_ID = "1002";
@Resource
private AccountMapper accountMapper ;
@Resource
private StorageFeignClient storageFeignClient;
@Transactional(rollbackFor = Exception.class)
@GlobalTransactional
public void debit(String userId, BigDecimal num, String commodityCode, int orderCount) {
System.out.println(RootContext.getXID()) ;
accountMapper.updateAccount(num, userId) ;
storageFeignClient.deduct(commodityCode, orderCount);
try {
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(100)) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ERROR_USER_ID.equals(userId)) {
throw new RuntimeException("account branch exception");
}
}
}
這里模擬了拋出異常當(dāng)輸入的userId為1002時拋出異常,就為了測試所有服務(wù)的事務(wù)是否回滾了。注意這里需要添加@GlobalTransactional注解
Feign接口
@FeignClient(name = "storage-service", url = "127.0.0.1:8802")
public interface StorageFeignClient {
@GetMapping("/storage/deduct")
void deduct(@RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count);
}
storage庫存子模塊
該子模塊的配置與account模塊的配置基本一致
StorageService
@Service
public class StorageService {
@Resource
private StorageMapper storageMapper ;
@Transactional
public void deduct(String commodityCode, int count) {
System.out.println(RootContext.getXID()) ;
storageMapper.updateStorage(count, commodityCode) ;
}
}
測試
啟動seata,nacos,account,storage服務(wù)后查看nacos
圖片
seata服務(wù)也已經(jīng)注冊上來了。
數(shù)據(jù)庫初始化數(shù)據(jù)
圖片
圖片
正常請求
圖片
數(shù)據(jù)變化
圖片
當(dāng)傳入userId=1002時
圖片
account模塊控制臺
圖片
storage模塊控制臺
圖片
數(shù)據(jù)庫數(shù)據(jù)
圖片
數(shù)據(jù)沒有任何變化說明回滾了