分布式的事务该怎么做?
分布式八大坑
分布式就是魔鬼啊!?
張大胖最近十分感慨,他所在的公司原來有個電商系統,后來隨著用戶量越來越大,對系統的可用性要求越來越高。 CTO要求把系統進行拆分, 從一個單體的應用,拆分成微服務組成的應用。?
微服務聽起來很美好,但是其中的苦只有做過的人才知道。 ?
在原來的單體應用中,訂單模塊想要調用庫存和支付,只要調用相關的類或者接口就可以了,只有一個數據庫,輕輕松松就可以把所有操作放到一個事務當中,保證不會出現扣了庫存但是支付失敗的情況。?
(單體應用)??現在好了,系統成了分布式,原來的進程間調用變成了跨越網絡的HTTP調用,這數據庫也從單個數據庫變成了多個獨立的數據庫,原來的事務肯定是不起作用了!?
大神Bill告訴張大胖分布式有八個大坑, 千萬別跳到坑里去:??
當你在構建一個分布式系統的時候,可能會不由自主地做一些假設,這些假設從長期來看,都是錯誤的,都會導致大麻煩:?
1. 網絡是可靠的?
2. (調用)沒有延遲?
3. ?無限的帶寬?
4. 網絡是安全的?
5. (系統)拓撲結構不會改變?
6. 有個管理員在管理這個系統?
7. (數據)傳輸代價為零?
8. 網絡是同質的(同類的) ?
這第一條就很要命,網絡是不可靠的, 網絡調用失敗的可能性是非常高的,很有可能出現扣減了庫存,但是沒有支付的情況。?
?
分布式的事務
怎么樣讓扣減庫存和支付服務能在一個類似數據庫事務中完成,要么都做,要么都不做呢??張大胖覺得十分頭疼。?
張大胖首先想到了兩階段的提交(2PC),但是2PC是針對底層的數據資源層實現的,現在要做的是業務層的事情, 況且這2PC也很不好用啊。?
(碼農翻身注: 2PC的故事參見《Java帝國之宮廷內斗》)?
他覺得必須要有一個協調人居中協調各個微服務,讓他們處于一個“事務”中, 可是這協調者該如何實現??
Bill 遞給他一篇文章:“你要實現的就是分布式事務了!看看這篇文章吧!” ?
張大胖接過打印的文章,標題是:《Distributed Atomic Transactions over RESTful Services》 ,他的頭嗡地一聲就變大了,哀嘆道:“英文的啊,你還是給我講講吧!”?
“英文很重要啊,大胖同學!” Bill說道,“其實這個分布式事務的原理很簡單,它的精華就是凍結資源和冪等性。”
張大胖說:“這冪等性我知道,就是一個操作不管是執行一千次,一萬次,效果和執行一次是一樣的。 這凍結資源是怎么回事?”?
“拿咱們的系統舉個例子吧,訂單服務要調用庫存服務扣減庫存(假設數量為2),還要調用支付服務從用戶余額扣錢(假設為100), 那訂單服務第一步就告訴庫存服務,給我凍結2個庫存; 告訴支付服務,給我凍結100塊錢。在這一步,兩個服務要做業務檢查,看看庫存余額夠不夠,如果足夠,就凍結他們,防止其他調用也進行了扣減操作,導致本次調用余額不足。 這一步,我們稱之為嘗試(Try)。 ”?
?(注: 這里也可以對庫存數量和用戶余額做扣減) ?
庫存服務和支付服務操作的都是自己的表,凍結操作可以放到一個本地事務中,保證原子性。?
?“明白, 接下來呢?” 張大胖問道。?
?“這一步如果成功完成,訂單服務就可以進入第二步,告訴這兩個服務真正地執行扣減操作,這一步叫做Confirm。”?
?(注:如果在第一步已經做了扣減,這里只需要修改相關狀態即可,大家可自行腦補。 )?
“慢著,如果調用支付服務進行Confirm時出錯怎么辦? ” 張大胖問道。
?“很簡單,那就告訴庫存服務和訂單服務,都進行Cancel操作, 把凍結的數量進行恢復。”?
Bill說道。 “我們把這套機制叫做?Try - Confirm -Cancel,簡稱TCC。對于每個每個微服務來講,都要提供try , confirm , cancel這三個接口。” Bill接著說,“另外每個微服務也得有一個專門用來管理TCC的組件。”?
異常場景
張大胖心想,你說得簡單,這都是所謂Happy Path?, 在分布式環境中出錯是必然的,他很快找到了第一個場景:?
場景1?: 庫存服務的Try操作完成, 支付服務的Try操作沒有完成, 怎么辦? ?
Bill說:“這很好處理,訂單服務可以嘗試去調用庫存的Cancel操作(這應該是個冪等操作,可以多次調用),把凍結的庫存釋放。”?
張大胖說:“那如果出現網絡問題,訂單服務無法聯系庫存服務了呢?”?
“不用擔心,庫存服務的TCC組件能夠發現凍結的時間已經超時,會自動把凍結的庫存給釋放。” ?
場景2?: 兩個微服務的Try操作都完成, 然后發生網絡故障,導致兩個Confirm都無法進行?
Bill說: “和第一種情況一下,TCC組件會發現超時,釋放凍結的資源, 當然,凍結的這部分資源在釋放前的一段時間內不可以被使用。”?
“可是,如果庫存服務所在的機器已經掛掉了呢?怎么計算超時?” 張大胖問道。?
“這是個好問題,所以TCC系統必須得記錄日志,把那些沒有完成的事情記錄下來,持久化到硬盤上。這樣下次重啟就可以接著執行了。”?
場景3?: Try操作都已完成,資源已經凍結,在第三步中庫存服務Confirm成功,庫存做了扣減, 但是支付服務掛掉了,余額還處于凍結狀態, 怎么辦??
Bill 說道:“那可以多嘗試幾次, 讓支付服務做Confirm操作(很明顯,這個Confirm操作必須得是一個冪等操作才行)。如果實在是無法成功,那就可以讓庫存服務做Cancel操作。 如果還是不行,只有讓人工介入了。”?
怪不得Bill一直在強調冪等性,原來真正的作用是這樣啊。
?
轉向BASE?
??張大胖想了想,似乎各種情況都能覆蓋了, 但是還有實現層面的大問題:?
(1) 就是對于try (凍結資源), confrim , cancel(恢復資源)這樣的操作都需要程序員去寫代碼實現。?
比如對于支付服務, 至少的實現三個方法:?
tryPayment(......)?
confirmPayment(......)?
cancelPayment(......) ?
這樣TCC框架才可以去調用。 ?
(2) 還得自己搞個TCC框架。?
Bill 笑道: “那沒辦法,分布式就是這么煩人。TCC框架倒是有一些現成的,比如Atomikos,tcc-transaction,Hmily等, 但是那些try,confrim, cancel是業務方法,程序員必須得寫, 跑不掉的。” ?
“就沒有別的辦法了嗎?”
“有啊,也可以嘗試下另外一個最終一致性的模型,叫做BASE。” ?Bill隨手又遞過來一篇論文,名字是《BASE: An Acid Alternative》??
“有沒有搞錯 ! 又是英文的!” ?
“你要是不想看英文的,就去看看老劉寫的《Java帝國之宮廷內斗》吧!”
?
來源:碼農翻身
總結
以上是生活随笔為你收集整理的分布式的事务该怎么做?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis 缓存和 MySQL 数据如何
- 下一篇: Linux 最常用的脚本,值得学习收藏!