分散式事務(一)兩階段提交及JTA

来源:http://www.cnblogs.com/jasongj/archive/2016/08/02/5727897.html
-Advertisement-
Play Games

分散式事務與本地事務一樣,包含原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability)。兩階段提交是保證分散式事務中原子性的重要方法。本文重點介紹了兩階段提交的原理,PostgreSQL中兩階段提交介面,以及Java中兩階段提交介面... ...


原創文章,同步發自作者個人博客 http://www.jasongj.com/big_data/two_phase_commit/

分散式事務

分散式事務簡介

分散式事務是指會涉及到操作多個資料庫(或者提供事務語義的系統,如JMS)的事務。其實就是將對同一資料庫事務的概念擴大到了對多個資料庫的事務。目的是為了保證分散式系統中事務操作的原子性。分散式事務處理的關鍵是必須有一種方法可以知道事務在任何地方所做的所有動作,提交或回滾事務的決定必須產生統一的結果(全部提交或全部回滾)。

分散式事務實現機制

如同作者在《SQL優化(六) MVCC PostgreSQL實現事務和多版本併發控制的精華》一文中所講,事務包含原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability)。

PostgreSQL針對ACID的實現技術如下表所示。

ACID
原子性(Atomicity)
一致性(Consistency)
隔離性
持久性

分散式事務的實現技術如下表所示。(以PostgreSQL作為事務參與方為例)

分散式ACID
原子性(Atomicity)
一致性(Consistency)
隔離性
持久性

從上表可以看到,一致性、隔離性和持久性靠的是各分散式事務參與方自己原有的機制,而兩階段提交主要保證了分散式事務的原子性。

兩階段提交

分散式事務如何保證原子性

在分散式系統中,各個節點(或者事務參與方)之間在物理上相互獨立,通過網路進行協調。每個獨立的節點(或組件)由於存在事務機制,可以保證其數據操作的ACID特性。但是,各節點之間由於相互獨立,無法確切地知道其經節點中的事務執行情況,所以多節點之間很難保證ACID,尤其是原子性。

如果要實現分散式系統的原子性,則須保證所有節點的數據寫操作,要不全部都執行(生效),要麼全部都不執行(生效)。但是,一個節點在執行本地事務的時候無法知道其它機器的本地事務的執行結果,所以它就不知道本次事務到底應該commit還是 roolback。常規的解決辦法是引入一個“協調者”的組件來統一調度所有分散式節點的執行。

XA規範

XA是由X/Open組織提出的分散式事務的規範。XA規範主要定義了(全局)事務管理器(Transaction Manager)和(局部)資源管理器(Resource Manager)之間的介面。XA介面是雙向的系統介面,在事務管理器(Transaction Manager)以及一個或多個資源管理器(Resource Manager)之間形成通信橋梁。XA引入的事務管理器充當上文所述全局事務中的“協調者”角色。事務管理器控制著全局事務,管理事務生命周期,並協調資源。資源管理器負責控制和管理實際資源(如資料庫或JMS隊列)。目前,Oracle、Informix、DB2、Sybase和PostgreSQL等各主流資料庫都提供了對XA的支持。

XA規範中,事務管理器主要通過以下的介面對資源管理器進行管理

  • xa_open,xa_close:建立和關閉與資源管理器的連接。
  • xa_start,xa_end:開始和結束一個本地事務。
  • xa_prepare,xa_commit,xa_rollback:預提交、提交和回滾一個本地事務。
  • xa_recover:回滾一個已進行預提交的事務。

兩階段提交原理

二階段提交的演算法思路可以概括為:協調者詢問參與者是否準備好了提交,並根據所有參與者的反饋情況決定向所有參與者發送commit或者rollback指令(協調者向所有參與者發送相同的指令)。

所謂的兩個階段是指

  • 準備階段 又稱投票階段。在這一階段,協調者詢問所有參與者是否準備好提交,參與者如果已經準備好提交則回覆Prepared,否則回覆Non-Prepared
  • 提交階段 又稱執行階段。協調者如果在上一階段收到所有參與者回覆的Prepared,則在此階段向所有參與者發送commit指令,所有參與者立即執行commit操作;否則協調者向所有參與者發送rollback指令,參與者立即執行rollback操作。

