Seata(Simple Extensible Autonomous Transaction Architecture)是2019 年 1 月份螞蟻金服和阿里巴巴共同開源的 分布式事務(wù) 解決方案。以 高效 并且對業(yè)務(wù) 0 侵入 的方式,解決 微服務(wù) 場景下面臨的分布式事務(wù)問題。
產(chǎn)生背景
隨著業(yè)務(wù)數(shù)據(jù)規(guī)模的快速發(fā)展,數(shù)據(jù)量越來越大,原有的單庫單表模式逐漸成為瓶頸。這時(shí)需要對數(shù)據(jù)庫進(jìn)行了水平拆分,將原單庫單表拆分成數(shù)據(jù)庫分片。分庫分表之后,原來在一個(gè)數(shù)據(jù)庫上就能完成的寫操作,可能就會(huì)跨多個(gè)數(shù)據(jù)庫,這就產(chǎn)生了跨數(shù)據(jù)庫事務(wù)問題。
同時(shí)系統(tǒng)的訪問量和業(yè)務(wù)復(fù)雜程度也在快速增長,單體系統(tǒng)架構(gòu)逐漸成為業(yè)務(wù)發(fā)展瓶頸,將單業(yè)務(wù)系統(tǒng)拆分成多個(gè)業(yè)務(wù)系統(tǒng),降低了各系統(tǒng)之間的耦合度,使不同的業(yè)務(wù)系統(tǒng)專注于自身業(yè)務(wù),更有利于業(yè)務(wù)的發(fā)展和系統(tǒng)容量的伸縮。如何保證多個(gè)服務(wù)間的數(shù)據(jù)一致性成為一個(gè)難題。
Seata產(chǎn)品模塊
如上圖所示,Seata 中有三大模塊,分別是 TM、RM 和 TC。 其中 TM 和 RM 是作為 Seata 的客戶端與業(yè)務(wù)系統(tǒng)集成在一起,TC 作為 Seata 的服務(wù)端獨(dú)立部署。
在 Seata 中,分布式事務(wù)的執(zhí)行流程:
- TM 開啟分布式事務(wù)(TM 向 TC 注冊全局事務(wù)記錄);
- 按業(yè)務(wù)場景,編排數(shù)據(jù)庫、服務(wù)等事務(wù)內(nèi)資源(RM 向 TC 匯報(bào)資源準(zhǔn)備狀態(tài) );
- TM 結(jié)束分布式事務(wù),事務(wù)一階段結(jié)束(TM 通知 TC 提交/回滾分布式事務(wù));
- TC 匯總事務(wù)信息,決定分布式事務(wù)是提交還是回滾;
- TC 通知所有 RM 提交/回滾 資源,事務(wù)二階段結(jié)束。
演示項(xiàng)目
Seata提供了很多Demo工程,這里選擇了springboot-dubbo-seata 項(xiàng)目,基于 Spring Boot + Dubbo 的示例。官方源碼:https://github.com/seata/seata-samples
此項(xiàng)目包含如下服務(wù):
- samples-business 業(yè)務(wù)服務(wù)
- samples-account 賬號(hào)服務(wù)
- samples-order 訂單服務(wù)
- samples-storage 庫存服務(wù)
他們之間的關(guān)系如下圖:
數(shù)據(jù)準(zhǔn)備
使用Mysql創(chuàng)建seata數(shù)據(jù)庫,執(zhí)行初始化腳本db_seata.sql。
/*
Navicat MySQL Data Transfer
Source Server : account
Source Server Version : 50614
Source Host : localhost:3306
Source Database : db_gts_fescar
Target Server Type : MYSQL
Target Server Version : 50614
File Encoding : 65001
Date: 2019-01-26 10:23:10
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for t_account
-- ----------------------------
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;
這里創(chuàng)建了4個(gè)表:t_account、t_order、t_storage、undo_log。
其中undo_log是記錄需要回滾的事務(wù),其余是業(yè)務(wù)表。
啟動(dòng)服務(wù)注冊中心
這里用的是alibaba的Nacos,請使用1.1.0版本,防止因?yàn)閐ubbo,nacos因版本不匹配出現(xiàn)的心跳請求出錯(cuò)的情況。
v1.1.0地址:https://github.com/alibaba/nacos/releases/tag/1.1.0
下載解壓后在bin目錄下執(zhí)行startup.cmd即可啟動(dòng),端口為8848
通過瀏覽器可訪問控制臺(tái):http://127.0.0.1:8848/nacos/index.html,默認(rèn)用戶名/密碼為nacos/nacos。
啟動(dòng)Seata Server
從官網(wǎng)下載:https://github.com/seata/seata/releases。最新版本0.8.1
下載解壓后在斌目錄下執(zhí)行:
seata-server.bat -p 8091 -h 127.0.0.1 -m file
啟動(dòng)演示工程
分別啟動(dòng):samples-account、samples-order、samples-storage、samples-business
啟動(dòng)前需要修改配置文件(application.properties),配置數(shù)據(jù)庫連接:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/seata?useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
==注意==
服務(wù)可能會(huì)啟動(dòng)失敗
2019-09-30 14:32:15.205 ERROR 7208 --- [Create-60221145] com.alibaba.druid.pool.DruidDataSource : create connection SQLException,
url: jdbc:mysql://localhost:3306/seata?useSSL=false&serverTimezone=Asia/Shanghai,
errorCode 0, state 08001
這是因?yàn)槲冶镜氐臄?shù)據(jù)庫是Mysql8,系統(tǒng)使用的mysql數(shù)據(jù)庫驅(qū)動(dòng)mysql-connector-java版本號(hào)為5.1.31。將版本升級(jí)就可以了。
<!-- <mysql-connector.version>5.1.31</mysql-connector.version> -->
<mysql-connector.version>8.0.11</mysql-connector.version>
啟動(dòng)后可通過nacos控制臺(tái)看到服務(wù)是否已準(zhǔn)備就緒:
執(zhí)行正常的請求
使用curl請求http://localhost:8104/business/dubbo/buy
請求數(shù)據(jù):
{
"userId":"1",
"commodityCode":"C201901140001",
"name":"cup",
"count":2,
"amount":"100"
}
返回操作成功:
C:\Users\WBPC1108>curl -X POST -H "Content-Type:application/json" -d "{\"userId\":\"1\",\"commodityCode\":\"C201901140001\",\"name\":\"cup\",\"count\":2,\"amount\":\"100\"}" "http://localhost:8104/business/dubbo/buy"
{"status":200,"message":"成功","data":null}
查看數(shù)據(jù)變化。
請求前:
請求后:
測試回滾請求
修改samples-business工程里的BusinessServiceImpl類,主動(dòng)拋出異常:
if (!flag) {
throw new RuntimeException("測試拋異常后,分布式事務(wù)回滾!");
}
請求返回錯(cuò)誤信息,數(shù)據(jù)沒變化:
C:\Users\WBPC1108>curl -X POST -H "Content-Type:application/json" -d "{\"userId\":\"1\",\"commodityCode\":\"C201901140001\",\"name\":\"cup\",\"count\":2,\"amount\":\"100\"}" "http://localhost:8104/business/dubbo/buy"
{"timestamp":"2019-09-30T08:25:54.248+0000","status":500,"error":"Internal Server Error","message":"測試拋異常后,分布式事務(wù)回滾!","path":"/business/dubbo/buy"}