"若干分散式事務框架的設計”與“我的偏見”
本文來談談我對若干分散式事務框架的看法,只談設計時導致無法輕易改變的硬傷(或者說我的偏見),其優點應該已表現在其文件中,不再贅述。至於我的偏見能不能成為你的偏見,請自行思考核實,僅供大家選型時開拓思路使用。
靶子
以下我略有了解的框架將成為靶子:
- TransactionsEssentials(atomikos免費版)
- tcc-transaction
- ByteTCC
- hmily
- tx-lcn
- GTS
- EasyTransaction
TransactionsEssentials
其是atomikos公司的兩階段提交事務的免費版本,說到兩階段提交大家第一印象應該都是慢,第二印象應該就是很方便,編碼少。
但實際上有多慢呢?可能大家都不太確定,我這裡有一組數字供大家參考,相同業務場景,兩個服務有資料協同需求:
- 若兩個服務在同一庫中,單庫事務可達600TPS+
- 但Atomikos速度為40-70TPS
當然,這裡沒有給出具體的場景與配置,確實差值也有點大,我當時也是不太相信的。但我從多個不同測試資料來源獲得的資料都較為類似,大家有空可以協助驗證下,然後評論給下資料。
所以對於TransactionsEssentials這個框架,我的偏見就是,太慢。
tcc-transaction
這是一個在GITHUB上開源了很久的框架了,其STAR數量接近3K,其程式碼簡潔易懂。但其有三個缺點:
- 慢
- 應用Crash時有機率導致持久化的事務狀態不正確
- 不支援框架冪
慢的原因
- 事務日誌使用一個數據庫的變長欄位儲存
- 會多次更新該欄位
- 該欄位儲存的內容不斷變長,使其不能在磁碟中原地更新,導致頁的分裂,或者導致該欄位從頁中移除,涉及大量IO
CRASH不一致的原因
- 其CRASH後純粹依賴事務日誌判斷全域性事務狀態(Trying,Confirming,Canceling)
- 然而該事務日誌記錄的事務狀態是無法與業務資料庫的事務狀態保持強一致的(若能,則需要引入2PC等手段,是不是很矛盾)
- 因此在Crash時導致事務日誌狀態不正確時,按照目前的設計是需要人工介入排查問題的
不支援框架冪等
這會導致業務開發工作量大大增加。
ByteTCC
ByteTCC是一個相容JTA規範的基於TCC機制的分散式事務管理器。
其實個人覺得基JTA規範擴充套件的TCC實現並非一個特別好的想法,其
- 強制Spring的PlatformTransactionManager要支援JTA,需要使用者修改原有的PlatformTransactionManager
- 使用了ByteTCC自行實現的UserTransaction(JTA相關介面),並在裡層整合"TCC"及“真JTA”的控制邏輯,這違反了程式設計裡的開閉原則
- 用自定義實現類替換掉了客戶原有實現中可能更為可靠的JTA/JDBC事務,即單機事務的程式碼邏輯也被改了
- 在JTA介面中整合“真JTA”及“TCC”的邏輯交錯在整個實現中,沒有很好地分離邏輯,不利於閱讀,也不利於修改
- 限制了使用其他的JTA實現
不知道大家有沒有聽懂我上面說了什麼,其實就是說,如果讓我來設計,我是儘量不會對原有邏輯進行修改,而是對邏輯進行擴充套件,這樣才能最大程度的程式的安全性,也能更好地與原有邏輯整合。
舉個例子,EasyTransaction裡就是基於擴充套件實現了各種功能,其能保證原有事務處理邏輯完全不變,僅僅只是外掛了TCC、可靠訊息等等的實現,同等情況下,其實現的理論風險會更小,並且EasyTransaction能無縫相容JTA事務以及EasyTransaction內的各種事務,並協調一起工作,而ByteTCC則由於其實現形式,難以簡單做到。
另外一個方面是其程式碼變得過於複雜,至少對我來說有理解難度,需要一些額外的知識支撐,不知道其他人的看法是怎樣的。
同時關於冪等,ByteTCC只支援Confirm及Cancel操作的冪等,不過這比很多框架都要強了。
hmily
這個框架的主要描述是“高效能分散式事務tcc方案開源框架”,個人感覺其之所以這麼聲稱是因為“採用disruptor框架進行事務日誌的非同步讀寫,與RPC框架的效能毫無差別”。
這裡有兩個個人認為的硬傷(也許是偏見吧):
- 非同步寫入事務日誌就等於TCC是不可靠的
- 持久化IO瓶頸才是一個分散式事務框架的主要瓶頸,其並非Disruptor框架主要針對的CPU瓶頸
為什麼不可靠
就一個簡單的問題吧,事務日誌儲存連線不上(網路斷掉/掉電了),這時非同步寫入的日誌放到記憶體了,然後遠端的訪問請求TRY發出去了,這個時候應用CRASH了。這就會導致TCC日誌不完整,從而導致事務無法恢復。
有一些觀點認為這些情況極其少見,不需處理,那我們併發程式設計時volatile之類的同步手段還需要用麼?
並且異常都是連鎖的,它並不是孤立出現的,我們無法預判會出現什麼異常情況,也有墨菲定律說,越擔心的事情越有可能發生,因此我們對於這類情況必然是雖然我們不能保證實現完美,但是我們的理論至少要使完美的。
非同步的Disruptor並不能解決矛盾的主要方面
我們知道,CPU的速度會比持久化IO的速度高很多個數量級,因此,基本上涉及持久化時,IO必然才是主要優化的目標。
因此我們做優化時,仿照KAFKA等,批量彙集資料,批量IO才是正確的解決之道。不做這個而去優化CPU效能這有點本末倒置,同時據我瞭解的多個測試結果中,hmily的效能都大幅不及EasyTransaction。
也不支援框架冪等
同上Tcc-transaction
tx-lcn
這個框架本質上是一個BestEffors 1PC的框架,是什麼意思呢,也就是大多數情況下,只要應用不Crash就不會導致不一致。
這裡帶來什麼矛盾呢?除非你不關心不一致,允許資料出錯不修復,但一旦出現數據不一致一定要修復的情況的話,就要走人工補償處理,或者呼叫相關的修復程式。
而人工補償處理,實際上,也就相當於人肉寫了一遍修復程式,而且人肉執行還沒留下程式碼,下次出問題還要人工再分析處理一遍。
因此,結論很明顯:
- 對於允許資料不一致的資料來說
- 用BestEffors 1PC挺好的,效能高於2PC,程式碼量與2PC一樣。
- 但是對於資料出現不一致時,必須修復的情況
- 我們必須要寫對應的修復程式
- 這實際上跟TCC/補償等工作量一樣了
- 為啥不切換到TCC/補償等效能更加高的形式
- 並且使用BestEffors1PC寫的業務程式碼在出現資料異常時,並不能保證其後續的補償是可執行的。
- 舉個例子,轉賬,出現了給別人加錢了,但是自己沒有扣錢的資料異常,此時兩位客戶就有可能立馬把錢取出來用掉
tx-lcn這個框架是有適用場景的,但是我個人覺得最好把相關的厲害關係放到險要位置,不然有巨多小白無腦就用了,而不知道其中的坑,我覺得不太好。
GTS
這個框架的偏見嘛,主要就是髒讀了。貼一段之前寫過的文字。
GTS確實很贊,其核心原理是補償。
但這個補償做得很屌,補償操作由框架自動生成,無需業務干預,框架會記錄修改前的記錄值到上面的txc_undo_log裡,若需要回滾,則拿出undo_log的記錄覆蓋回原有記錄
同時這裡存在一個事務隔離級別的問題,GTS的做法是預設髒讀,那麼就可以直接拿資料庫記錄展示(但個人覺得應該可以不做髒讀,直接拿undo_log裡的記錄做mvcc,只要undo_log記錄不大,都可以載入到記憶體裡)。
還有另外一個問題是如何禁止其他事務對進行中的全域性事務記錄的更新,GTS的做法是需要接管APP中的資料來源,這樣就可以解析控制業務要執行的SQL,對於update操作(或者select for update),予以禁止或等待。
不過整體的做法相當於魔改資料庫,將資料庫的部分功能拉到了業務APP裡進行,並修改了預設隔離級別(髒讀,如果業務有用資料庫記錄樂觀鎖來控制併發的話,將會失效),還有就是,不通過GTS的定製資料來源訪問會訪問修改到未提交資料
EasyTransaction
這個嘛,是我自己寫的框架,上面出現的偏見,在我這裡都不會有,這篇文章本質是個軟文,哈哈,所以ET在我這沒有偏見,但我彙總下ET的優點把:
- 真正的高效能,對IO做了大量優化,多個獨立三方公司橫向評測分散式事務框架時,同等場景及同等可靠性保證下效能最佳(可自行驗證測試)
- 理論上只要外部元件不丟資料,在ET內部是不會出現事務不完整的情況(相對於上面一些框架,其原理就不可靠,執行出現異常可以說是必然的)
- 支援框架冪等,業務程式程式不再需要接管 冪等、呼叫時序錯亂處理等繁瑣重複邏輯(這個是一個繁瑣重複的工作,貌似只有ET對本項進行了完整支援)
- 基於擴充套件的實現,而非基於修改的實現,更易理解,風險更小,功能更強大
- 支援多種事務形態(TCC,補償,可靠訊息,SAGAs等)混合使用,可按照最適合業務的選擇最貼切的事務形態(這時ET建立之初的理念及特點,也是其他框架所不具有的特性)
總結
以上的偏見是不成熟的小想法,若有不正確,各位大佬儘管在評論區拍磚。同時本文僅供各位大佬選型時開拓思路,我認為的偏見不一定就是你的偏見,可能僅僅只是考慮角度、設計理念不一樣而已。
個人認為EasyTransaction的理念、設計、可靠性、效能等都不會比上面的框架差,但ET的STAR數量卻不及上面的各個開源框架,我覺得一定是ET有什麼我自己沒有察覺到的缺陷,請大家拍磚以促進本框架進步,可以直接評論,或者到GITHUB上提ISSUE,感謝你的改進建議!
https://github.com/QNJR-GROUP/EasyTransaction