深刻淺出JAVA分佈式事物

什麼是分佈式事物

  分佈式事務就是指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位於不一樣的分佈式系統的不一樣節點之上。以上是百度百科的解釋,簡單的說,就是一次大的操做由不一樣的小操做組成,這些小的操做分佈在不一樣的服務器上,且屬於不一樣的應用,好比從一個oracle中存一個記錄,再從另外一個oracle中刪除那條記錄,分佈式事務須要保證這些小操做要麼所有成功,要麼所有失敗。本質上來講,分佈式事務就是爲了保證不一樣數據庫的數據一致性html

事物涉及的對象

  資源:應用程序存儲和獲取數據的地方,能夠是數據庫,文件,也能夠是內存。若是是應用程序的事務塊代碼中涉及到的數據庫,文件,內存,那這些資源就稱爲事務型資源java

  資源管理器:在事務模型中,應用不是直接訪問資源,而是經過中間介訪問資源,這個中間介就叫資源管理器。資源分爲可持久化資源(對應了持久化資源管理),易失資源(對應了易失資源管理器spring

  事務管理器:實現事務的開始,提交,回滾sql

事務管理器的類型

  輕量級事務管理器:做用於開啓事務的應用程序域,只能包含一個持久化資源,若是再添加一個持久化資源,將被輕量級事務管理器忽略。可是能夠登記多個易失資源數據庫

 內核事務管理器:引入內核事務管理器主要是把文件管理(NTFS文件系統)和註冊表管理歸入事務範疇。咱們將那些支持事務的文件系統和註冊表叫做事務型文件系統(TxF)和事務型註冊表(TxR)。 之因此稱爲內核事務管理器,是因它運行在內核模式上,而不是在用戶模式上。一樣內核事務管理器只支持一個持久化資源編程

  分佈式事務協調器:分佈式事務協調器可以管理一個分佈式事務涉及的全部事務型資源,無論具體的事務型資源分佈在何處,每一臺電腦上只有一個分佈式事務協調器,它管理了當前計算機的全部事務資源。它能夠跨程序域,跨進程,跨機器,跨網絡來執行事務。當事務跨機器時,每臺機器的分佈式事務協調器按照相應的協議工做,實現對整個事務的管理,它對應的事務管理協議有Ole-Tx和WS-Atomic Transaction(WS-AT)這些。segmentfault

本地事物

  事務由資源管理器(如DBMS)本地管理,輕量級事務管理器,內核事務管理器都只支持本地事務服務器

  優勢:支持嚴格的ACID屬性,可靠高效,狀態能夠只在資源管理器中維護,應用編程模型簡單(在框架或平臺的支持)網絡

  侷限:不具有分佈事務處理能力,隔離的最小單位由資源管理器決定,如數據庫中的一條記錄架構

  以支付寶轉帳餘額寶爲例,假設有

  • 支付寶帳戶表:A(id,userId,amount)
  • 餘額寶帳戶表:B(id,userId,amount)
  • 用戶的userId=1;

  從支付寶轉帳1萬塊錢到餘額寶的動做分爲兩步:

  • 1)支付寶表扣除1萬:update A set amount=amount-10000 where userId=1;
  • 2)餘額寶表增長1萬:update B set amount=amount+10000 where userId=1;

  如何確保支付寶餘額寶收支平衡呢?有人說這個很簡單嘛,能夠用事務解決

Begin transaction
         update A set amount=amount-10000 where userId=1;
         update B set amount=amount+10000 where userId=1;
End transaction
commit;

  很是正確,若是你使用spring的話一個註解就能搞定上述事務功能

@Transactional(rollbackFor=Exception.class)
    public void update() {
        updateATable(); //更新A表
        updateBTable(); //更新B表
    }

  若是系統規模較小,數據表都在一個數據庫實例上,上述本地事務方式能夠很好地運行,可是若是系統規模較大,好比支付寶帳戶表和餘額寶帳戶表顯然不會在同一個數據庫實例上,他們每每分佈在不一樣的物理節點上,這時本地事務已經失去用武之地。既然本地事務失效,分佈式事務天然就登上舞臺

分佈式事物

  理解分佈式事務是怎樣實現的,事務提交樹是關鍵

  事務提交樹:事務提交樹的根是事務初始化服務所在的機器的DTC,它在整個事務提交過程當中充當着總協調者,又被稱爲全局提交協調器。資源管理器充當着事務提交樹的葉子節點,它們的父結點爲本機的DTC,分佈於不一樣機器的DTC按照事務的傳播路徑造成了上下級關係

  在一個分佈式事務中,事務的初始化和提交是屬於一個對象,只有最初開始的事務才能被提交,咱們將這種能被初始化和提交的事務稱做可提交事務。隨着參與者逐個登記到事務中,它們本地的事務實際上依賴着這個最初開始的事務,因此咱們稱這種事務叫依賴事務

兩階段提交協議

  兩階段提交協議(Two-phase Commit,2PC)常常被用來實現分佈式事務。通常分爲協調器C和若干事務執行者Si兩種角色,這裏的事務執行者就是具體的數據庫,協調器能夠和事務執行器在一臺機器上

1) 咱們的應用程序(client)發起一個開始請求到TC;

2) TC先將<prepare>消息寫到本地日誌,以後向全部的Si發起<prepare>消息。以支付寶轉帳到餘額寶爲例,TC給A的prepare消息是通知支付寶數據庫相應帳目扣款1萬,TC給B的prepare消息是通知餘額寶數據庫相應帳目增長1w。爲何在執行任務前須要先寫本地日誌,主要是爲了故障後恢復用,本地日誌起到現實生活中憑證 的效果,若是沒有本地日誌(憑證),出問題容易死無對證;

