Seata 的動態降級需要結合配置中心的動態配置訂閱功能。動態配置訂閱,即通過配置中心監聽訂閱,根據需要讀取已更新的緩存值,ZK、Apollo、Nacos 等第三方配置中心都有現成的監聽器可實現動態刷新配置;動態降級,即通過動態更新指定配置參數值,使得 Seata 能夠在運行過程中動態控制全局事務失效(目前只有 AT 模式有這個功能)。
那麼 Seata 支持的多個配置中心是如何適配不同的動態配置訂閱以及如何實現降級的呢?下面從源碼的層面詳細給大家講解一番。
動態配置訂閱
Seata 配置中心有一個監聽器基準接口,它主要有一個抽象方法和 default 方法,如下:
io.seata.config.ConfigurationChangeListener
該監聽器基準接口主要有兩個實現類型:
- 實現註冊配置訂閱事件監聽器:用於實現各種功能的動態配置訂閱,比如 GlobalTransactionalInterceptor 實現了 ConfigurationChangeListener,根據動態配置訂閱實現的動態降級功能;
- 實現配置中心動態訂閱功能與適配:對於目前還沒有動態訂閱功能的 file 類型默認配置中心,可以實現該基準接口來實現動態配置訂閱功能;對於阻塞訂閱需要另起一個線程去執行,這時候可以實現該基準接口進行適配,還可以複用該基準接口的線程池;以及還有異步訂閱,有訂閱單個 key,有訂閱多個 key 等等,我們都可以實現該基準接口以適配各個配置中心。
這裏就用默認的 file 配置中心,以它的實現類 FileListener 舉例子,它的實現邏輯如下:
如上,
dataId:爲訂閱的配置屬性;
listener:配置訂閱事件監聽器,用於將外部傳入的 listener 作爲一個 wrapper,執行真正的變更邏輯,這裏特別需要注意的是,該監聽器與 FileListener 同樣實現了 ConfigurationChangeListener 接口,只不過 FileListener 是用於給 file 提供動態配置訂閱功能,而 listener 用於執行配置訂閱事件;
executor:用於處理配置變更邏輯的線程池,在 ConfigurationChangeListener#onProcessEvent 方法中用到。
FileListener#onChangeEvent 方法的實現讓 file 具備了動態配置訂閱的功能,它的邏輯如下:
無限循環獲取訂閱的配置屬性當前的值,從緩存中獲取舊的值,判斷是否有變更,如果有變更就執行外部傳入 listener 的邏輯。
ConfigurationChangeEvent 用於保存配置變更的事件類,它的成員屬性如下:
這裏的 getConfig 方法是如何感知 file 配置的變更呢?我們點進去,發現它最終的邏輯如下:
發現它是創建一個 future 類,然後包裝成一個 Runnable 放入線程池中異步執行,最後調用 get 方法阻塞獲取值,那麼我們繼續往下看:
allowDynamicRefresh:動態刷新配置開關;
targetFileLastModified:file 最後更改的時間緩存。
以上邏輯:
獲取 file 最後更新的時間值 tempLastModified,然後對比對比緩存值 targetFileLastModified,如果 tempLastModified > targetFileLastModified,說明期間配置有更改過,這時就重新加載 file 實例,替換掉舊的 fileConfig,使得後面的操作能夠獲取到最新的配置值。
添加一個配置屬性監聽器的邏輯如下:
configListenersMap 爲 FileConfiguration 的配置監聽器緩存,它的數據結構如下:
ConcurrentMap<String/*dataId*/, Set<ConfigurationChangeListener>> configListenersMap
從數據結構上可看出,每個配置屬性可關聯多個事件監聽器。
最終執行 onProcessEvent 方法,這個是監聽器基準接口裏面的 default 方法,它會調用 onChangeEvent 方法,即最終會調用 FileListener 中的實現。
動態降級
有了以上的動態配置訂閱功能,我們只需要實現 ConfigurationChangeListener 監聽器,就可以做各種各種的功能,目前 Seata 只有動態降級有用到動態配置訂閱的功能。
在「Seata AT 模式啓動源碼分析」這篇文章中講到,Spring 集成 Seata 的項目中,在 AT 模式啓動時,會用 用GlobalTransactionalInterceptor 代替了被 GlobalTransactional 和 GlobalLock 註解的方法,GlobalTransactionalInterceptor 實現了 MethodInterceptor,最終會執行 invoker 方法,那麼想要實現動態降級,就可以在這裏做手腳。
- 在 GlobalTransactionalInterceptor 中加入一個成員變量:
private volatile boolean disable;
在構造函數中進行初始化賦值:
ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION(service.disableGlobalTransaction)這個參數目前有兩個功能:
- 在啓動時決定是否開啓全局事務;
- 在開啓全局事務後,決定是否降級。
- 實現 ConfigurationChangeListener:
這裏的邏輯簡單,就是判斷監聽事件是否屬於 ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION 配置屬性,如果是,直接更新 disable 值。
- 接下來在 GlobalTransactionalInterceptor#invoke 中做點手腳
如上,disable = true 時,不執行全局事務與全局鎖。
- 配置中心訂閱降級監聽器
io.seata.spring.annotation.GlobalTransactionScanner#wrapIfNecessary
在 Spring AOP 進行 wrap 邏輯過程中,當前配置中心將訂閱降級事件監聽器。
更多精彩文章請關注作者維護的公衆號「後端進階」,這是一個專注後端相關技術的公衆號。
關注公衆號並回復「後端」免費領取後端相關電子書籍。
歡迎分享,轉載請保留出處。