兩階段提交中,協調者和參與方的交互過程如下圖所示。
Two-phase commit

兩階段提交前提條件

  • 網路通信是可信的。雖然網路並不可靠,但兩階段提交的主要目標並不是解決諸如拜占庭問題的網路問題。同時兩階段提交的主要網路通信危險期(In-doubt Time)在事務提交階段,而該階段非常短。
  • 所有crash的節點最終都會恢復,不會一直處於crash狀態。
  • 每個分散式事務參與方都有WAL日誌,並且該日誌存於穩定的存儲上。
  • 各節點上的本地事務狀態即使碰到機器crash都可從WAL日誌上恢復。

兩階段提交容錯方式

兩階段提交中的異常主要分為如下三種情況

  1. 協調者正常,參與方crash
  2. 協調者crash,參與者正常
  3. 協調者和參與方都crash

對於第一種情況,若參與方在準備階段crash,則協調者收不到Prepared回覆,協調方不會發送commit命令,事務不會真正提交。若參與方在提交階段提交,當它恢復後可以通過從其它參與方或者協調方獲取事務是否應該提交,並作出相應的響應。

第二種情況,可以通過選出新的協調者解決。

第三種情況,是兩階段提交無法完美解決的情況。尤其是當協調者發送出commit命令後,唯一收到commit命令的參與者也crash,此時其它參與方不能從協調者和已經crash的參與者那兒瞭解事務提交狀態。但如同上一節兩階段提交前提條件所述,兩階段提交的前提條件之一是所有crash的節點最終都會恢復,所以當收到commit的參與方恢復後,其它節點可從它那裡獲取事務狀態並作出相應操作。

JTA

JTA介紹

作為java平臺上事務規範JTA(Java Transaction API)也定義了對XA事務的支持,實際上,JTA是基於XA架構上建模的。在JTA 中,事務管理器抽象為javax.transaction.TransactionManager介面,並通過底層事務服務(即Java Transaction Service)實現。像很多其他的Java規範一樣,JTA僅僅定義了介面,具體的實現則是由供應商(如J2EE廠商)負責提供,目前JTA的實現主要有以下幾種:

  • J2EE容器所提供的JTA實現(如JBoss)。
  • 獨立的JTA實現:如JOTM(Java Open Transaction Manager),Atomikos。這些實現可以應用在那些不使用J2EE應用伺服器的環境里用以提供分佈事事務保證。

PostgreSQL兩階段提交介面

  • PREPARE TRANSACTION transaction_id PREPARE TRANSACTION 為當前事務的兩階段提交做準備。 在命令之後,事務就不再和當前會話關聯了;它的狀態完全保存在磁碟上, 它提交成功有非常高的可能性,即使是在請求提交之前資料庫發生了崩潰也如此。這條命令必須在一個用BEGIN顯式開始的事務塊裡面使用。
  • COMMIT PREPARED transaction_id 提交已進入準備階段的ID為transaction_id的事務
  • ROLLBACK PREPARED transaction_id 回滾已進入準備階段的ID為transaction_id的事務

典型的使用方式如下

postgres=> BEGIN;
BEGIN
postgres=> CREATE TABLE demo(a TEXT, b INTEGER);    
CREATE TABLE
postgres=> PREPARE TRANSACTION 'the first prepared transaction';
PREPARE TRANSACTION
postgres=> SELECT * FROM pg_prepared_xacts;
 transaction |              gid               |           prepared            | owner | database 
-------------+--------------------------------+-------------------------------+-------+----------
       23970 | the first prepared transaction | 2016-08-01 20:44:55.816267+08 | casp  | postgres
(1 row)

從上面代碼可看出,使用PREPARE TRANSACTION transaction_id語句後,PostgreSQL會在pg_catalog.pg_prepared_xact表中將該事務的transaction_id記於gid欄位中,並將該事務的本地事務ID,即23970,存於transaction欄位中,同時會記下該事務的創建時間及創建用戶和資料庫名。

繼續執行如下命令

postgres=> \q
SELECT * FROM pg_prepared_xacts;
 transaction |              gid               |           prepared            | owner | database 
