您好,登錄后才能下訂單哦!
如何用Seata解決分布式事務問題?這個問題可能是我們日常學習或工作經常見到的。通過這個問題,希望你能收獲更多。今天跟隨小編一起來看解決方案吧。
Seata 是 阿里巴巴2019年開源的分布式事務解決方案,致力于在微服務架構下提供高性能和簡單易用的分布式事務服務。在 Seata 開源之前,Seata 對應的內部版本在阿里內部一直扮演著分布式一致性中間件的角色,幫助阿里度過歷年的雙11,對各業務進行了有力的支撐。經過多年沉淀與積累,2019.1 Seata 正式宣布對外開源 。目前 Seata 1.0 已經 GA。
讓我們想象一下傳統的單片應用程序,它的業務由3個模塊組成,他們使用單個本地數據源。自然,本地事務將保證數據的一致性。
微服務架構已發生了變化。上面提到的3個模塊被設計為3種服務。本地事務自然可以保證每個服務中的數據一致性。但是整個業務邏輯范圍如何?
Seata怎么辦?
我們說,分布式事務是由一批分支事務組成的全局事務,通常分支事務只是本地事務。
Seata有3個基本組成部分:
Seata管理的分布式事務的典型生命周期:
用戶購買商品的業務邏輯。整個業務邏輯由3個微服務提供支持:
# db_seata
DROP SCHEMA IF EXISTS db_seata;
CREATE SCHEMA db_seata;
USE db_seata;
# Account
CREATE TABLE `account_tbl` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` VARCHAR(255) DEFAULT NULL,
`money` INT(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
INSERT INTO account_tbl (id, user_id, money)
VALUES (1, '1001', 10000);
INSERT INTO account_tbl (id, user_id, money)
VALUES (2, '1002', 10000);
# Order
CREATE TABLE `order_tbl`
(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` VARCHAR(255) DEFAULT NULL,
`commodity_code` VARCHAR(255) DEFAULT NULL,
`count` INT(11) DEFAULT '0',
`money` INT(11) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
# Storage
CREATE TABLE `storage_tbl` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`commodity_code` VARCHAR(255) DEFAULT NULL,
`count` INT(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
INSERT INTO storage_tbl (id, commodity_code, count)
VALUES (1, '2001', 1000);
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,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
seata AT 模式需要 undo_log 表,另外三張是業務表。
Server端存儲模式(store.mode)現有file、db兩種(后續將引入raft),file模式無需改動,直接啟動即可。db模式需要導入用于存儲全局事務回話信息的三張表。
注:file模式為單機模式,全局事務會話信息內存中讀寫并持久化本地文件root.data,性能較高;
db模式為高可用模式,全局事務會話信息通過db共享,相應性能差些
可以直接通過bash 腳本啟動 Seata Server,也可以通過 Docker 鏡像啟動,但是 Docker 方式目前只支持使用 file 模式,不支持將 Seata-Server 注冊到 Eureka 或 Nacos 等注冊中心。
在 https://github.com/seata/seata/releases 下載相應版本的 Seata Server,解壓后執行以下命令啟動,這里使用 file 配置
docker run --name seata-server -p 8091:8091 seataio/seata-server:latest
項目名 | 地址 | 說明 |
---|---|---|
sbm-account-service | 127.0.0.1:8081 | 賬戶服務 |
sbm-order-service | 127.0.0.1:8082 | 訂單服務 |
sbm-storage-service | 127.0.0.1:8083 | 倉儲服務 |
sbm-business-service | 127.0.0.1:8084 | 主業務 |
seata-server | 172.16.2.101:8091 | seata-server |
為了不讓篇幅太長,這里只給出部分代碼,詳細代碼文末會給出源碼地址
maven 引入 seata 的依賴 eata-spring-boot-starter
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
spring.application.name=account-service
server.port=8081
spring.datasource.url=jdbc:mysql://172.16.2.101:3306/db_seata?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
seata.tx-service-group=my_test_tx_group
mybatis.mapper-locations=classpath*:mapper/*Mapper.xml
seata.service.grouplist=172.16.2.101:8091
logging.level.io.seata=info
logging.level.io.seata.samples.account.persistence.AccountMapper=debug
public interface StorageService {
/**
* 扣除存儲數量
*/
void deduct(String commodityCode, int count);
}
public interface OrderService {
/**
* 創建訂單
*/
Order create(String userId, String commodityCode, int orderCount);
}
public interface AccountService {
/**
* 從用戶賬戶中借出
*/
void debit(String userId, int money);
}
只需要使用一個 @GlobalTransactional 注解在業務方法上。
@GlobalTransactional
public void purchase(String userId, String commodityCode, int orderCount) {
LOGGER.info("purchase begin ... xid: " + RootContext.getXID());
storageClient.deduct(commodityCode, orderCount);
orderClient.create(userId, commodityCode, orderCount);
}
全局事務ID的跨服務傳遞,需要我們自己實現,這里通過攔截器的方式。每個服務都需要添加下面兩個類。
@Component
public class SeataFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
String xid = req.getHeader(RootContext.KEY_XID.toLowerCase());
boolean isBind = false;
if (StringUtils.isNotBlank(xid)) {
RootContext.bind(xid);
isBind = true;
}
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
if (isBind) {
RootContext.unbind();
}
}
}
@Override
public void destroy() {
}
}
@Configuration
public class SeataRestTemplateAutoConfiguration {
@Autowired(
required = false
)
private Collection<RestTemplate> restTemplates;
@Autowired
private SeataRestTemplateInterceptor seataRestTemplateInterceptor;
public SeataRestTemplateAutoConfiguration() {
}
@Bean
public SeataRestTemplateInterceptor seataRestTemplateInterceptor() {
return new SeataRestTemplateInterceptor();
}
@PostConstruct
public void init() {
if (this.restTemplates != null) {
Iterator var1 = this.restTemplates.iterator();
while (var1.hasNext()) {
RestTemplate restTemplate = (RestTemplate) var1.next();
List<ClientHttpRequestInterceptor> interceptors = new ArrayList(restTemplate.getInterceptors());
interceptors.add(this.seataRestTemplateInterceptor);
restTemplate.setInterceptors(interceptors);
}
}
}
}
curl -X POST http://127.0.0.1:8084/api/business/purchase/commit
此時返回結果為:true
UserId 為1002 的用戶下單,sbm-account-service會拋出異常,事務會回滾
http://127.0.0.1:8084/api/business/purchase/rollback
此時返回結果為:false
查看 undo_log 的日志或者主鍵,可以看到在執行過程中有保存數據。
如查看主鍵自增的值,在執行前后的值會發生變化,在執行前是 1,執行后是 7
以上就是用Seata解決分布式事務問題的方法介紹,詳細使用情況還得要大家自己使用過才能知道具體要領。如果想閱讀更多相關內容的文章,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。