四月初,去面試了本市的一家以前在作辦公室無人貨架的公司,雖然他們如今在面臨着轉型,可是對於我這種想從傳統企業往互聯網行業走的孩子來講,仍是比較有吸引力的。java
在面試過程當中就提到了分佈式事務問題。我又一次在沒有好好整理的問題上吃了虧,記錄一下,仍是長記性 !!!git
面試官先是在紙上先畫了這樣一張圖: github
讓我看這張圖按照上面的流程走,有沒有什麼問題?面試官並無直接說出來這裏面會有分佈式事務的問題,而是讓我來告訴他,這就是面試套路呀。web
我回答了這中間可能存在分佈式事務的問題,當步驟 2 在調用 B 系統時,可能存在B 系統處理完成後,在響應的時候超時,致使 A 系統誤認爲 B 處理失敗了,從而致使A 系統回滾,跟 B 系統存在數據不一致的狀況。面試
ok ,我回答到這裏,應該回答了面試官的第一層意思,至少我有這種意識,他點了點頭。sql
接着,他繼續問:「那你有什麼好的解決方式嗎?」數據庫
此時我腦子裏面只有兩階段提交的大概流程圖的印象,而後巴卡巴拉的跟他說了一番,什麼中間來個協調者呀,先預提交什麼的,若是有失敗,就 rollback,若是 ok,再真正的提交事務,就是網上這些大神們說的這些理論。服務器
而後面試官就繼續問:那A 在調用 B 的這條線斷了,大家代碼具體是怎麼處理的呢 ?怎麼來作到 rollback 的呢 ?說說你代碼怎麼寫的。網絡
此時,我懵了。併發
最後結果,你們確定也能猜到,涼涼。
這裏咱們說的事務通常是指 數據庫事務,簡稱 事務,是數據庫管理系統執行過程當中的一個邏輯單位,由一個有限的數據庫操做序列構成。維基百科中這麼說的。
用轉帳的例子來講,A 帳戶要給 B 帳戶轉 100塊,這中間至少包含了兩個操做:
在支持事務的數據庫管理系統來講,就是得確保上面兩個操做(整個「事務」)都能完成,不能存在,A 的100塊扣了,而後B 的帳戶又沒加上去的狀況。
數據庫事務包含了四個特性,分別是:
咱們知道,上面的轉帳 咱們是在一個數據庫中的事務操做。咱們可使用一些框架 好比 Spring 的事務管理器來給咱們統一搞定。
可是若是咱們系統中出現垮庫操做,好比一個操做中,我須要操做多個庫,或者說這個操做會垮應用以前的調用,那麼Spring 的事務管理機制就對這種場景沒有辦法了。
就像上面面試題中出現的問題同樣,在系統 A 的步驟 2 在遠程調用 B 的時候,因爲網絡超時,致使B 沒有正常響應,可是A 這邊調用失敗,進行了回滾,而 B 又提交了事務。此時就可能會致使數據不一致的狀況,參生分佈式事務的問題。
分佈式事務的存在,就是解決數據不一致的狀況。
分佈式系統中有這麼一個廣爲流傳的理論:CAP 定理
這個定理呀,起源於加州大學柏克萊分校(University of California, Berkeley)的計算機科學家埃裏克·布魯爾在 2000年的分佈式計算原理研討會(PODC)上提出的一個猜測。後來在2002年,麻省理工學院(MIT)的賽斯·吉爾伯特和南希·林奇發表了布魯爾猜測的證實,使之成爲一個定理。【摘自維基百科】
他說呀,對於一個分佈式計算系統來講,不可能同時知足如下三點:
而一個分佈式系統最多隻能知足其中的兩項。
那麼,上面的三點分別是什麼玩意兒?爲何又只能同時知足兩項呢?
咱們先看這樣一個場景,如今咱們系統部署了兩份(兩個節點,web1 和 web2 ),一樣的業務代碼,可是維護的是本身這個節點生成的數據。可是用戶訪問進來,可能會訪問到不一樣的節點。可是無論是訪問web1 仍是web2 ,在用戶參數數據 事後,這個數據都必須得同步到另外的節點,讓用戶無論訪問哪一個節點,都是響應他須要的數據。以下圖:
咱們先說 分區容錯性:也就是說呀,就算上面這兩個節點之間發生了網絡故障,沒法發生同步的問題,可是用戶訪問進來,無論到哪一個節點,這個節點都得單獨提供服務,這一點對於互聯網公司來講,是必需要知足的。
當 web1 和 web2 之間的網絡發生故障,致使數據沒法進行同步。用戶在web1 上寫了數據,立刻又訪問進來讀取數據,請求到了 web2,可是此時 web2 是沒有數據的。那麼咱們是 給用戶返回 null ?仍是說給一些提示,說系統不可用,稍後重試呢?
都不妥吧,兄弟。
若是要保證可用性,那麼有數據的節點返回數據,沒數據的節點返回 null ,就會出現用戶那裏看到的是一下子有數據,一下子沒有數據,此時就存在 一致性 的問題。
若是保證一致性,那麼在用戶訪問的時候,無論 web1 仍是web2 ,咱們可能會返回一些提示信息,說系統不可用,稍後再試等等,保證每次都是一致的。明明咱們有數據在,可是咱們系統卻響應的是提示信息,此時就是 可用性 的問題。
因爲分區容錯性(P)是必須保證的,那麼咱們分佈式系統就更可能是在一致性(CP) 和可用性(AP)上作平衡了,只能同時知足兩個條件。
其實,你們想一想,ZK 是否是就是嚴格實現了 CP ,而 Eureka 則是保證了 AP。
其實分佈式事務強調的就是一致性。
在說 2PC 以前,咱們先了解一下 XA規範 是個什麼東西?
XA規範 描述了全局的事務管理器與局部的資源管理器之間的接口。XA規範的目的是容許多個資源(如數據庫,應用服務器,消息隊列,等等)在同一事務中訪問,這樣可使ACID屬性跨越應用程序而保持有效。
XA 使用 兩階段提交(2PC) 來保證全部資源同時提交或回滾任何特定的事務。
你們想一個場景,在作單應用的時候,有的同窗連過兩個庫吧?在一個事務中會同時向兩個系統插入數據。可是對於普通事務來說,是管不了的。
看下圖(只是舉例這種操做的套路,不侷限於下面的業務):
一個服務裏面要去操做兩個庫,如何保證事務成功呢。
這裏咱們介紹一個框架 Atomikos ,他就是實現了這種 XA 的套路。看代碼:
具體代碼移步 Github AtomikosJTATest: github.com/heyxyw/lear…
看到上面的圖了哇,Atomikos 本身實現了一個事務管理器。咱們獲取的鏈接都從它哪裏拿。
這個過程是否是就有兩個角色了,一個 事務管理器,一個資源管理器(咱們這裏是 數據庫,也能夠是其餘的組件,消息隊列什麼的)。
整個執行過程是這樣:
上圖是正常狀況,下圖是一方出現故障的狀況。圖片來自:XA 事務處理:www.infoq.cn/article/xa-… ,具體關於XA 的詳細講解,能夠好好看看。整個2PC 的流程:
第一階段(提交請求階段):
第二階段 (提交執行階段):
成功,當協調者節點從全部參與者節點得到的相應消息都爲"贊成"時:
失敗,若是任一參與者節點在第一階段返回的響應消息爲"終止",或者 協調者節點在第一階段的詢問超時以前沒法獲取全部參與者節點的響應消息時:
有時候,第二階段也被稱做完成階段,由於不管結果怎樣,協調者都必須在此階段結束當前事務。
上面咱們說了兩階段提交的方案,接下來咱們講講怎麼基於可靠消息最終一致性方案來解決分佈式事務的問題。
這個方案,就有消息服務中間件角色參與進來了。咱們先看一個大提的流程圖:
咱們以建立訂單下單過程和 後面出庫 的流程爲例來說述上面的圖。
在下單邏輯裏面(Producer 端),咱們先生成一個訂單的數據,好比訂單號,數量等關鍵的信息,先包裝成一條消息,並把消息的狀態置爲 init ,而後發送到 獨立消息服務中,而且入庫。
接下來繼續處理 下單的其餘本地的邏輯。
處理完成後,走到確認發送消息這一步,說明個人訂單是可以下成功的。那麼咱們再向消息服務裏面發送一條confirm 的消息,消息服務裏面就能夠把這個訂單的消息狀態修改成 send 而且,發送到消息隊列裏面。
接下來,消費者端去消費這條消息。處理本身這邊的邏輯,處理完成之後,而後反饋消息處理結果到獨立消息服務,獨立消息服務把消息狀態置爲 end 狀態 ,表示結束。可是得注意保證接口的冪等性,避免重複消費帶來的問題。
這裏面可能出現的問題,以及各個步驟怎麼解決的:
貼一下關鍵的獨立消息服務核心邏輯代碼框架:
定時任務:
這種方案,跟上面的獨立消息服務一致,這裏直接去掉獨立服務,只利用消息隊列來實現,也就是阿里的 RocketMQ 。
流程圖以下:
這裏的整個流程跟上面基於消息服務是一致的。這裏就不過多闡述,具體代碼實現請參考 :www.jianshu.com/p/453c6e7ff… ,寫得很是好。
針對這裏的 可靠消息最終一致性方案 來講,咱們說的 可靠 是指保證消息必定能發送到消息中間件裏面去,保證這裏可靠。
對於下游的系統來講,消費不成功,通常來講就是採起失敗重試,重試屢次不成功,那麼就記錄日誌,後續人工介入來進行處理。因此這裏得強調一下,後面的系統,必定要處理 冪等,重試,日誌 這幾個東西。
若是是對於資金類的業務,後續系統回滾了之後,得想辦法去通知前面的系統也進行回滾,或者是發送報警由人工來手工回滾和補償。
TCC 的全程分爲三個階段,分別是 Try、Confirm、Cancel:
仍是以轉帳的例子爲例,在跨銀行進行轉帳的時候,須要涉及到兩個銀行的分佈式事務,從A 銀行向 B 銀行轉 1 塊,若是用TCC 方案來實現:
大概思路就是這樣的:
這種方案就比較複雜了,一步操做要作多個接口來配合完成。
以 ByteTCC 框架的實現例子來大概描述一下上面的流程,示例地址 gitee.com/bytesoft/By…
最開始 A 銀行帳戶 與 B 銀行帳戶都分別爲:amount(數量)=1000,frozen(凍結金額)= 0
從A銀行帳戶發起轉帳到 B 銀行帳戶 1 塊:
try 階段:A 銀行帳戶金額減 1,凍結金額 加 1,B 銀行 帳戶 凍結金額加 1 。
此時:
confirm 階段 : A銀行帳戶凍結金額 減 1,B 銀行帳戶金額 加 1,凍結金額 減 1
此時:
cancel 階段: A 銀行帳戶金額 + 1,凍結金額 -1 ,B 銀行 凍結金額 -1
此時:
至此,整個過程就演示完畢,你們記得跑一遍代碼。其實仍是蠻複雜的,有許多接口一塊兒來配合完成整個業務,試想一下,若是咱們項目中大量用到 TCC 來寫,你受得了?
BASE 理論是 Basically Available(基本可用),Soft State(軟狀態)和Eventually Consistent(最終一致性)三個短語的縮寫。
其核心思想是:
即便沒法作到強一致性(Strong consistency),但每一個應用均可以根據自身的業務特色,採用適當的方式來使系統達到最終一致性(Eventual consistency)
到這裏你們再想一想, 上面 TCC 方案中的帳戶設計了一個凍結字段 frozen ,這裏是否是就是 BASE理論 中間的 軟狀態 呢 ?
對存在很是多的微服務的公司來講,服務之間的調用異常的複雜,那麼在引入分佈式事務的過程當中,你須要考慮加入分佈式事務後,系統實現起來的複雜性和開發成本,或者說哪些地方根本就不須要搞分佈式事務。
其實不必處處都搞分佈式事務,對於大多數的業務來講,其實咱們並不須要作分佈式事務,直接作日誌,作監控就行了。而後出現問題,手工去處理,一個月也不會有那麼多的問題的。若是你每天都出現這些問題,你是否是要好好去排查排查你的代碼Bug了。
對於資金類的場景,那麼基本上會採用分佈式事務方案來保證,像其餘的服務,會員,積分,商品信息呀這些,可能就不須要這麼去搞了。