-------------+--------------------------------+-------------------------------+-------+----------
       23970 | the first prepared transaction | 2016-08-01 20:44:55.816267+08 | casp  | cqdb
(1 row)

cqdb=> ROLLBACK PREPARED 'the first prepared transaction';            
ROLLBACK PREPARED
cqdb=> SELECT * FROM pg_prepared_xacts;
 transaction | gid | prepared | owner | database 
-------------+-----+----------+-------+----------
(0 rows)

即使退出當前session,pg_catalog.pg_prepared_xact表中關於已經進入準備階段的事務信息依然存在,這與上文所述準備階段後各節點會將事務信息存於磁碟中持久化相符。註:如果不使用PREPARED TRANSACTION 'transaction_id',則已BEGIN但還未COMMIT或ROLLBACK的事務會在session退出時自動ROLLBACK。

在ROLLBACK已進入準備階段的事務時,必須指定其transaction_id

PostgreSQL兩階段提交註意事項

  • PREPARE TRANSACTION transaction_id命令後,事務狀態完全保存在磁碟上。
  • PREPARE TRANSACTION transaction_id命令後,事務就不再和當前會話關聯,因此當前session可繼續執行其它事務。
  • COMMIT PREPAREDROLLBACK PREPARED可在任何會話中執行,而並不要求在提交準備的會話中執行。
  • 不允許對那些執行了涉及臨時表或者是創建了帶WITH HOLD游標的事務進行PREPARE。 這些特性和當前會話綁定得實在是太緊密了,因此在一個準備好的事務里沒什麼可用的。
  • 如果事務用SET修改了運行時參數,這些效果在PREPARE TRANSACTION之後保留,並且不會被任何以後的COMMIT PREPAREDROLLBACK PREPARED所影響,因為SET的生效範圍是當前session。
  • 從性能的角度來看,把一個事務長時間停在準備好的狀態是不明智的,因為它會影響VACUUM回收存儲的能力。
  • 已準備好的事務會繼續持有它們獲得的鎖,直到該事務被commit或者rollback。所以如果已進入準備階段的事務一直不被處理,其它事務可能會因為獲取不到鎖而被block或者失敗。
  • 預設情況下,PostgreSQL並不開啟兩階段提交,可以通過在postgresql.conf文件中設置max_prepared_transactions配置項開啟PostgreSQL的兩階段提交。

JTA實現PostgreSQL兩階段提交

本文使用Atomikos提供的JTA實現,利用PostgreSQL提供的兩階段提交特性,實現了分散式事務。本文中的分散式事務使用了2個不同機器上的PostgreSQL實例。

本例所示代碼可從作者Github獲取。

package com.jasongj.jta.resource;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.WebApplicationException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Path("/jta")
public class JTAResource {
  private static final Logger LOGGER = LoggerFactory.getLogger(JTAResource.class);

  @GET
  public String test(@PathParam(value = "commit") boolean isCommit)
      throws NamingException, SQLException, NotSupportedException, SystemException {
    UserTransaction userTransaction = null;
    try {
      Context context = new InitialContext();
      userTransaction = (UserTransaction) context.lookup("java:comp/UserTransaction");
      userTransaction.setTransactionTimeout(600);
      
      userTransaction.begin();
      
      DataSource dataSource1 = (DataSource) context.lookup("java:comp/env/jdbc/1");
      Connection xaConnection1 = dataSource1.getConnection();
      
      DataSource dataSource2 = (DataSource) context.lookup("java:comp/env/jdbc/2");
      Connection xaConnection2 = dataSource2.getConnection();
      LOGGER.info("Connection autocommit : {}", xaConnection1.getAutoCommit());

      Statement st1 = xaConnection1.createStatement();
      Statement st2 = xaConnection2.createStatement();
      LOGGER.info("Connection autocommit after created statement: {}", xaConnection1.getAutoCommit());
      

      st1.execute("update casp.test set qtime=current_timestamp, value = 1");
      st2.execute("update casp.test set qtime=current_timestamp, value = 2");
      LOGGER.info("Autocommit after execution : ", xaConnection1.getAutoCommit());

      userTransaction.commit();
      LOGGER.info("Autocommit after commit: ",  xaConnection1.getAutoCommit());
      return "commit";

    } catch (Exception ex) {
      if (userTransaction != null) {
        userTransaction.rollback();
      }
      LOGGER.info(ex.toString());
      throw new WebApplicationException("failed", ex);
    }
  }
}

