Seata 簡介
傳統的單體應用中,業務操作使用同一條連線操作不同的資料表,一旦出現異常就可以整體回滾。隨著公司的快速發展、業務需求的變化,單體應用被拆分成微服務應用,原來的單體應用被拆分成多個獨立的微服務,分別使用獨立的資料來源,業務操作需要呼叫三個服務來完成。此時每個服務內部的資料一致性由本地事務來保證,但是全域性的資料一致性問題無法保證。在微服務架構中,一次業務請求需要操作多個資料來源或需要進行遠端呼叫,就會產生分散式事務問題。
Seata 是一款開源的分散式事務解決方案,致力於提供高效能和簡單易用的分散式事務服務,Seata 為使用者提供 AT、TCC、SAGA 和 XA 事務模式,為使用者打造一站式的分散式解決方案。AT 模式是 Seata 預設的工作模式,該模式是 XA 協議的演變。
關於分散式事務的知識可以參考:https://zhuanlan.zhihu.com/p/263555694
Seata 服務端
透過 https://github.com/seata/seata/releases 地址下載 Seata 安裝包,本文使用的 Seata 版本是 seata-server-1.7.1
解壓 seata-server-1.7.1 安裝包,修改 conf/application.conf 檔案
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: D:\seata-server-1.7.1\logs\seata
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
config:
# 使用nacos作為配置中心,seata將從nacos獲取配置
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: 0178e474-2cfb-47c3-bded-da7cfa260f99
group: springcloud-project
data-id: seata-server.properties
registry:
# 使用nacos作為註冊中心,seata將自身服務註冊到nacos
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: 0178e474-2cfb-47c3-bded-da7cfa260f99
group: springcloud-project
application: seata-server
cluster: default
store:
# 儲存模式:
# file模式為單機模式,全域性事務會話資訊記憶體中讀寫並持久化本地檔案 rootdata,效能較高
# db模式為高可用模式,全域性事務會話資訊透過db共享,相應效能會差
# redis模式效能較高,存在事務資訊丟失風險,需要提前配置適合當前場景的redis持久化配置
mode: file
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login
在 Nacos 控制檯建立配置檔案 seata-server.properties,注意分組和名稱空間要與上述配置保持一致
具體配置項是從 script/config-center/config.txt 貼上修改而來,這裡只使用對我們有用的配置,主要是資料庫配置資訊
#Transaction storage configuration, only for the server.
store.mode=db
store.lock.mode=db
store.session.mode=db
#These configurations are required if the `store mode` is `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/test_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useAffectedRows=true
store.db.user=root
store.db.password=123
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
在上面配置的資料庫內,執行 script/server/db 目錄下的 sql 指令碼,建立服務端所需的表
完成以後,即可進入 bin 目錄使用指令碼啟動 Seata
Seata 客戶端
為客戶端微服務新增依賴
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
配置檔案新增如下配置
seata:
enabled: true
tx-service-group: test-seata-group # 自定義事務組名稱,需要與下面service.vgroup-mapping中的一個對映保持一致
service:
vgroup-mapping:
test-seata-group: default
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8847/nacos
namespace: 0178e474-2cfb-47c3-bded-da7cfa260f99
group: springcloud-project
application: seata-server
Seata 預設使用 AT 模式,該模式需求每個客戶端庫內都存在一張 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;
在需要使用分散式事務的方法上新增註解 @GlobalTransactional
,當方法內發生異常時,就可以帶動所呼叫微服務進行回滾
@GlobalTransactional
public void create(Order order) {
log.info("下單開始");
orderDao.create(order);
log.info("下單結束");
log.info("開始扣減餘額");
server02FeignClient.accountIncrUsed(order.getUserId(), order.getMoney());
log.info("扣減餘額結束");
log.info("開始扣減庫存");
server03FeignClient.storageIncrUsed(order.getProductId(), order.getCount());
log.info("扣減庫存結束");
log.info("開始修改訂單狀態");
orderDao.updateStatus(1, order.getId());
log.info("修改訂單狀態結束");
}
配置完成,啟動服務,即可開始測試