3) Si收到<prepare>消息後,執行具體本機事務,但不會進行commit,若是成功返回<yes>,不成功返回<no>。同理,返回前都應把要返回的消息寫到日誌裏,看成憑證

4) TC收集全部執行器返回的消息,若是全部執行器都返回yes,那麼給全部執行器發生送commit消息,執行器收到commit後執行本地事務的commit操做;若是有任一個執行器返回no,那麼給全部執行器發送abort消息,執行器收到abort消息後執行事務abort操做

注:TC或Si把發送或接收到的消息先寫到日誌裏,主要是爲了故障後恢復用。如某一Si從故障中恢復後,先檢查本機的日誌,若是已收到<commit >,則提交,若是<abort >則回滾。若是是<yes>,則再向TC詢問一下,肯定下一步。若是什麼都沒有,則極可能在<prepare>階段Si就崩潰了,所以須要回滾。

現現在實現基於兩階段提交的分佈式事務也沒那麼困難了,若是使用java,那麼可使用開源軟件atomikos(http://www.atomikos.com/)來快速實現。

不過但凡使用過的上述兩階段提交的同窗均可以發現性能實在是太差,根本不適合高併發的系統。爲何?

  • 1)兩階段提交涉及屢次節點間的網絡通訊,通訊時間太長!
  • 2)事務時間相對於變長了,鎖定的資源的時間也變長了,形成資源等待時間也增長好多!

正是因爲分佈式事務存在很嚴重的性能問題,大部分高併發服務都在避免使用,每每經過其餘途徑來解決數據一致性問題。

使用消息隊列來避免分佈式事務

若是仔細觀察生活的話,生活的不少場景已經給了咱們提示

好比在北京頗有名的姚記炒肝點了炒肝並付了錢後,他們並不會直接把你點的炒肝給你,而是給你一張小票,而後讓你拿着小票到出貨區排隊去取。爲何他們要將付錢和取貨兩個動做分開呢?緣由不少,其中一個很重要的緣由是爲了使他們接待能力加強(併發量更高)

仍是回到咱們的問題,只要這張小票在,你最終是能拿到炒肝的。同理轉帳服務也是如此,當支付寶帳戶扣除1萬後,咱們只要生成一個憑證(消息)便可,這個憑證(消息)上寫着「讓餘額寶帳戶增長 1萬」,只要這個憑證(消息)能可靠保存,咱們最終是能夠拿着這個憑證(消息)讓餘額寶帳戶增長1萬的,即咱們能依靠這個憑證(消息)完成最終一致性

如何可靠保存憑證(消息)

有兩種方法:

一、業務與消息耦合的方式

  支付寶在完成扣款的同時,同時記錄消息數據,這個消息數據與業務數據保存在同一數據庫實例裏(消息記錄表表名爲message)

Begin transaction
         update A set amount=amount-10000 where userId=1;
         insert into message(userId, amount,status) values(1, 10000, 1);
End transaction
commit;

  上述事務能保證只要支付寶帳戶裏被扣了錢,消息必定能保存下來。

  當上述事務提交成功後,咱們經過實時消息服務將此消息通知餘額寶,餘額寶處理成功後發送回覆成功消息,支付寶收到回覆後刪除該條消息數據

二、業務與消息解耦方式

  上述保存消息的方式使得消息數據和業務數據緊耦合在一塊兒,從架構上看不夠優雅,並且容易誘發其餘問題。爲了解耦,能夠採用如下方式。

  1)支付寶在扣款事務提交以前,向實時消息服務請求發送消息,實時消息服務只記錄消息數據,而不真正發送,只有消息發送成功後纔會提交事務;

  2)當支付寶扣款事務被提交成功後,向實時消息服務確認發送。只有在獲得確認發送指令後,實時消息服務才真正發送該消息;

  3)當支付寶扣款事務提交失敗回滾後,向實時消息服務取消發送。在獲得取消發送指令後,該消息將不會被髮送;

  4)對於那些未確認的消息或者取消的消息,須要有一個消息狀態確認系統定時去支付寶系統查詢這個消息的狀態並進行更新。爲何須要這一步驟,舉個例子:假設在第2步支付寶扣款事務被成功提交後,系統掛了,此時消息狀態並未被更新爲「確認發送」,從而致使消息不能被髮送。

  優勢:消息數據獨立存儲,下降業務系統與消息系統間的耦合;

  缺點:一次消息發送須要兩次請求;業務處理服務須要實現消息狀態回查接口

如何解決消息重複投遞的問題

  還有一個很嚴重的問題就是消息重複投遞,以咱們支付寶轉帳到餘額寶爲例,若是相同的消息被重複投遞兩次,那麼咱們餘額寶帳戶將會增長2萬而不是1萬了

  爲何相同的消息會被重複投遞?好比餘額寶處理完消息msg後,發送了處理成功的消息給支付寶,正常狀況下支付寶應該要刪除消息msg,但若是支付寶這時候悲劇的掛了,重啓後一看消息msg還在,就會繼續發送消息msg

  解決方法很簡單,在餘額寶這邊增長消息應用狀態表(message_apply),通俗來講就是個帳本,用於記錄消息的消費狀況,每次來一個消息,在真正執行以前,先去消息應用狀態表中查詢一遍,若是找到說明是重複消息,丟棄便可,若是沒找到才執行,同時插入到消息應用狀態表(同一事務)

for each msg in queue
  Begin transaction
    select count(*) as cnt from message_apply where msg_id=msg.msg_id;
    if cnt==0 then
      update B set amount=amount+10000 where userId=1;
      insert into message_apply(msg_id) values(msg.msg_id);
  End transaction
  commit;

 

推薦閱讀:

  XA 分佈式事務研究

  Spring分佈式事務實現

相關文章
相關標籤/搜索