一篇帶你快速上手 Seata
1.事務(wù)
事務(wù)(Transaction),在數(shù)據(jù)庫操作中,指的是一個(gè)原子性的操作序列。這個(gè)序列中的所有操作要么全部成功,要么全部失敗,絕不會出現(xiàn)部分成功的情況。你可以將事務(wù)想象成一個(gè)數(shù)據(jù)庫操作的“整體”,要么一起完成,要么一起取消。
事務(wù)通常由高級數(shù)據(jù)庫操作語言或編程語言書寫的用戶程序所引起,并用形如 begin transaction
或 end transaction
語句(或函數(shù)調(diào)用)來界定。事務(wù)由事務(wù)開始(begin transaction
)和事務(wù)結(jié)束(end transaction
)之間執(zhí)行的全體操作組成。
事務(wù)通常具備以下四個(gè)特性,簡稱為 ACID:
- 原子性(Atomicity):事務(wù)是不可分割的最小操作單位,事務(wù)中的所有操作要么全部成功,要么全部失敗,不會停留在中間狀態(tài)。
- 一致性(Consistency):事務(wù)完成后,系統(tǒng)狀態(tài)應(yīng)該保持一致,即從一個(gè)一致的狀態(tài)轉(zhuǎn)換到另一個(gè)一致的狀態(tài)。
- 隔離性(Isolation):在事務(wù)未完成之前,不允許其他事務(wù)訪問它的數(shù)據(jù),保證事務(wù)之間相互獨(dú)立,不受其他事務(wù)的干擾。
- 持久性(Durability):一旦事務(wù)提交后,所做的更改將永久保存在數(shù)據(jù)庫中,即使系統(tǒng)崩潰也不會丟失事務(wù)的結(jié)果。
(1)分布式事務(wù)
分布式事務(wù):在多個(gè)獨(dú)立的資源或服務(wù)之間保證一致性的事務(wù)操作。涉及多個(gè)系統(tǒng)(如多個(gè)數(shù)據(jù)庫、微服務(wù)等),并確保所有參與的資源要么全部成功,要么全部失敗,以保持?jǐn)?shù)據(jù)一致性。分布式事務(wù)廣泛應(yīng)用于分布式系統(tǒng)、微服務(wù)架構(gòu)等需要跨數(shù)據(jù)庫或跨服務(wù)的場景。
(2)Java 分布式事務(wù)
分布式事務(wù):是指在多個(gè)數(shù)據(jù)庫或系統(tǒng)中,通過技術(shù)手段,協(xié)調(diào)多個(gè)操作,使其作為一個(gè)整體來執(zhí)行。也就是說,這些操作要么全部成功,要么全部失敗,以保證整個(gè)系統(tǒng)的數(shù)據(jù)一致性。通常,分布式事務(wù)會采用兩階段提交(2PC)等協(xié)議,由事務(wù)管理器(如Atomikos、Bitronix)來協(xié)調(diào)各個(gè)參與者(如數(shù)據(jù)庫)的行為。事務(wù)管理器會向所有參與者發(fā)送提交或回滾的請求,只有所有參與者都確認(rèn)收到并執(zhí)行了請求,事務(wù)才算完成。
通過 技術(shù)實(shí)現(xiàn)的分布式事務(wù)控制,通常使用 () 進(jìn)行管理。 允許 應(yīng)用程序在多個(gè)不同的數(shù)據(jù)源之間管理全局事務(wù),確保在分布式系統(tǒng)中保持?jǐn)?shù)據(jù)一致性。 分布式事務(wù)通常使用事務(wù)管理器(如 Atomikos、Bitronix)協(xié)調(diào)多個(gè)資源,并通過協(xié)議(如 2PC)實(shí)現(xiàn)事務(wù)的提交或回滾。
(3)2PC(Two-Phase Commit)
2PC 協(xié)議簡單、實(shí)現(xiàn)容易,但在網(wǎng)絡(luò)不穩(wěn)定的環(huán)境下,可能出現(xiàn)阻塞問題。例如,如果協(xié)調(diào)者在提交階段宕機(jī),所有參與者將被阻塞在等待狀態(tài),影響系統(tǒng)性能。此外,2PC缺少有效的容錯(cuò)機(jī)制,一旦協(xié)調(diào)者出現(xiàn)故障,事務(wù)狀態(tài)可能難以恢復(fù)。
TCC(Try-Confirm-Cancel)
- Try 階段:資源預(yù)留。
- Confirm 階段:確認(rèn)提交。
- Cancel 階段:取消操作。
TCC 具備靈活的錯(cuò)誤處理和恢復(fù)機(jī)制,適用于異步、長事務(wù)和高并發(fā)場景。每個(gè)服務(wù)都可以實(shí)現(xiàn)自己的 Try、Confirm、Cancel 邏輯,從而避免傳統(tǒng) 2PC 帶來的阻塞和性能問題,但實(shí)現(xiàn)難度相對較高,因?yàn)樾枰_發(fā)人員手動(dòng)編寫事務(wù)的補(bǔ)償邏輯。
CAP 理論:CAP 理論由計(jì)算機(jī)科學(xué)家 Eric Brewer 在 2000 年提出,后來被廣泛接受并成為分布式系統(tǒng)設(shè)計(jì)中的重要指導(dǎo)原則。分布式系統(tǒng)中的基本定理,指出在一個(gè)分布式數(shù)據(jù)存儲系統(tǒng)中,無法同時(shí)滿足一致性(Consistency)、可用性(Availability)和分區(qū)容錯(cuò)性(Partition Tolerance)這三個(gè)特性。
BASE 理論:一種相對 CAP 理論的分布式系統(tǒng)設(shè)計(jì)理論,提出一種弱一致性的替代方案,用于滿足分布式系統(tǒng)的高可用性需求。核心思想是弱化強(qiáng)一致性的要求,以提高系統(tǒng)的可用性和性能。BASE 適用于那些不需要強(qiáng)一致性、但需要高響應(yīng)速度和高可用性的分布式系統(tǒng),比如電商平臺和社交媒體等。
XA
XA是一種兩階段提交協(xié)議規(guī)范,由X/Open組織提出。它定義了資源管理器(如數(shù)據(jù)庫)和事務(wù)管理器之間交互的接口,用于協(xié)調(diào)分布式事務(wù)。
- XA的兩個(gè)階段:
a.準(zhǔn)備階段(Prepare):事務(wù)管理器向所有參與者發(fā)送準(zhǔn)備提交的請求。如果所有參與者都準(zhǔn)備好了,則進(jìn)入提交階段;否則,進(jìn)入回滾階段。
b.提交(或回滾)階段:事務(wù)管理器向所有參與者發(fā)送提交或回滾的請求。所有參與者根據(jù)收到的指令執(zhí)行提交或回滾操作。
- XA的優(yōu)點(diǎn):
a.實(shí)現(xiàn)了強(qiáng)一致性,保證了數(shù)據(jù)的一致性。
- XA的缺點(diǎn):
a.性能問題:2PC協(xié)議涉及到多個(gè)網(wǎng)絡(luò)交互,性能較低。
b.單點(diǎn)故障:事務(wù)管理器是單點(diǎn),一旦故障,整個(gè)系統(tǒng)可能無法正常工作。
c.阻塞問題:參與者在準(zhǔn)備階段需要一直持有鎖,影響系統(tǒng)的并發(fā)性能。
AT(補(bǔ)償事務(wù))
AT是一種基于補(bǔ)償?shù)姆植际绞聞?wù)解決方案。它在業(yè)務(wù)操作之前,會先記錄操作的補(bǔ)償邏輯,如果業(yè)務(wù)操作失敗,則執(zhí)行補(bǔ)償邏輯來撤銷之前的操作。
- AT的三個(gè)階段:
a.Try階段:執(zhí)行業(yè)務(wù)操作,并記錄補(bǔ)償信息。
b.Confirm階段:如果Try階段成功,則提交事務(wù)。
c.Cancel階段:如果Try階段失敗,或者Confirm階段失敗,則執(zhí)行補(bǔ)償操作。
- AT的優(yōu)點(diǎn):
a.性能較好:避免了長時(shí)間的鎖等待。
b.靈活度高:可以自定義補(bǔ)償邏輯。
- AT的缺點(diǎn):
a.實(shí)現(xiàn)復(fù)雜:需要開發(fā)人員手動(dòng)編寫補(bǔ)償邏輯。
b.補(bǔ)償邏輯的正確性:補(bǔ)償邏輯的編寫難度較大,如果編寫不當(dāng),可能導(dǎo)致數(shù)據(jù)不一致。
SAGA
SAGA是一種長事務(wù)解決方案,它將長事務(wù)拆分成多個(gè)本地事務(wù),通過補(bǔ)償機(jī)制來保證最終一致性。
- SAGA的執(zhí)行過程:
a.正向操作:按照順序執(zhí)行一系列本地事務(wù)。
b.補(bǔ)償操作:如果某個(gè)本地事務(wù)執(zhí)行失敗,則按照逆序執(zhí)行相應(yīng)的補(bǔ)償事務(wù)。
- SAGA的優(yōu)點(diǎn):
a.靈活度高:可以根據(jù)業(yè)務(wù)需求靈活調(diào)整。
b.性能較好:避免了長事務(wù)帶來的性能問題。
- SAGA的缺點(diǎn):
a.實(shí)現(xiàn)復(fù)雜:需要仔細(xì)設(shè)計(jì)補(bǔ)償邏輯,保證最終一致性。
b.錯(cuò)誤處理復(fù)雜:需要考慮各種異常情況,并保證補(bǔ)償操作的冪等性。
2.Seata
https://seata.apache.org/zh-cn/
是一款開源的分布式事務(wù)解決方案,致力于提供高性能和簡單易用的分布式事務(wù)服務(wù)。 將為用戶提供了 AT、TCC、SAGA 和 XA 事務(wù)模式,為用戶打造一站式的分布式解決方案。
(1)AT 模式
一階段 - 執(zhí)行業(yè)務(wù)操作并記錄回滾日志: 會在多個(gè)服務(wù)中執(zhí)行業(yè)務(wù)操作并記錄回滾日志。
二階段 - 根據(jù)事務(wù)協(xié)調(diào)器的指令提交或回滾: 的事務(wù)協(xié)調(diào)器會決定最終是提交還是回滾整個(gè)分布式事務(wù)。
寫隔離
例:兩個(gè)并發(fā)的分布式事務(wù) T1 和 T2,初始值為 1000, 通過本地鎖和全局鎖的組合確保數(shù)據(jù)的一致性和隔離性。
讀隔離
在 的 AT 模式中,默認(rèn)的全局讀隔離級別是讀未提交(Read Uncommitted),這意味著事務(wù)在讀取數(shù)據(jù)時(shí)可能會讀到尚未提交的變更數(shù)據(jù)。這是為了提升性能,因?yàn)樽x未提交可以減少鎖的競爭。但在某些場景中,系統(tǒng)可能需要更高的隔離級別,即 讀已提交(Read Committed),這可以通過在查詢時(shí)使用 SELECT FOR UPDATE 語句來實(shí)現(xiàn)。
3.案例
https://github.com/apache/incubator-seata-samples
用戶購買商品的業(yè)務(wù)邏輯。
整個(gè)業(yè)務(wù)邏輯由 3 個(gè)微服務(wù)提供支持
- 倉儲服務(wù)(stock service):對給定的商品扣除倉儲數(shù)量。
- 訂單服務(wù)(order service):根據(jù)采購需求創(chuàng)建訂單。
- 帳戶服務(wù)(account service):從用戶賬戶中扣除余額。
圖片
(1)數(shù)據(jù)庫
創(chuàng)建 MySQL 數(shù)據(jù)庫:
$ docker run -d -p 3306:3306 --name seata -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0fb9c8e50a82 mysql:5.7 "docker-entrypoint.s…" 50 seconds ago Up 49 seconds 0.0.0.0:3306->3306/tcp, 33060/tcp seata
依次創(chuàng)建 stock、order 和 account 數(shù)據(jù)庫:
$ docker exec -it seata mysql -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.44 MySQL Community Server (GPL)
Copyright (c) 2000, 2023, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> CREATE DATABASE `order`;
Query OK, 1 row affected (0.00 sec)
mysql> CREATE DATABASE stock;
Query OK, 1 row affected (0.00 sec)
mysql> CREATE DATABASE account;
Query OK, 1 row affected (0.00 sec)
mysql> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| account |
| mysql |
| order |
| performance_schema |
| stock |
| sys |
+--------------------+
7 rows in set (0.00 sec)
(2)數(shù)據(jù)表
依次在 stock、order 和 account 數(shù)據(jù)庫創(chuàng)建 stock_tbl、order_tbl 和 account_tbl 數(shù)據(jù)表,每個(gè)數(shù)據(jù)庫附帶 undo_log 數(shù)據(jù)表:
CREATE DATABASE IF NOT EXISTS `stock`;
CREATE DATABASE IF NOT EXISTS `order`;
CREATE DATABASE IF NOT EXISTS `account`;
USE `stock`;
DROP TABLE IF EXISTS `stock_tbl`;
CREATE TABLE `stock_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`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
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,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
USE `order`;
DROP TABLE IF EXISTS `order_tbl`;
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;
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,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
USE `account`;
DROP TABLE IF EXISTS `account_tbl`;
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;
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,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
mysql> USE stock;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> SHOW TABLES;
+-----------------+
| Tables_in_stock |
+-----------------+
| stock_tbl |
| undo_log |
+-----------------+
2 rows in set (0.00 sec)
mysql> USE order;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> SHOW TABLES;
+-----------------+
| Tables_in_order |
+-----------------+
| order_tbl |
| undo_log |
+-----------------+
2 rows in set (0.00 sec)
mysql> USE account;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> SHOW TABLES;
+-------------------+
| Tables_in_account |
+-------------------+
| account_tbl |
| undo_log |
+-------------------+
2 rows in set (0.00 sec)
(3)啟動(dòng)服務(wù)
https://github.com/apache/incubator-seata/releases
選擇下載的是:https://github.com/apache/incubator-seata/releases/download/v1.5.2/seata-server-1.5.2.zip
解壓之后,進(jìn)入 seata-server-1.5.2/seata/bin 目錄,執(zhí)行:
$ sh seata-server.sh -p 8091 -h 127.0.0.1 -m file
apm-skywalking not enabled
seata-server is starting, you can check the /d/projects/seata/seata-server-1.5.2/seata/logs/start.out
查看日志:
$ tail -300 /d/projects/seata/seata-server-1.5.2/seata/logs/start.out
D:\SDK\Java\jdk1.8.0_202/bin/java -server -Dloader.path=.lib -Xmx2048m -Xms2048m -Xmn1024m -Xss512k -XX:SurvivorRatio=10 -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=1024m -XX:-OmitStackTraceInFastThrow -XX:-UseAdaptiveSizePolicy -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/d/projects/seata/seata-server-1.5.2/seata/logs/java_heapdump.hprof -XX:+DisableExplicitGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFractinotallow=75 -Xloggc:/d/projects/seata/seata-server-1.5.2/seata/logs/seata_gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -Dio.netty.leakDetectinotallow=advanced -Dapp.name=seata-server -Dapp.pid=452 -Dapp.home=/d/projects/seata/seata-server-1.5.2/seata -Dbasedir=/d/projects/seata/seata-server-1.5.2/seata -Dspring.config.locatinotallow=/d/projects/seata/seata-server-1.5.2/seata/conf/application.yml -Dlogging.cnotallow=/d/projects/seata/seata-server-1.5.2/seata/conf/logback-spring.xml -jar /d/projects/seata/seata-server-1.5.2/seata/target/seata-server.jar -p 8091 -h 127.0.0.1 -m file
???????????????[???????????????[ ???????????[ ?????????????????[ ???????????[
?????X?T?T?T?T?a?????X?T?T?T?T?a?????X?T?T?????[?^?T?T?????X?T?T?a?????X?T?T?????[
???????????????[???????????[ ???????????????U ?????U ???????????????U
?^?T?T?T?T?????U?????X?T?T?a ?????X?T?T?????U ?????U ?????X?T?T?????U
???????????????U???????????????[?????U ?????U ?????U ?????U ?????U
?^?T?T?T?T?T?T?a?^?T?T?T?T?T?T?a?^?T?a ?^?T?a ?^?T?a ?^?T?a ?^?T?a
:
16:33:03.227 INFO --- [ main] io.seata.server.ServerApplication : Starting ServerApplication v1.5.2 using Java 1.8.0_202 on LAPTOP-CJVNN4P6 with PID 12296 (D:\projects\seata\seata-server-1.5.2\seata\target\seata-server.jar started by solisamicus in D:\projects\seata\seata-server-1.5.2\seata\bin)
16:33:03.232 INFO --- [ main] io.seata.server.ServerApplication : No active profile set, falling back to default profiles: default
16:33:04.876 INFO --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 7091 (http)
16:33:04.885 INFO --- [ main] o.a.coyote.http11.Http11NioProtocol : Initializing ProtocolHandler ["http-nio-7091"]
16:33:04.886 INFO --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
16:33:04.886 INFO --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.55]
16:33:04.948 INFO --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
16:33:04.948 INFO --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1659 ms
16:33:05.441 INFO --- [ main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page: class path resource [static/index.html]
16:33:05.597 INFO --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure Ant [pattern='/'] with []
16:33:05.597 INFO --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure Ant [pattern='/**/*.css'] with []
16:33:05.597 INFO --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure Ant [pattern='/**/*.js'] with []
16:33:05.597 INFO --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure Ant [pattern='/**/*.html'] with []
16:33:05.597 INFO --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure Ant [pattern='/**/*.map'] with []
16:33:05.597 INFO --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure Ant [pattern='/**/*.svg'] with []
16:33:05.598 INFO --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure Ant [pattern='/**/*.png'] with []
16:33:05.598 INFO --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure Ant [pattern='/**/*.ico'] with []
16:33:05.598 INFO --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure Ant [pattern='/console-fe/public/**'] with []
16:33:05.598 INFO --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure Ant [pattern='/api/v1/auth/login'] with []
16:33:05.615 INFO --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@594d9f07, org.springframework.security.web.context.SecurityContextPersistenceFilter@118dcbbd, org.springframework.security.web.header.HeaderWriterFilter@2e26173, org.springframework.security.web.authentication.logout.LogoutFilter@2ecf5915, io.seata.console.filter.JwtAuthenticationTokenFilter@5befbac1, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@350ec690, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@34a2d6e0, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@5e9f73b, org.springframework.security.web.session.SessionManagementFilter@203d1d93, org.springframework.security.web.access.ExceptionTranslationFilter@2f74900b, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@3c6c4689]
16:33:05.639 INFO --- [ main] o.a.coyote.http11.Http11NioProtocol : Starting ProtocolHandler ["http-nio-7091"]
16:33:05.660 INFO --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 7091 (http) with context path ''
16:33:05.670 INFO --- [ main] io.seata.server.ServerApplication : Started ServerApplication in 3.177 seconds (JVM running for 3.761)
16:33:07.372 INFO --- [ main] i.s.core.rpc.netty.NettyServerBootstrap : Server started, service listen port: 8091
16:33:07.384 INFO --- [ main] io.seata.server.ServerRunner : seata server started in 1713 millSeconds
(4)運(yùn)行示例
依次啟動(dòng):
- DubboStockServiceStarter:初始化庫存數(shù)據(jù)(添加庫存記錄)。
- DubboAccountServiceStarter:初始化賬戶數(shù)據(jù)(添加用戶賬戶記錄)。
- DubboOrderServiceStarter:啟動(dòng)訂單服務(wù)(等待下單操作)。
- DubboBusinessTester:執(zhí)行測試場景(下單操作,觸發(fā)分布式事務(wù))。
啟動(dòng)賬戶服務(wù)。
初始化賬戶表數(shù)據(jù):
- 刪除用戶 U100001 的賬戶記錄(若存在)。
- 插入一條新記錄,給用戶 U100001 分配初始余額 999。
accountJdbcTemplate.update("delete from account_tbl where user_id = 'U100001'");
accountJdbcTemplate.update("insert into account_tbl(user_id, money) values ('U100001', 999)");
mysql> SELECT * FROM `account`.`account_tbl`;
+----+---------+-------+
| id | user_id | money |
+----+---------+-------+
| 1 | U100001 | 999 |
+----+---------+-------+
1 row in set (0.00 sec)
啟動(dòng)訂單服務(wù)。
訂單服務(wù)啟動(dòng)后,等待買家下單。
ClassPathXmlApplicationContext orderContext = new ClassPathXmlApplicationContext(
new String[] {"spring/dubbo-order-service.xml"});
orderContext.getBean("service");
new ApplicationKeeper(orderContext).keep();
mysql> SELECT * FROM `order`.`order_tbl`;
Empty set (0.00 sec)
啟動(dòng)庫存服務(wù)。
初始化庫存表數(shù)據(jù):
a.刪除商品 C00321 的庫存記錄(若存在)。
b.為商品 C00321 添加庫存 100。
stockJdbcTemplate.update("delete from stock_tbl where commodity_code = 'C00321'");
stockJdbcTemplate.update("insert into stock_tbl(commodity_code, count) values ('C00321', 100)");
mysql> SELECT * FROM `stock`.`stock_tbl`;
+----+----------------+-------+
| id | commodity_code | count |
+----+----------------+-------+
| 1 | C00321 | 100 |
+----+----------------+-------+
1 row in set (0.00 sec)
測試整個(gè)電商平臺。
模擬業(yè)務(wù)場景:用戶 U100001 購買商品 C00321,數(shù)量為 2。
final BusinessService business = (BusinessService)context.getBean("business");
business.purchase("U100001", "C00321", 2);
正常事務(wù)
選擇注釋 dubbo/src/main/java/io/seata/samples/dubbo/service/impl/BusinessServiceImpl.java
@Override
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-demo-tx")
public void purchase(String userId, String commodityCode, int orderCount) {
LOGGER.info("purchase begin ... xid: " + RootContext.getXID());
stockService.deduct(commodityCode, orderCount);
// just test batch update
//stockService.batchDeduct(commodityCode, orderCount);
orderService.create(userId, commodityCode, orderCount);
//if (random.nextBoolean()) {
// throw new RuntimeException("random exception mock!");
//}
}
mysql> USE account;
mysql> SELECT * FROM `account`.`account_tbl`;
+----+---------+-------+
| id | user_id | money |
+----+---------+-------+
| 1 | U100001 | 599 |
+----+---------+-------+
1 row in set (0.00 sec)
mysql> USE `order`;
mysql> SELECT * FROM `order`.`order_tbl`;
+----+---------+----------------+-------+-------+
| id | user_id | commodity_code | count | money |
+----+---------+----------------+-------+-------+
| 1 | U100001 | C00321 | 2 | 400 |
+----+---------+----------------+-------+-------+
1 row in set (0.00 sec)
mysql> USE `stock`;
mysql> SELECT * FROM `stock`.`stock_tbl`;
+----+----------------+-------+
| id | commodity_code | count |
+----+----------------+-------+
| 1 | C00321 | 98 |
+----+----------------+-------+
1 row in set (0.00 sec)
異常事務(wù)
選擇保留 dubbo/src/main/java/io/seata/samples/dubbo/service/impl/BusinessServiceImpl.java
@Override
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-demo-tx")
public void purchase(String userId, String commodityCode, int orderCount) {
LOGGER.info("purchase begin ... xid: " + RootContext.getXID());
stockService.deduct(commodityCode, orderCount);
// just test batch update
//stockService.batchDeduct(commodityCode, orderCount);
orderService.create(userId, commodityCode, orderCount);
if (random.nextBoolean()) {
throw new RuntimeException("random exception mock!");
}
}
圖片