本文來自 Apache Seata官方文檔,歡迎訪問官網,查看更多深度文章。
本文來自 Apache Seata官方文檔,歡迎訪問官網,查看更多深度文章。
Mac下的Seata Demo環境搭建(AT模式)
前言
最近因為工作需要,研究學習了Seata分布式事務框架,本文把自己學習的知識記錄一下
Seata總覽
cloc代碼統計
先看一下seata項目cloc代碼統計(截止到2020-07-20)
Java代碼行數大約是 97K
代碼質量
單元測試覆蓋率50%
Demo代碼
本文講的Demo代碼是seata-samples項目下的seata-samples-dubbo模塊,地址如下:
https://github.com/apache/incubator-seata-samples/tree/master/dubbo
解決的核心問題
AT模式的Demo例子給出了一個典型的分布式事務場景:
- 在一個采購交易中,需要:
- 扣減商品庫存
- 扣減用戶賬號余額
- 生成采購訂單
- 很明顯,以上3個步驟必須:要么全部成功,要么全部失敗,否則系統的數據會錯亂
- 而現在流行的微服務架構,一般來說,庫存,賬號余額,訂單是3個獨立的系統
- 每個微服務有自己的數據庫,相互獨立
這里就是分布式事務的場景。
解決方案
AT模式解決這個問題的思路其實很簡單,一句話概括就是:
在分布式事務過程中,記錄待修改的數據修改前和修改后的值到undo_log表,萬一交易中出現異常,通過這個里的數據做回滾
當然,具體代碼實現起來,我相信很多細節遠沒這么簡單。
Demo代碼結構
從github上clone最新的代碼
git clone git@github.com:apache/incubator-seata-samples.git
閱讀Demo代碼結構
$ cd seata-samples/dubbo/
$ tree -C -I 'target' .
.
├── README.md
├── pom.xml
├── seata-samples-dubbo.iml
└── src└── main├── java│ └── io│ └── seata│ └── samples│ └── dubbo│ ├── ApplicationKeeper.java│ ├── Order.java│ ├── service│ │ ├── AccountService.java│ │ ├── BusinessService.java│ │ ├── OrderService.java│ │ ├── StorageService.java│ │ └── impl│ │ ├── AccountServiceImpl.java│ │ ├── BusinessServiceImpl.java│ │ ├── OrderServiceImpl.java│ │ └── StorageServiceImpl.java│ └── starter│ ├── DubboAccountServiceStarter.java│ ├── DubboBusinessTester.java│ ├── DubboOrderServiceStarter.java│ └── DubboStorageServiceStarter.java└── resources├── file.conf├── jdbc.properties├── log4j.properties├── registry.conf├── spring│ ├── dubbo-account-service.xml│ ├── dubbo-business.xml│ ├── dubbo-order-service.xml│ └── dubbo-storage-service.xml└── sql├── dubbo_biz.sql└── undo_log.sql13 directories, 27 files
-
在io.seata.samples.dubbo.starter包下的4個*Starter類,分別模擬上面所述的4個微服務
- Account
- Business
- Order
- Storage
-
4個服務都是標準的dubbo服務,配置文件在seata-samples/dubbo/src/main/resources/spring目錄下
-
運行demo需要把這4個服務都啟動起來,Business最后啟動
-
主要的邏輯在io.seata.samples.dubbo.service,4個實現類分別對應4個微服務的業務邏輯
-
數據庫信息的配置文件:src/main/resources/jdbc.properties
時序圖
Ok, 趕緊動手, Make It Happen!
運行Demo
MySQL
建表
執行seata-samples/dubbo/src/main/resources/sql的腳本dubbo_biz.sql和undo_log.sql
mysql> show tables;
+-----------------+
| Tables_in_seata |
+-----------------+
| account_tbl |
| order_tbl |
| storage_tbl |
| undo_log |
+-----------------+
4 rows in set (0.01 sec)
執行完之后,數據庫里應該有4個表
修改seata-samples/dubbo/src/main/resources/jdbc.properties文件
根據你MySQL運行的環境修改變量的值
jdbc.account.url=jdbc:mysql://localhost:3306/seata
jdbc.account.username=your_username
jdbc.account.password=your_password
jdbc.account.driver=com.mysql.jdbc.Driver
# storage db config
jdbc.storage.url=jdbc:mysql://localhost:3306/seata
jdbc.storage.username=your_username
jdbc.storage.password=your_password
jdbc.storage.driver=com.mysql.jdbc.Driver
# order db config
jdbc.order.url=jdbc:mysql://localhost:3306/seata
jdbc.order.username=your_username
jdbc.order.password=your_password
jdbc.order.driver=com.mysql.jdbc.Driver
ZooKeeper
啟動ZooKeeper,我的本地的Mac是使用Homebrew安裝啟動的
$ brew services start zookeeper
==> Successfully started `zookeeper` (label: homebrew.m$ brew services list
Name Status User Plist
docker-machine stopped
elasticsearch stopped
kafka stopped
kibana stopped
mysql started portman /Users/portman/Librar
y/LaunchAgents/homebrew.mxcl.mysql.plist
nginx stopped
postgresql stopped
redis stopped
zookeeper started portman /Users/portman/Librar
y/LaunchAgents/homebrew.mxcl.zookeeper.plist
啟動TC事務協調器
在這個鏈接里頁面中,下載對應版本的seata-server程序,我本地下載的是1.2.0版本
- 進入文件所在目錄并解壓文件
- 進入seata目錄
- 執行啟動腳本
$ tar -zxvf seata-server-1.2.0.tar.gz
$ cd seata
$ bin/seata-server.sh
觀察啟動日志是否有報錯信息,如果一切正常,并看到了以下的Server started的信息,說明啟動成功了。
2020-07-23 13:45:13.810 INFO [main]io.seata.core.rpc.netty.RpcServerBootstrap.start:155 -Server started ...
IDE中啟動模擬的微服務
- 首先要把seata-samples項目導入到本地IDE中,這里我用的是IntelliJ IDEA
- 刷新Maven的工程依賴
- 先啟動Account,Order,Storage這個3個服務,然后Business才能去調用,對應的啟動類分別是:
io.seata.samples.dubbo.starter.DubboStorageServiceStarter
io.seata.samples.dubbo.starter.DubboOrderServiceStarter
io.seata.samples.dubbo.starter.DubboStorageServiceStarter
每個服務啟動完之后,看到這句提示信息,說明服務啟動成功了
Application is keep running ...
啟動成功后,account_tbl,storage_tbl表會有兩條初始化的數據,分別是賬戶余額和商品庫存
mysql> SELECT * FROM account_tbl; SELECT * FROM storage_tbl;
+----+---------+-------+
| id | user_id | money |
+----+---------+-------+
| 1 | U100001 | 999 |
+----+---------+-------+
1 row in set (0.00 sec)+----+----------------+-------+
| id | commodity_code | count |
+----+----------------+-------+
| 1 | C00321 | 100 |
+----+----------------+-------+
1 row in set (0.00 sec)
使用Business驗證效果
正常情況
還是在IDE中執行DubboBusinessTester類的主函數,程序跑完會自動退出
在程序一切正常的情況下,每個微服務的事物都應該是提交了的,數據保持一致
我們來看一下MySQL中數據的變化
mysql> SELECT * FROM account_tbl; SELECT * FROM order_tbl; SELECT * FROM storage_tbl;
+----+---------+-------+
| id | user_id | money |
+----+---------+-------+
| 1 | U100001 | 599 |
+----+---------+-------+
1 row in set (0.00 sec)+----+---------+----------------+-------+-------+
| id | user_id | commodity_code | count | money |
+----+---------+----------------+-------+-------+
| 1 | U100001 | C00321 | 2 | 400 |
+----+---------+----------------+-------+-------+
1 row in set (0.00 sec)+----+----------------+-------+
| id | commodity_code | count |
+----+----------------+-------+
| 1 | C00321 | 98 |
+----+----------------+-------+
1 row in set (0.00 sec)
從3個表的數據可以看到:賬戶余額扣減了400塊;訂單表增加了1條記錄;商品庫存扣減了2個
這個結果是程序的邏輯是一致的,說明事務沒有問題
異常情況
其實即使不加入分布式事務的控制,一切都正常情況下,事務本身就不會有問題的
所以我們來重點關注,當程序出現異常時的情況
現在我把BusinessServiceImpl的拋異常的代碼注釋放開,然后再執行一次DubboBusinessTester,來看看有什么情況發生
@Override@GlobalTransactional(timeoutMills = 300000, name = "dubbo-demo-tx")public void purchase(String userId, String commodityCode, int orderCount) {LOGGER.info("purchase begin ... xid: " + RootContext.getXID());storageService.deduct(commodityCode, orderCount);orderService.create(userId, commodityCode, orderCount);//放開這句拋異常的注釋,模擬程序出現異常throw new RuntimeException("portman's foooooobar error.");}
接著,我再一次執行DubboBusinessTester,執行過程中在控制臺可以看到異常報錯信息
Exception in thread "main" java.lang.RuntimeException: portman's foooooobar error.
現在我們再看一下MySQL里的數據變化,發現數據沒有任何變化,說明分布式事務的控制已經起作用了
待思考問題
上面的步驟只是演示了seata最簡單的demo程序,更多更復雜的情況后續大家可以一起討論和驗證
學習過程中還有一些問題和疑惑,后續進一步學習
- 全局鎖對性能的影響程度
- undo_log日志可以回滾到原來狀態,但是如果數據狀態已經發生變化如何處理(比如增加的用戶積分已經被別的本地事務花掉了)
參考文獻
- Seata 是什么?
- 快速開始
作者信息
許曉加,金蝶軟件架構師
Github