Seata 分佈式事務初體驗

阿里技術(如何選擇分佈式事務解決方案?) 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 準備工作

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 模式嗯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章