阿里技術(如何選擇分佈式事務解決方案?) https://mp.weixin.qq.com/s/2AL3uJ5BG2X3Y2Vxg0XqnQ## 起初看了這個分佈式相關的信息,對於seata 有點想了解一下實現的精髓,之前公司內部也有大佬分享過seata 還不止一次的分享過 內部也實現了一個簡單的分佈式事務,藉此機會瞭解一下這個鬼東西。 Seata (Simple Extensible Autonomous Transaction Architecture) AT (Automatic Transaction) 模式
官方的文檔: https://seata.io/zh-cn/docs/overview/what-is-seata.html
一、AT (Automatic Transaction) 模式
AT 模式 完全是根據 上面 https://mp.weixin.qq.com/s/2AL3uJ5BG2X3Y2Vxg0XqnQ## 這篇博客copy 下來的,感覺比官方文檔說明更加的清楚,兩階段提交的變異品,總體來說還是十分不錯。Seata支持很多種模式 AT、TCC、XA、SAGA 但是AT具有創新性,我們就瞭解這個吧。
AT 模式的運行機制
下面我們重點說一下 AT 模式的運行機制:
- 全局事務依然是基於各個分支事務來完成。Seata Server 協調各個分支事務要麼一起提交,要麼一起回滾。
- 各個分支事務在運行時,Seata Client 通過對 SQL 執行的代理和攔截,通過解析 SQL 定位到行記錄,記錄下 SQL 執行前後的行數據快照,beforeImage 和 afterImage 共同構成了回滾日誌,回滾日誌記錄在獨立的表中。回滾日誌的寫入和業務數據的更改在在同一個本地事務中提交。
- 分支事務完成後,立即釋放對本地資源的鎖,然後給 Seata 協調器上報事務執行的結果。
- Seata 協調器彙總各個分支事務的完成情況**,生成事務提交或者回滾的決議,將決議下發給 Seata Client**。
- 如果決議是提交事務,則 Seata Client 異步清理回滾日誌;如果決議是回滾事務,則 Seata Client 根據回滾日誌進行補償操作,補償前會對比當前數據快照和 afterImage 是否一致,如果不一致則回滾失敗,需要人工介入。
AT模式限制
AT 模式通過自動生成回滾日誌的方式,使得業務方接入成本低,對業務入侵度很低,但是應用 AT 模式也有一些限制:
- AT 模式只支持基於 ACID 事務的關係數據庫。
- AT 模式是通過對 SQL 解析來完成的,對 SQL 語法的支持有限,使用複雜 SQL 時需要考慮兼容性。
- 目前不支持複合主鍵,業務表在設計時注意添加自增主鍵。
- 全局事務默認的隔離級別是讀未提交,但是通過 SELECT…FOR UPDATE 等語句,可以實現讀已提交的隔離級別。通過全局排它寫鎖,可以做到的隔離級別介於讀未提交和讀已提交之間。
二、實踐
2.1 準備工作
-
java 環境變量
-
mysql 本地安裝一臺
-
nacos 安裝,下載 nacos-1.2.1
https://github.com/alibaba/nacos/releases
啓動腳本 ./bin startup.sh -m standalone
訪問地址: http://127.0.0.1:8848/nacos/#
-
seata server 安裝
https://github.com/seata/seata/releases
參考這個文檔: https://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html
本地啓動seata-server 使用db-server sql 初始化腳本
https://github.com/seata/seata/blob/1.2.0/script/server/db/mysql.sql
修改server的配置文件 模式修改爲db,修改數據庫密碼
啓動腳本 sh ./bin/seata-server.sh -p 8091 -h 127.0.0.1 -m db
at 模式客戶端也需要undo_log 數據庫創建,github seata example 目錄下 也有sql
https://github.com/seata/seata/blob/1.2.0/script/client/at/db/mysql.sql -
下載github seata example
https://github.com/seata/seata-samples/tree/master/springboot-dubbo-seata/
然後根據官方的文檔: https://seata.io/zh-cn/docs/user/quickstart.html 即可體驗一下子。
這裏 客戶端的服務端都使用一個數據庫拉~
2.2 實踐
此時,我們已經下載好了 seata example 、啓動好了 seata server、nacos等等。
官方的體驗文檔: https://seata.io/zh-cn/docs/user/quickstart.html 這裏的前提是修改好了 數據庫密碼
依次啓動服務
然後去nacos 可以看到在線的服務
模擬買買買
然後去查看數據庫,水杯的數據的變化
http://localhost:8104/business/dubbo/buy
{
"userId": "1",
"commodityCode": "C201901140001",
"name": "demoData",
"count": 1,
"amount": 1
}
買買買的攔截Aop
從seata的邏輯流程圖中可以看到一絲的痕跡,攔截要做一些的處理,傳遞context 信息,爲後續業務接口的調用傳遞上下文信息。eg xid
/**
* init global transaction scanner
*
* @Return: GlobalTransactionScanner
*/
@Bean
public GlobalTransactionScanner globalTransactionScanner(){
return new GlobalTransactionScanner("dubbo-gts-seata-example", "my_test_tx_group");
}
連接池的代理
io.seata.rm.datasource.DataSourceProxy,爲啥要代理連接池?這個是執行sql的地方啊!從AT 模式: https://seata.io/zh-cn/docs/overview/what-is-seata.html 這個實踐的原理中有過介紹 ,業務發起方會將分佈式事務的上下文進行傳遞,如果當前執行事務有分佈式的上下文信息,那麼就會解析sql,查詢前鏡像:根據解析得到的條件信息,生成查詢語句,定位數據,執行業務 SQL:更新這條記錄,詢後鏡像:根據前鏡像的結果,通過 主鍵 定位數據。
這裏對於sql的解析, 兼容性真的需要水平。(AT 模式是通過對 SQL 解析來完成的,對 SQL 語法的支持有限,使用複雜 SQL 時需要考慮兼容性。目前不支持複合主鍵,業務表在設計時注意添加自增主鍵。)
/**
* init datasource proxy
* @Param: druidDataSource datasource bean instance
* @Return: DataSourceProxy datasource proxy
*/
@Bean
public DataSourceProxy dataSourceProxy(DruidDataSource druidDataSource){
return new DataSourceProxy(druidDataSource);
}
@Bean
public DataSourceTransactionManager transactionManager(DataSourceProxy dataSourceProxy) {
return new DataSourceTransactionManager(dataSourceProxy);
}
斷點看看 undo_log
{
"@class": "io.seata.rm.datasource.undo.BranchUndoLog",
"xid": "192.168.0.104:8091:2012950778",
"branchId": 2012950785,
"sqlUndoLogs": [
"java.util.ArrayList",
[
{
"@class": "io.seata.rm.datasource.undo.SQLUndoLog",
"sqlType": "UPDATE",
"tableName": "t_storage",
"beforeImage": {
"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
"tableName": "t_storage",
"rows": [
"java.util.ArrayList",
[
{
"@class": "io.seata.rm.datasource.sql.struct.Row",
"fields": [
"java.util.ArrayList",
[
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "id",
"keyType": "PRIMARY_KEY",
"type": 4,
"value": 1
},
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "count",
"keyType": "NULL",
"type": 4,
"value": 999
}
]
]
}
]
]
},
"afterImage": {
"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
"tableName": "t_storage",
"rows": [
"java.util.ArrayList",
[
{
"@class": "io.seata.rm.datasource.sql.struct.Row",
"fields": [
"java.util.ArrayList",
[
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "id",
"keyType": "PRIMARY_KEY",
"type": 4,
"value": 1
},
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "count",
"keyType": "NULL",
"type": 4,
"value": 998
}
]
]
}
]
]
}
}
]
]
}
註釋打開看看回滾的效果
三、總結
從程序的角度實踐,簡單的瞭解了一下seata的使用,瞭解了AT 模式對於二階段提交的改進的使用。動手實踐一下還是不錯的,GlobalTransactionScanner 攔截全局事務,註冊TM client、RM client 通過netty活server端通信,其中還有動態從配置中讀出配置等等接入,簡單的看看一下源代碼,學習了一波。結合 https://mp.weixin.qq.com/s/2AL3uJ5BG2X3Y2Vxg0XqnQ## 這篇文章,理論和實踐都玩了一下,seata example 中還玩了一下TCC 模式嗯。