Uwe Weber
Informix 和 DB2 UDB 的 IT 專家, IBM Germany
簡介
在現代企業環境中,用多個數據庫和多種品牌的數據庫來存儲公司數據已經不足為奇。最終,這些數據將會在不同數據庫外進行比較、合并。
如果您有一個異構的數據庫環境,并且計劃將不同數據庫中的數據收集到一個單獨的應用程序中,那么您就應該可以使用傳統技術執行該任務。在使用 Java 時,您將通過 JDBC 處理所有的數據庫操作。清單 1 展示了在 Java 應用程序中如何連接 DB2 UDB 和 IDS 的代碼片斷。
|
兩階段提交協議簡介
清單 1 中的演示允許您修改不同數據庫中的數據。代替執行查詢,它可以使用 JDBC 方法 executeUpdate()
執行數據修改。
但是如果您需要在單個事務中封裝到 DB2 和 IDS 表的新一行的 insert
,要做什么呢?
意思就是說,如果其中一條 insert 語句失敗了,就應該將數據庫(這里:兩種數據庫!)的初始狀態恢復為客戶機未執行任何動作的狀態。該行為可以通過使用兩階段提交(Two-Phase-Commit)協議完成。這一標準化協議描述了如何實現分布式事務(XA)或分布式工作單元(Distributed Unit of Work,DUOW)的技術,以達到跨數據庫系統的一致狀態(根據 ACID)。
常規事務(單階段提交)中,由 COMMIT
或 ROLLBACK
所執行的事務終止是一種決定性的操作,與之相反,兩階段提交(Two-Phase-Commit)事務是分為兩步(階段)進行的。
首先,兩階段提交(Two-Phase-Commit)事務的啟動與常規的單階段提交(One-Phase-Commit)事務類似。接著,應用程序/客戶機對該兩階段提交(Two-Phase-Commit)操作中所涉及的所有數據庫執行其修改工作。現在,在最終提交該事務之前,客戶機通知參與的數據庫準備提交(第 1 階段)。如果客戶機從數據庫收到一條“okay”,就發出命令向數據庫提交該事務(第 2 階段)。最后分布式事務(Distributed Transaction)結束。
兩階段提交(Two-Phase-Commit)中的第 1 階段十分重要。通過首先詢問數據庫是否可以進行提交,一旦某一參與的數據庫報告錯誤,就有機會立即中止整個事務。因而,第 2 階段將由 ROLLBACK
,而非 COMMIT
完成。
圖 1 提供了對于兩階段提交(Two-Phase-Commit)協議如何工作的圖形化印象。正如所演示的,分布式事務(Distributed Transaction)使用由元組表示的描述符(例如:[x,b1])。其意思是,一個分布式事務(Distributed Transaction)包含兩個元素。首先,有一個惟一全局事務 ID(global transaction id) —— 代表分布式事務(Distributed Transaction)的簡單標識符 - 由 x 表示,第二個是分支 ID(branch id),它描述整個事務的一部分。一般,分支指的是一個數據庫連接。如果您有一個將處理兩個參與數據庫的分布式事務(Distributed Transaction),您就可以用諸如 [100,1] 的描述符表示一個數據庫,用諸如 [100,2] 的描述符表示另一數據庫。因此本例中,就有一個編號為 100 的全局事務,其中包含兩個 ID 分別為 1 和 2 的分支。
“但是”,您或許會問,“如果在兩階段提交(Two-Phase-Commit)協議的第 2 階段中出現錯誤,又將發生什么事情呢?”
“的確,您將陷入麻煩中!”
實際上,稍后我們將會討論該主題。
請看 清單 2。在第 16-19 行代碼中,您可能錯覺地認為第 17 和 18 行的語句都是屬于由 con_db2.setAutoCommit(false)
(第 16 行)所定義的事務邊界的一部分。而事實卻是該行代碼啟動了一個顯式事務,用于連接到由 con_db2.commit()
(第 19 行)所提交的 DB2 數據庫。第 18 行中所做的修改不受該事務的影響。本例沒有使用兩階段提交(Two-Phase-Commit)協議,因此,它不是一個分布式事務(Distributed Transaction)。無論是到 DB2 數據庫的連接,還是到 Informix Dynamic Server(IDS)的連接,它們都沒有意識到彼此的存在。
|
JTA 和事務管理器(TM)
Java Transaction API 允許您操作應用程序中的分布式事務(Distributed Transaction)。JTA 中有一組方法,它將傳統的 JDBC 調用封裝到了兩階段提交(Two-Phase-Commit)協議中。
在異構環境中,您通常會發現一個事務管理器(Transaction Manager),負責處理分布式事務。(實際上,事務管理器可以完成大量的工作負載平衡。)因此,不僅存在到數據庫的直接連接,還有到事務管理器(Transaction Manager)的連接。這就是 JTA 發揮作用的地方:JTA 是 Java 應用程序和事務管理器(Transaction Manager)之間的接口。圖 2 演示了一個包含分布式事務的典型環境。
由于存在事務管理器(Transaction Manager),它通常包含在應用程序服務器(Application Server)中,就不再有兩層(Two-Tier)架構。傳統的客戶/服務器(Client/Server)架構已經由三層(Tree-Tier)架構所取代,三層架構包含應用程序/客戶機、事務管理器(Transaction Manager)/應用程序服務器(Application Server)和數據庫服務器,而數據庫服務器一般稱作 XA Resource。
- 包含 SQL 和 JTA 調用的 Java 應用程序。
- 管理分布式事務的應用程序服務器(Application Server)。
- 參與分布式事務的數據庫。
- Java 應用程序向應用程序服務器(Application Server)提交常規 SQL 語句和通用的 XA 調用。
- 應用程序所發送的消息由應用程序服務器(Application Server)進行處理,并使用 SQL 和數據庫供應商特定的 XA 調用發送給數據庫。
通常,應用程序服務器(Application Server)提供了應用程序可以使用的多種服務。在談到分布式事務時,該服務就稱作 XA Resource。當然,在應用程序可以使用 XA Resource 之前,首先要在應用程序服務器中注冊和配置 XA Resource。
現在,如果您計劃在應用程序中使用 JTA,就必須修改代碼,以便還可以與應用程序服務器(Application Server)進行通信。這包括一些附加的方法調用和指定的錯誤/異常處理。請參閱 清單 3,以了解如何工作。
用 JTA 進行兩階段提交的必要條件
首先,在編寫 JTA 應用程序時,您需要合適的 JDK。好消息就是在使用當前的 JDK 時,不需要任何附加包。大多數的 JTA 相關類都在 javax.transaction
和 javax.transaction.xa
中。
您需要用于 DB2 UDB 和 Informix Dynamic Server 的 JDBC 驅動程序。您將需要 Type 4 JDBC 用于 Informix Dynamic Server。DB2 要求您來選擇需要哪個 JDBC 驅動程序。有 Type 2、3 和 4 JDBC。在用 JTA 進行編程時,您必須使用 Type 2 或 4 JDBC 驅動程序。為了方便,本文中所演示的所有例子都使用 Type 4 JDBC 驅動程序用于 DB2。(關于各驅動程序之間差別的解釋,請查閱手冊。)
以上描述說明了應用程序服務器(Application Server)或事務管理器(Transaction Manager)的存在。在下面的例子中,您不會看到“外部”應用程序服務器(Application Server),因為已經使用 DB2XADataSource 和 IfxXADataSource 類直接將之構建到您的應用程序中了。如果您使用一個真正的應用程序服務器(Application Server),那么該應用程序服務器將使用這些類來連接到數據庫的本地 XA 調用。
下面的例子(清單 3)演示了一個小型應用程序,該應用程序使用 JTA 實現兩階段提交(Two-Phase-Commit)協議。該例子并不完整,是為了讓代碼更加易讀。
|
在第 19-39 行中,您看到了該應用程序中所使用的所有類。大多數類是您所知道的。第 30-33 行中導入的類是使用 JTA 所必要的。同樣有意思的是第 35、36 和 38、39 行中的數據庫供應商的特定類。xyzXADataSource 類包含了用于啟用兩階段提交協議的本地 XA 代碼。
|
DBX 類僅僅包含一個私有成員,用于負責屬性文件。在該文件中,有一些數據庫特定的設置,例如到引擎的端口或登錄信息。
該類的構造函數實例化了 SQL 和 XA 相關類:
- Connection: 表示到數據庫的傳統 SQL(JDBC)連接。
- DB2XADataSource 和 IfxXADataSource: 這些類包含到數據庫的本地 XA 調用。使用這些類來啟用兩階段提交協議(Two-Phase-Commit-Protocol)。如果有一個應用程序服務器(Application Server),就不需要在程序中處理這些類,因為應用程序服務器(Application Server)封裝樂應用程序的這部分。
- Xid: 指一個 XA 事務。本例中,使用了兩個不同的數據庫,所以需要兩個不同的 Xid —— 每個數據庫連接(分支)一個。
- XAConnection: JTA 中的一部分。該類允許您啟動(提交、準備提交 ...)分布式事務(Distributed Transaction)。
- XAResource: 該資源指的是應用程序服務器(Application Server)所提供的一個服務。同樣,本例中,我們不使用應用程序服務器(Application Server)。因此,必須在該應用程序中進行創建和初始化。
|
這些代碼行調用一個方法來設置 XADataSource(參見下面)。
|
為了方便,這里同時演示了用于 XADataSource 的 IDS 和 DB2 設置,因為它們十分相似。
在安裝 IfxDataSource(第 363 行)之后,需要將多個設置指定到數據源對象。這些設置是從屬性文件讀取的。在設置傳統的 JDBC 數據庫連接時,所做的這些設置可以與數據庫 URL 相比。請注意,沒有將任何登錄信息指定給數據源對象。登錄信息仍然是數據庫連接本身中的一部分。
正如上面所提到的,如果存在應用程序服務器(Application Server),還可以由它來進行這一初始化。
在用正確的參數初始化 XADataSource 之后,就將 XADataSource 返回給方法調用者。
|
在第 85 和 86 行的代碼中,創建了到數據庫的 XA Connection。下面描述了如何初始化這些 XA Connection。
|
為了設置 XAConnection,要使用前面初始化的 DataSource 對象。第 336 行使用 XADataSource 創建了 XAConnection。為了完成 XAConnection,只需要將身份驗證信息傳遞給該對象。
|
現在,您準備創建 XAResource 對象了。這些對象將允許您操作兩階段提交(Two-Phase-Commit)。
|
XAResource 對象的安裝沒有什么特別的。該對象是通過調用 XAConnection 中的 getXAResource()
來創建的。
在完成所有關于 XA 的準備之后,就創建到數據庫的 JDBC 連接。
|
在 getDatabaseConnection()
方法中,建立了一個 JDBC 數據庫連接。
|
這看上去有些混亂。既然已經在第 336 行中設置了 XAConnection,我們為何還需要 JDBC 連接呢?我們為何仍然需要一個“傳統”連接的理由是所有其他 JDBC 操作和類(Statement、ResultSet ...)都基于或使用 Connection
對象。如果您看一看 JDBC 類的層次結構圖,將會發現 XAConnection
并非是 Connection
,反之亦然。XAConnection
(實際上,它是 ConnectionPool
的子類)使用 Connection
(層次化)。
|
啟動 XA 事務之前的最后一步就是為數據庫創建 XA ID 對象。在分布式事務(Distributed Transaction)中進行操作時,總是要使用這個 xid。
|
createIfxXID 方法創建一個 XID(這里:用于 IDS 連接)。正如“兩階段提交協議簡介”小節中提到的,XA 事務包含定義該事務的兩個元素。上面例子中的重要部分在第 198 行中。IDS XID 是同三個參數創建的。第一個參數是 format ID,它描述在什么格式中構建分布式事務(Distributed Transaction)。您可以省略這一格式信息。第二個參數定義了全局事務 ID(global transaction ID)。該 ID 對于所有參與數據庫來說是惟一的。第三個參數表示該全局事務中的事務分支。
在(為 DB2 和 IDS)構建 XID 之后,我們可以使用它們來修改單個事務中的數據。
|
execBranch()
方法包含了上面為每個連接所定義的 XA 事務中的修改。
|
第 219-226 行代碼包含了分布式事務(Distributed Transaction)中為相應分支所使用的真正 SQL 語句。分支邊界在第 220 行中以 start
方法開始。傳遞給該方法的參數就是我們已經知道的事務 ID,而第二個參數包含了用于該 XA 事務的一些附加信息。因為這是第一個兩階段提交(Two-Phase-Commit)協議操作,所以不需要向該方法傳遞任何特殊信息。TMNOFLAGS
說明了這一事實。分支邊界終止于第 225 行。標志 TMSUCCESS
描述所有操作都成功。
在 IDS 和 DB2 的分支都執行之后,全局事務就準備提交這些修改。當然,在可以向數據庫傳送最后的提交之前,必須詢問數據庫是否準備進行提交。
|
第 104 和 105 行通知數據庫準備提交。如果數據庫報告 XAResource.XA_OK
,就可以提交整個事務。否則,該事務就將被 ROLLBACK
中止。
|
prepareCommit()
方法中最重要的一行在第 426 行中。prepare
方法引起數據庫調用兩階段提交協議(Two-Phase-Commit)的“第 1 階段”。
根據“第 1 階段”的結果,將提交或中止該分布式事務(Distributed Transaction)。下面是將用于發出這些必要操作的兩個方法。
|
如果“第 1 階段”未報告任何錯誤,就在第 136 行中為 xid 所描述的事務分支提交“第 2 階段”。方法 commit()
中的第二個參數區分單階段或兩階段提交操作。因為我們具有一個兩階段提交操作,所以必須將該值設置為 false。
下面的例子展示了如何為數據庫回滾事務分支。
|
問題解答
本文中的例子演示了如何在 Java 中使用 JTA 實現兩階段提交(Two-Phase-Commit)協議。在該應用程序中,如果一個事務分支報告了錯誤,您就要負責進行錯誤處理。但是“兩階段提交協議簡介”小節中提到仍然存在一個問題,那就是如果第 2 階段中一個事務分支發生故障,該怎么辦呢?
如果再次查看程序代碼,您可以看到在“第 1 階段”和“第 2 階段”之間有一個很小的時間間隔。在這一時間間隔中,出于某種理由,其中某一參與數據庫可能崩潰。如果發生了,我們將陷入分布式事務已經部分提交的情形中。
假定下列情形:在“第 1 階段”之后,您從 DB2 和 IDS 數據庫中都收到了“okay”。在下一步中,應用程序成功提交了 DB2 的事務分支。接著,應用程序通知 DB2 事務分支提交事務。現在,在應用程序可以通知 IDS 事務分支提交它這一部分之前,IDS 引擎由于斷電發生崩潰。這就是一種部分提交全局事務的情形。您現在該怎么辦呢?
在重啟之后,DB2 和 IDS 都將嘗試恢復打開的事務分支。該引擎等待來自應用程序的提示如何做。如果應用程序沒有準備重新發送“第 2 階段”的提交,該事務分支將被引擎所啟動的試探性回滾中止。這是非常糟糕的,因為這將使該全局事務處于不一致狀態。
一種解決方案是用一個小型應用程序連接引擎中打開的事務分支,并通知引擎提交或回滾這一打開的事務。如果您使用 IDS 作為后端,那么還有一個隱藏的 onmode 標志,允許您結束打開的事務分支。(onmode -Z xid)。
在 DB2 UDB 中,您可以發出 LIST INDOUBT TRANSACTIONS
來獲得打開的 XA 事務的有關信息。您必須查看 DB2 Information Center 中的描述來解決該問題。
上面描述的情形是一個很好的例子,也是使用應用程序服務器(Application Server)或事務監控器(Transaction Monitor)的理由。在使用一個中間層服務器時,就由該服務器負責保持事情正常。
備選方案
清單 1 演示了在應用程序中從數據庫讀取數據并處理結果的可行方法。如果您的應用程序是“只讀”應用程序,IBM? 就提供了另一種解決方案,稱作 WebSphere? Information Integrator。WebSphere Information Integrator 使用來自 DB2 UDB(或 DB2 Data Joiner、DB2 Relational Connect)的聯邦數據庫技術,以將多個數據庫(通常:數據源)虛擬化(virtualize)到一個數據庫中。不同的、非本地的數據庫中的表都鏈接到 DB2 UDB 中。該操作對于客戶機應用程序是完全透明的。客戶機可以訪問其他數據庫中的所有遠程表,就像它們是本地 DB2 UDB 表一樣。正如 清單 1 中引用的,不再需要連接兩個數據庫。到 DB2 UDB 的單個連接就已經足夠了,因為 DB2 中可以看到 IDS 數據庫中的所有表。
目前,WebSphere Information Integrator 不支持兩階段提交,然而,將來的版本將支持兩階段提交協議;這將帶來實現企業應用程序的新方法。
- 您可以參閱本文在 developerWorks 全球站點上的 英文原文。
- IBM DB2 Information Center
- IBM Informix Information Center
- Java Transaction API
- IBM Informix JDBC Programmers Guide
- IBM DB2 Application Developers Guide Pt. II
關于作者 Uwe Weber 是一位 Informix 和 DB2 UDB 方面的 IT 專家。他居住在德國的慕尼黑。Uwe 的 IT 經歷始于 1997 年,從那時起,他作為一名 Informix 產品講師開始在 Informix 工作。IBM 于 2001 年收購 Informix 之后,他調去了技術預售部門,與 EMEA Central 的客戶一起工作。 |