javascript
seata xid是什么_使用Seata彻底解决Spring Cloud中的分布式事务问题!
Seata是Alibaba開源的一款分布式事務(wù)解決方案,致力于提供高性能和簡單易用的分布式事務(wù)服務(wù),本文將通過一個(gè)簡單的下單業(yè)務(wù)場(chǎng)景來對(duì)其用法進(jìn)行詳細(xì)介紹。
什么是分布式事務(wù)問題?
單體應(yīng)用
單體應(yīng)用中,一個(gè)業(yè)務(wù)操作需要調(diào)用三個(gè)模塊完成,此時(shí)數(shù)據(jù)的一致性由本地事務(wù)來保證。
微服務(wù)應(yīng)用
隨著業(yè)務(wù)需求的變化,單體應(yīng)用被拆分成微服務(wù)應(yīng)用,原來的三個(gè)模塊被拆分成三個(gè)獨(dú)立的應(yīng)用,分別使用獨(dú)立的數(shù)據(jù)源,業(yè)務(wù)操作需要調(diào)用三個(gè)服務(wù)來完成。此時(shí)每個(gè)服務(wù)內(nèi)部的數(shù)據(jù)一致性由本地事務(wù)來保證,但是全局的數(shù)據(jù)一致性問題沒法保證。
小結(jié)
在微服務(wù)架構(gòu)中由于全局?jǐn)?shù)據(jù)一致性沒法保證產(chǎn)生的問題就是分布式事務(wù)問題。簡單來說,一次業(yè)務(wù)操作需要操作多個(gè)數(shù)據(jù)源或需要進(jìn)行遠(yuǎn)程調(diào)用,就會(huì)產(chǎn)生分布式事務(wù)問題。
Seata簡介
Seata 是一款開源的分布式事務(wù)解決方案,致力于提供高性能和簡單易用的分布式事務(wù)服務(wù)。Seata 將為用戶提供了 AT、TCC、SAGA 和 XA 事務(wù)模式,為用戶打造一站式的分布式解決方案。
Seata原理和設(shè)計(jì)
定義一個(gè)分布式事務(wù)
我們可以把一個(gè)分布式事務(wù)理解成一個(gè)包含了若干分支事務(wù)的全局事務(wù),全局事務(wù)的職責(zé)是協(xié)調(diào)其下管轄的分支事務(wù)達(dá)成一致,要么一起成功提交,要么一起失敗回滾。此外,通常分支事務(wù)本身就是一個(gè)滿足ACID的本地事務(wù)。這是我們對(duì)分布式事務(wù)結(jié)構(gòu)的基本認(rèn)識(shí),與 XA 是一致的。
協(xié)議分布式事務(wù)處理過程的三個(gè)組件Transaction Coordinator (TC):事務(wù)協(xié)調(diào)器,維護(hù)全局事務(wù)的運(yùn)行狀態(tài),負(fù)責(zé)協(xié)調(diào)并驅(qū)動(dòng)全局事務(wù)的提交或回滾;
Transaction Manager (TM):控制全局事務(wù)的邊界,負(fù)責(zé)開啟一個(gè)全局事務(wù),并最終發(fā)起全局提交或全局回滾的決議;
Resource Manager (RM):控制分支事務(wù),負(fù)責(zé)分支注冊(cè)、狀態(tài)匯報(bào),并接收事務(wù)協(xié)調(diào)器的指令,驅(qū)動(dòng)分支(本地)事務(wù)的提交和回滾。
一個(gè)典型的分布式事務(wù)過程TM 向 TC 申請(qǐng)開啟一個(gè)全局事務(wù),全局事務(wù)創(chuàng)建成功并生成一個(gè)全局唯一的 XID;
XID 在微服務(wù)調(diào)用鏈路的上下文中傳播;
RM 向 TC 注冊(cè)分支事務(wù),將其納入 XID 對(duì)應(yīng)全局事務(wù)的管轄;
TM 向 TC 發(fā)起針對(duì) XID 的全局提交或回滾決議;
TC 調(diào)度 XID 下管轄的全部分支事務(wù)完成提交或回滾請(qǐng)求。
seata-server的安裝與配置我們先從官網(wǎng)下載seata-server,這里下載的是seata-server-0.9.0.zip,下載地址:https://github.com/seata/seata/releases
解壓seata-server安裝包到指定目錄,修改conf目錄下的file.conf配置文件,主要修改自定義事務(wù)組名稱,事務(wù)日志存儲(chǔ)模式為db及數(shù)據(jù)庫連接信息;service {
#vgroup->rgroup
vgroup_mapping.fsp_tx_group = "default" #修改事務(wù)組名稱為:fsp_tx_group,和客戶端自定義的名稱對(duì)應(yīng)
#only support single node
default.grouplist = "127.0.0.1:8091"
#degrade current not support
enableDegrade = false
#disable
disable = false
#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
max.commit.retry.timeout = "-1"
max.rollback.retry.timeout = "-1"
}
## transaction log store
store {
## store mode: file、db
mode = "db" #修改此處將事務(wù)信息存儲(chǔ)到數(shù)據(jù)庫中
## database store
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "dbcp"
## mysql/oracle/h2/oceanbase etc.
db-type = "mysql"
driver-class-name = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://localhost:3306/seat-server" #修改數(shù)據(jù)庫連接地址
user = "root" #修改數(shù)據(jù)庫用戶名
password = "root" #修改數(shù)據(jù)庫密碼
min-conn = 1
max-conn = 3
global.table = "global_table"
branch.table = "branch_table"
lock-table = "lock_table"
query-limit = 100
}
}由于我們使用了db模式存儲(chǔ)事務(wù)日志,所以我們需要?jiǎng)?chuàng)建一個(gè)seat-server數(shù)據(jù)庫,建表sql在seata-server的/conf/db_store.sql中;
修改conf目錄下的registry.conf配置文件,指明注冊(cè)中心為nacos,及修改nacos連接信息即可;registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos" #改為nacos
nacos {
serverAddr = "localhost:8848" #改為nacos的連接地址
namespace = ""
cluster = "default"
}
}先啟動(dòng)Nacos,再使用seata-server中/bin/seata-server.bat文件啟動(dòng)seata-server。
數(shù)據(jù)庫準(zhǔn)備
創(chuàng)建業(yè)務(wù)數(shù)據(jù)庫seat-order:存儲(chǔ)訂單的數(shù)據(jù)庫;
seat-storage:存儲(chǔ)庫存的數(shù)據(jù)庫;
seat-account:存儲(chǔ)賬戶信息的數(shù)據(jù)庫。
初始化業(yè)務(wù)表
order表CREATE TABLE `order` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(11) DEFAULT NULL COMMENT '用戶id',
`product_id` bigint(11) DEFAULT NULL COMMENT '產(chǎn)品id',
`count` int(11) DEFAULT NULL COMMENT '數(shù)量',
`money` decimal(11,0) DEFAULT NULL COMMENT '金額',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
ALTER TABLE `order` ADD COLUMN `status` int(1) DEFAULT NULL COMMENT '訂單狀態(tài):0:創(chuàng)建中;1:已完結(jié)' AFTER `money` ;
storage表CREATE TABLE `storage` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`product_id` bigint(11) DEFAULT NULL COMMENT '產(chǎn)品id',
`total` int(11) DEFAULT NULL COMMENT '總庫存',
`used` int(11) DEFAULT NULL COMMENT '已用庫存',
`residue` int(11) DEFAULT NULL COMMENT '剩余庫存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `seat-storage`.`storage` (`id`, `product_id`, `total`, `used`, `residue`) VALUES ('1', '1', '100', '0', '100');
account表CREATE TABLE `account` (
`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`user_id` bigint(11) DEFAULT NULL COMMENT '用戶id',
`total` decimal(10,0) DEFAULT NULL COMMENT '總額度',
`used` decimal(10,0) DEFAULT NULL COMMENT '已用余額',
`residue` decimal(10,0) DEFAULT '0' COMMENT '剩余可用額度',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `seat-account`.`account` (`id`, `user_id`, `total`, `used`, `residue`) VALUES ('1', '1', '1000', '0', '1000');
創(chuàng)建日志回滾表
使用Seata還需要在每個(gè)數(shù)據(jù)庫中創(chuàng)建日志表,建表sql在seata-server的/conf/db_undo_log.sql中。
完整數(shù)據(jù)庫示意圖
制造一個(gè)分布式事務(wù)問題
這里我們會(huì)創(chuàng)建三個(gè)服務(wù),一個(gè)訂單服務(wù),一個(gè)庫存服務(wù),一個(gè)賬戶服務(wù)。當(dāng)用戶下單時(shí),會(huì)在訂單服務(wù)中創(chuàng)建一個(gè)訂單,然后通過遠(yuǎn)程調(diào)用庫存服務(wù)來扣減下單商品的庫存,再通過遠(yuǎn)程調(diào)用賬戶服務(wù)來扣減用戶賬戶里面的余額,最后在訂單服務(wù)中修改訂單狀態(tài)為已完成。該操作跨越三個(gè)數(shù)據(jù)庫,有兩次遠(yuǎn)程調(diào)用,很明顯會(huì)有分布式事務(wù)問題。
客戶端配置對(duì)seata-order-service、seata-storage-service和seata-account-service三個(gè)seata的客戶端進(jìn)行配置,它們配置大致相同,我們下面以seata-order-service的配置為例;
修改application.yml文件,自定義事務(wù)組的名稱;spring:
cloud:
alibaba:
seata:
tx-service-group: fsp_tx_group #自定義事務(wù)組名稱需要與seata-server中的對(duì)應(yīng)添加并修改file.conf配置文件,主要是修改自定義事務(wù)組名稱;service {
#vgroup->rgroup
vgroup_mapping.fsp_tx_group = "default" #修改自定義事務(wù)組名稱
#only support single node
default.grouplist = "127.0.0.1:8091"
#degrade current not support
enableDegrade = false
#disable
disable = false
#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
max.commit.retry.timeout = "-1"
max.rollback.retry.timeout = "-1"
disableGlobalTransaction = false
}添加并修改registry.conf配置文件,主要是將注冊(cè)中心改為nacos;registry {
# file 、nacos 、eureka、redis、zk
type = "nacos" #修改為nacos
nacos {
serverAddr = "localhost:8848" #修改為nacos的連接地址
namespace = ""
cluster = "default"
}
}在啟動(dòng)類中取消數(shù)據(jù)源的自動(dòng)創(chuàng)建:@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class SeataOrderServiceApplication{
public static void main(String[] args){
SpringApplication.run(SeataOrderServiceApplication.class, args);
}
}創(chuàng)建配置使用Seata對(duì)數(shù)據(jù)源進(jìn)行代理:/**
* 使用Seata對(duì)數(shù)據(jù)源進(jìn)行代理
* Created by macro on 2019/11/11.
*/
@Configuration
public class DataSourceProxyConfig{
@Value("${mybatis.mapperLocations}")
private String mapperLocations;
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource){
return new DataSourceProxy(dataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception{
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources(mapperLocations));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}使用@GlobalTransactional注解開啟分布式事務(wù):package com.macro.cloud.service.impl;
import com.macro.cloud.dao.OrderDao;
import com.macro.cloud.domain.Order;
import com.macro.cloud.service.AccountService;
import com.macro.cloud.service.OrderService;
import com.macro.cloud.service.StorageService;
import io.seata.spring.annotation.GlobalTransactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 訂單業(yè)務(wù)實(shí)現(xiàn)類
* Created by macro on 2019/11/11.
*/
@Service
public class OrderServiceImpl implements OrderService{
private static final Logger LOGGER = LoggerFactory.getLogger(OrderServiceImpl.class);
@Autowired
private OrderDao orderDao;
@Autowired
private StorageService storageService;
@Autowired
private AccountService accountService;
/**
* 創(chuàng)建訂單->調(diào)用庫存服務(wù)扣減庫存->調(diào)用賬戶服務(wù)扣減賬戶余額->修改訂單狀態(tài)
*/
@Override
@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
public void create(Order order){
LOGGER.info("------->下單開始");
//本應(yīng)用創(chuàng)建訂單
orderDao.create(order);
//遠(yuǎn)程調(diào)用庫存服務(wù)扣減庫存
LOGGER.info("------->order-service中扣減庫存開始");
storageService.decrease(order.getProductId(),order.getCount());
LOGGER.info("------->order-service中扣減庫存結(jié)束:{}",order.getId());
//遠(yuǎn)程調(diào)用賬戶服務(wù)扣減余額
LOGGER.info("------->order-service中扣減余額開始");
accountService.decrease(order.getUserId(),order.getMoney());
LOGGER.info("------->order-service中扣減余額結(jié)束");
//修改訂單狀態(tài)為已完成
LOGGER.info("------->order-service中修改訂單狀態(tài)開始");
orderDao.update(order.getUserId(),0);
LOGGER.info("------->order-service中修改訂單狀態(tài)結(jié)束");
LOGGER.info("------->下單結(jié)束");
}
}
分布式事務(wù)功能演示運(yùn)行seata-order-service、seata-storage-service和seata-account-service三個(gè)服務(wù);
數(shù)據(jù)庫初始信息狀態(tài):
調(diào)用接口進(jìn)行下單操作后查看數(shù)據(jù)庫:http://localhost:8180/order/create?userId=1&productId=1&count=10&money=100
我們?cè)趕eata-account-service中制造一個(gè)超時(shí)異常后,調(diào)用下單接口:/**
* 賬戶業(yè)務(wù)實(shí)現(xiàn)類
* Created by macro on 2019/11/11.
*/
@Service
public class AccountServiceImpl implements AccountService{
private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);
@Autowired
private AccountDao accountDao;
/**
* 扣減賬戶余額
*/
@Override
public void decrease(Long userId, BigDecimal money){
LOGGER.info("------->account-service中扣減賬戶余額開始");
//模擬超時(shí)異常,全局事務(wù)回滾
try {
Thread.sleep(30*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
accountDao.decrease(userId,money);
LOGGER.info("------->account-service中扣減賬戶余額結(jié)束");
}
}此時(shí)我們可以發(fā)現(xiàn)下單后數(shù)據(jù)庫數(shù)據(jù)并沒有任何改變;
我們可以在seata-order-service中注釋掉@GlobalTransactional來看看沒有Seata的分布式事務(wù)管理會(huì)發(fā)生什么情況:/**
* 訂單業(yè)務(wù)實(shí)現(xiàn)類
* Created by macro on 2019/11/11.
*/
@Service
public class OrderServiceImpl implements OrderService{
/**
* 創(chuàng)建訂單->調(diào)用庫存服務(wù)扣減庫存->調(diào)用賬戶服務(wù)扣減賬戶余額->修改訂單狀態(tài)
*/
@Override
// ? ?@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
public void create(Order order){
LOGGER.info("------->下單開始");
//省略代碼...
LOGGER.info("------->下單結(jié)束");
}
}由于seata-account-service的超時(shí)會(huì)導(dǎo)致當(dāng)庫存和賬戶金額扣減后訂單狀態(tài)并沒有設(shè)置為已經(jīng)完成,而且由于遠(yuǎn)程調(diào)用的重試機(jī)制,賬戶余額還會(huì)被多次扣減。
參考資料
Seata官方文檔:https://github.com/seata/seata/wiki
使用到的模塊springcloud-learning
├── seata-order-service -- 整合了seata的訂單服務(wù)
├── seata-storage-service -- 整合了seata的庫存服務(wù)
└── seata-account-service -- 整合了seata的賬戶服務(wù)
項(xiàng)目源碼地址
https://github.com/macrozheng/springcloud-learning
總結(jié)
以上是生活随笔為你收集整理的seata xid是什么_使用Seata彻底解决Spring Cloud中的分布式事务问题!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python怎么使用time模块_PYT
- 下一篇: java解析json_JAVA解析JSO