Seata介紹#
什麼是Seata#
- 一個開源分布式事務架構,由阿裡中間件團隊發起的開源項目Fescar,後更名為Seata
- 中文文檔位址:http://seata.io/zh-cn/docs/user/quickstart.html
Seata三大元件#
- TC:Transaction Coordinator事務協調器,管理全局的分支事務的狀态,用于全局性事務的送出和復原
- TM:Transaction Manager 事務管理器,使用者開啟、送出或者復原【全局事務】
- RM:Resource Manager資料總管,用于分支事務上的資源管理,向TC注冊分支事務,上報分支事務的狀态,接收TC的指令來送出或者復原分支事務傳統XA協定實作2PC方案的RM是在資料庫層,RM本質上就是資料庫自身Seata的RM是以jar包的形式嵌入在應用程式裡面
架構:TC為單獨部署的Server服務端,TM和RM為嵌入到應用中的Client用戶端
XID
- TM請求TC開啟一個全局事務,TC會生成一個XID作為該全局事務的編号XID,XID會在微服務的調用鍊路中傳播,保證将多個微服務對的子事務關聯在一起
Seata部署安裝#
下載下傳Seata位址#
http://seata.io/zh-cn/blog/download.html
注:我這邊下載下傳的是1.4.1,seata部署版本需要與SpringBoot依賴的版本相對應!!!!!!
Seata部署#
前期準備#
準備好Nacos、mysql
注:nacos配置中心資料是持久化到mysql的!!!!
部署&修改配置#
修改存儲模式DB
上傳至伺服器,目錄為:/usr/local/software
# 1、建立目錄
mkdir -p /usr/local/software
# 2、解壓
unzip seata-server-1.4.1.zip
# 3、修改存儲模式 DB
cd seata/conf/
vi file.conf
注:修改為自己的mysql!!!!
## transaction log store, only used in seata-server
store {
## store mode: file、db、redis
mode = "file"
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://47.116.143.16:3306/seata?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai"
user = "root"
password = "root"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
将seata需要的3張表導入資料庫中,分别是:global_table、branch_table、lock_table
官網位址:http://seata.io/zh-cn/docs/user/quickstart.html
github位址:https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql
seata的mysql腳本
修改Seata 配置中心&注冊中心
修改Seata的配置
# 修改Seata配置
cd /usr/local/software/seata/conf
vi registry.conf
注:修改成自己的nacos資訊
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
loadBalance = "RandomLoadBalance"
loadBalanceVirtualNodes = 10
nacos {
application = "seata-server"
serverAddr = "47.116.143.16:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "47.116.143.16:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
}
因為Seata的配置中心是nacos,需要把Seata的配置,通過腳本推送到nacos中
官網位址:https://seata.io/zh-cn/docs/user/configuration/nacos.html
腳本位址:https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh
config.txt位址(可以暫時不修改配置參數,直接到nacos中修改配置):https://github.com/seata/seata/blob/develop/script/config-center/config.txt
将Seata配置參數推送到nacos配置中心
# 1、将github中的nacos-config.sh,傳到伺服器上,目錄為:/usr/local/software/seata/conf
# 我這邊使用的是,将腳本檔案拷出,在服務建立檔案夾,賦予權限
touch nacos-config.sh
chmod +x nacos-config.sh
# 2、将config.txt,放到伺服器上,目錄為:/usr/local/software/seata
執行腳本
sh nacos-config.sh -h 47.116.143.16 -p 8848 -g SEATA_GROUP -u nacos -w nacos
-h:nacos主機位址
-p:nacos端口号
-g:nacos分組
-t:nacos命名空間
-u:nacos賬号
-w:nacos密碼
推送成功,已将Seata配置參數推送到Nacos配置中心
在nacos配置中心裡,修改Seata參數,具體修改參考官網如下
具體config.txt裡的參數解釋:https://seata.io/zh-cn/docs/user/configurations.html
建立2個配置需要與微服務中的配置對應上
service.vgroupMapping.${spring.alibaba.seata.tx-service-group}=default
如下
service.vgroupMapping.order_service_group=default
service.vgroupMapping.product_service_group=default
注意:分組為:SEATA_GROUP
啟動Seata服務#
- ./seata-server.sh啟動,預設端口8091(守護程序方式啟動 nohup ./seata-server.sh &)
注意:如果seata部署在伺服器,微服務在本地啟動的話,2個服務不在一個區域網路下,是以沒法通信,啟動Seata時,需要指定ip和端口号
sh seata-server.sh -p 8091 -h 47.116.143.16
Seata AT模式日期序列化問題解決方案#
後端服務引入kryo依賴#
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>de.javakaffee</groupId>
<artifactId>kryo-serializers</artifactId>
<version>0.42</version>
</dependency>
修改Seata在nacos配置中心配置#
将
client.undo.logSerialization=jackson
修改為
client.undo.logSerialization=kryo
微服務整合Seata#
前期準備#
在每個微服務所連的庫,建立一張表
-- 注意此處0.3.0+ 增加唯一索引 ux_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;
聚合工程搭建#
。。。。
項目結構#
- ybchen-common:公共子產品
- ybchen-order-service:訂單微服務
- ybchen-product-service:商品微服務
資料庫分表為:order(訂單微服務庫)、product(商品微服務庫)、seata(Seata全局事務涉及的表)、nacos(Nacos配置中心,mysql持久化)
Seata依賴#
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.1</version>
</dependency>
<!-- seata 自身序列化bug問題-開始 -->
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>de.javakaffee</groupId>
<artifactId>kryo-serializers</artifactId>
<version>0.42</version>
</dependency>
<!-- seata 自身序列化bug問題-結束 -->
分布式事務示範#
關鍵代碼片段
order-service
@Autowired
OrderMapper orderMapper;
@Autowired
ProductStockControllerFeign productStockControllerFeign;
@Override
//開啟分布式事務 Seta AT模式
@GlobalTransactional
public ReturnT<String> add() {
OrderDO orderDO = new OrderDO();
int outTradeNo = new Random().nextInt(1000);
orderDO.setOutTradeNo("T" + outTradeNo);
orderDO.setCreateTime(new Date());
int rows = orderMapper.insert(orderDO);
if (rows > 0) {
//扣減商品庫存
ReturnT<String> reduceReturn = productStockControllerFeign.reduce();
if (ReturnT.isSuccess(reduceReturn)) {
log.info("購買成功");
//TODO 模拟異常方式二
// int num = 1 / 0;
return ReturnT.buildSuccess("購買成功");
}
// 解決全局攔截器問題,通過接口響應狀态碼,來判斷是否主動抛異常!!!!!!!
if (reduceReturn.getCode() != 0) {
log.info("扣減商品庫存失敗,接口響應:{}", reduceReturn);
throw new BizException(110, "扣減商品庫存失敗");
}
log.info("扣減商品庫存失敗");
return ReturnT.buildError("扣減商品庫存失敗");
}
log.info("購買失敗");
return ReturnT.buildError("購買失敗");
}
product-service
@Autowired
ProductStockMapper productStockMapper;
@Override
public ReturnT<String> reduceProductStock() {
ProductStockDO stockDO = new ProductStockDO();
stockDO.setProductId(10086);
stockDO.setBuyNum(1);
stockDO.setCreateTime(new Date());
int rows = productStockMapper.insert(stockDO);
//TODO 模拟異常方式一
// int num = 1 / 0;
if (rows > 0) {
log.info("扣減商品庫存成功,rows=" + rows);
return ReturnT.buildSuccess("扣減商品庫存成功");
} else {
log.info("扣減商品庫存失敗,rows=" + rows);
return ReturnT.buildError("扣減失敗");
}
}
正常情況#
場景描述:product微服務和order微服務均正常,2個微服務的事務全部送出成功,2個庫都插入資料成功
異常情況一(product微服務異常)#
場景描述:product微服務發生異常,order微服務正常情況,出現異常情況時,需要2個微服務的事務全部復原,2個庫插入的資料都復原
異常情況二(order微服務異常)#
場景描述:order微服務發生異常,product微服務正常,出現異常情況時,需要2個微服務的事務全部復原,2個庫插入的資料都復原
異常情況三(product微服務未啟動)#
場景描述:order微服務正常啟動,product微服務未啟動,需要把order微服務插入的資料復原
項目源碼#
https://github.com/543210188/ybchen-seata
https://gitee.com/yenbin_chen/ybchen-seatay
原文連結:https://www.cnblogs.com/chenyanbin/p/seata.html