從上示代碼中可以看到,雖然使用了Atomikos的JTA實現,但因為使用了面向介面編程特性,所以只出現了JTA相關的介面,而未顯式使用Atomikos相關類。具體的Atomikos使用是在WebContent/META-INFO/context.xml中配置。

<Context>
  <Transaction factory="com.atomikos.icatch.jta.UserTransactionFactory" />
    <Resource name="jdbc/1"
    auth="Container"
    type="com.atomikos.jdbc.AtomikosDataSourceBean"
    factory="com.jasongj.jta.util.EnhancedTomcatAtomikosBeanFactory"
    uniqueResourceName="DataSource_Resource1"
    minPoolSize="2"
    maxPoolSize="8"
    testQuery="SELECT 1"
    xaDataSourceClassName="org.postgresql.xa.PGXADataSource"
    xaProperties.databaseName="postgres"
    xaProperties.serverName="192.168.0.1"
    xaProperties.portNumber="5432"
    xaProperties.user="casp"
    xaProperties.password=""/>

    <Resource name="jdbc/2"
    auth="Container"
    type="com.atomikos.jdbc.AtomikosDataSourceBean"
    factory="com.jasongj.jta.util.EnhancedTomcatAtomikosBeanFactory"
    uniqueResourceName="DataSource_Resource2"
    minPoolSize="2"
    maxPoolSize="8"
    testQuery="SELECT 1"
    xaDataSourceClassName="org.postgresql.xa.PGXADataSource"
    xaProperties.databaseName="postgres"
    xaProperties.serverName="192.168.0.2"
    xaProperties.portNumber="5432"
    xaProperties.user="casp"
    xaProperties.password=""/>  
</Context>

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ...
  • TreeSet簡介 TreeSet 是一個有序的集合,它的作用是提供有序的Set集合。它繼承於AbstractSet抽象類,實現了NavigableSet<E>, Cloneable, java.io.Serializable介面。 TreeSet 繼承於AbstractSet,所以它是一個Set集 ...
  • 一、prototype和__proto__的概念 prototype是函數的一個屬性(每個函數都有一個prototype屬性),這個屬性是一個指針,指向一個對象。它是顯示修改對象的原型的屬性。 __proto__是一個對象擁有的內置屬性(請註意:prototype是函數的內置屬性,__proto__ ...
  • 常常會在程式中遇到多個子類有共同的方法已經相似的調用過程。這個時候我們就可以使用模板模式來解決這些重覆性的工作,例如我們買東西的時候一般都是挑選商品、付款這樣的步驟,區別僅僅是挑選的商品品種不一樣而已,這個時候我們就可以使用模板模式。那麼模板模式需要怎麼來實現呢,如下圖 代碼如下: public c ...
  • 本文為作者原創,如需轉載請註明出處。 1. 實現的功能 一主多備,自動選主 啟動記錄可查詢 一主多備,自動選主 啟動記錄可查詢 2. 前置需求 一臺資料庫用以記錄,如 MySQL、Redis、MongoDB 等。關鍵是設計中的思想,用啥資料庫都行。 3. 設計實現 在資料庫中,存一張表,結構如下: ...
  • 橋接模式的定義: 將抽象化(Abstraction)與實現化(Implementation)脫耦,使得二者可以獨立地變化。 橋接模式結構圖: 橋接模式中得角色: 抽象化(Abstraction)角色:抽象化給出的定義,並保存一個對實現化對象的引用。 修正抽象化(Refined Abstraction ...
  • 1. 理解MVC MVC是一種經典的設計模式,全名為Model-View-Controller,即模型-視圖-控制器。 其中,模型是用於封裝數據的載體,例如,在Java中一般通過一個簡單的POJO(Plain Ordinary Java Object)來表示,其本質是一個普通的Java Bean,包 ...
  • 1.用法示例1 String str = QString("%1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11").arg("1","2","3","4","5","6","7","8","9"); qDebug() << str << endl; 輸出 "1 2 3 4 5 6 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...