Java Transaction Service 是 J2EE 架構(gòu)的關(guān)鍵元素。它與 Java Transaction API 結(jié)合在一起,使我們能夠構(gòu)建對于各種系統(tǒng)和網(wǎng)絡(luò)故障都非常健壯的分布式應(yīng)用程序。事務(wù)是可靠應(yīng)用程序的基本構(gòu)建塊 —— 如果沒有事務(wù)的支持,編寫可靠的分布式應(yīng)用程序?qū)⑹欠浅@щy的。幸運(yùn)的是,JTS 執(zhí)行的大部分工作對于程序員都是透明的;J2EE 容器使事務(wù)劃分和資源征用對程序員來說幾乎是不可見的。這個(gè)由三個(gè)部分組成的系列文章的第一期講述了一些基礎(chǔ)知識,包括什么是事務(wù),以及事務(wù)對于構(gòu)建可靠的分布式應(yīng)用程序來說至關(guān)重要的原因。<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
如果您閱讀過任何有關(guān) J2EE 的介紹性文章或者書籍,那么就會發(fā)現(xiàn),只有一小部分資料是專門針對 Java Transaction Service(JTS)或 Java Transaction API(JTA)的。這并不是因?yàn)?JTS 是 J2EE 中不重要的部分或者可選部分 —— 恰恰相反。JTS 受到的關(guān)注之所以會比 EJB 技術(shù)少,是因?yàn)樗鼮閼?yīng)用程序提供的服務(wù)非常透明 —— 很多開發(fā)人員甚至沒有注意到在他們的應(yīng)用程序中事務(wù)在哪里開始和結(jié)束。在某種意義上,JTS 的默默無聞恰恰是它的成功:因?yàn)樗浅S行У仉[藏了事務(wù)管理的很多細(xì)節(jié),因此,我們沒有聽說過或者談?wù)撨^很多關(guān)于它的內(nèi)容。但是,您可能想了解它在幕后都為您執(zhí)行什么功能。
毫不夸張地說,沒有事務(wù)就不能編寫可靠的分布式應(yīng)用程序。事務(wù)允許采用某種控制方式修改應(yīng)用程序的持久性狀態(tài),以便使應(yīng)用程序?qū)τ诟鞣N各樣的系統(tǒng)故障(包括系統(tǒng)崩潰、網(wǎng)絡(luò)故障、電源故障甚至自然災(zāi)害)更加健壯。事務(wù)是構(gòu)建容錯(cuò)、高可靠性以及高可用性應(yīng)用程序所需的基本構(gòu)建塊之一。
假設(shè)您正在從一個(gè)賬戶向另一個(gè)賬戶進(jìn)行轉(zhuǎn)賬個(gè)賬戶差額由數(shù)據(jù)庫表中的某一行來表示。如果您想從賬戶 A 轉(zhuǎn)賬到賬戶 B,則可能執(zhí)行如下這些 SQL 代碼:
SELECT accountBalance INTO aBalance FROM Accounts WHERE accountId=aId; IF (aBalance >= transferAmount) THEN UPDATE Accounts SET accountBalance = accountBalance - transferAmount WHERE accountId = aId; UPDATE Accounts SET accountBalance = accountBalance + transferAmount WHERE accountId = bId; INSERT INTO AccountJournal (accountId, amount) VALUES (aId, -transferAmount); INSERT INTO AccountJournal (accountId, amount) VALUES (bId, transferAmount); else FAIL "Insufficient funds in account"; END if |
到目前為止,這段代碼看起來非常簡單易懂。如果手頭的資金充足,則從一個(gè)賬戶中減去資金,并添加到另一個(gè)賬戶中。但是,如果出現(xiàn)系統(tǒng)電源故障或者崩潰,又會發(fā)生什么情況呢?表示賬戶 A 和賬戶 B 的行可能不會存儲在同一個(gè)磁盤塊中,這意味著要完成轉(zhuǎn)賬需要進(jìn)行多個(gè)磁盤 IO。如果在已寫入第一個(gè)磁盤塊之后,在寫入第二個(gè)磁盤塊之前,系統(tǒng)發(fā)生故障,又會發(fā)生什么情況呢?A 賬戶中的資金已經(jīng)劃走,但是沒有出現(xiàn)在賬戶 B 中(A 和 B 客戶都不會愿意),或者資金將出現(xiàn)在賬戶 B 中,但是沒有記入賬戶 A 的借出賬中(銀行不會愿意)。如果賬戶已正確更新,而賬戶日記賬沒有更新,又會發(fā)生什么情況呢?那么賬戶 A 和賬戶 B 的每月銀行結(jié)賬單將與它們賬戶的余額不一致。
不僅不可能同時(shí)將多個(gè)數(shù)據(jù)塊寫入磁盤,而且每當(dāng)進(jìn)行修改時(shí)馬上將每個(gè)數(shù)據(jù)塊寫入磁盤,也對系統(tǒng)性能有不利影響。將磁盤寫入延遲到比較適宜的時(shí)間可能會大大改善應(yīng)用程序的吞吐量,但是,需要采用不損害數(shù)據(jù)完整性的方式執(zhí)行。
甚至在系統(tǒng)沒有發(fā)生故障時(shí),上面討論的代碼還有另一種風(fēng)險(xiǎn) —— 并發(fā)性。如果賬戶 A 中有 100 美元,但是卻同時(shí)開始向它的兩個(gè)不同的賬戶分別轉(zhuǎn)賬 100 美元,那么會發(fā)生什么情況呢?如果時(shí)間上湊巧,并且沒有適當(dāng)?shù)逆i定機(jī)制,兩次轉(zhuǎn)賬都可能成功,從而使賬戶 A 的余額為負(fù)值。
這些情況似乎都是非??赡馨l(fā)生的,因此希望企業(yè)數(shù)據(jù)系統(tǒng)能夠解決這些問題是理所應(yīng)當(dāng)?shù)?。我們希望在發(fā)生火災(zāi)、洪水、電源故障、磁盤以及系統(tǒng)出現(xiàn)故障時(shí),銀行都能夠保持正確的賬戶記錄。可以通過冗余(冗余的磁盤、計(jì)算機(jī)以及數(shù)據(jù)中心)來提供容錯(cuò),但是 事務(wù) 使得構(gòu)建容錯(cuò)的軟件應(yīng)用程序成為可能。事務(wù)提供了一個(gè)框架,用于在系統(tǒng)或組件發(fā)生故障時(shí)保持?jǐn)?shù)據(jù)一致性和完整性。
![]() ![]() |
![]()
|
那么到底什么是事務(wù)呢?在定義這個(gè)術(shù)語之前,我們首先定義 應(yīng)用程序狀態(tài) 的概念。應(yīng)用程序的狀態(tài)包含影響應(yīng)用程序操作的所有內(nèi)存和磁盤中的數(shù)據(jù)項(xiàng)目 —— 應(yīng)用程序 “知道” 的所有內(nèi)容。應(yīng)用程序狀態(tài)可以存儲在內(nèi)存、文件或者數(shù)據(jù)庫中。如果系統(tǒng)發(fā)生故障,例如應(yīng)用程序、網(wǎng)絡(luò)或者計(jì)算機(jī)系統(tǒng)崩潰,則我們想確保當(dāng)重新啟動系統(tǒng)時(shí),可以恢復(fù)應(yīng)用程序的狀態(tài)。
現(xiàn)在,我們將 事務(wù) 定義為對應(yīng)用程序狀態(tài)的相關(guān)操作的集合。事務(wù)具有 原子性 、 一致性 、 隔離性 以及 持久性 這幾個(gè)屬性。這些屬性統(tǒng)稱為 ACID 屬性。
原子性 意味著要么所有事務(wù)操作都應(yīng)用于應(yīng)用程序狀態(tài),要么都不應(yīng)用;事務(wù)是不可拆分的工作單元。
一致性 意味著事務(wù)代表應(yīng)用程序狀態(tài)的正確轉(zhuǎn)換 —— 即事務(wù)不能違反應(yīng)用程序中固有的任何完整性限制。實(shí)際上,一致性的概念是特定于應(yīng)用程序的。例如,在記賬應(yīng)用程序中,一致性可能包括所有資產(chǎn)賬戶的總和始終等于所有負(fù)債賬戶的總和這個(gè)不變式。在本系列的第 3 部分中討論事務(wù)劃分時(shí),我們將詳細(xì)討論這個(gè)需求。
隔離性 意味著一個(gè)事務(wù)的效果不影響正在同時(shí)執(zhí)行的其他事務(wù)。從事務(wù)的角度講,它意味著事務(wù)按順序執(zhí)行而不是并行執(zhí)行。在數(shù)據(jù)庫系統(tǒng)中,通常通過使用鎖機(jī)制來實(shí)現(xiàn)隔離性。為了使應(yīng)用程序獲得最佳性能,有時(shí)也會對某些事務(wù)放松隔離性的要求。
持久性 意味著一旦成功完成某個(gè)事務(wù),對應(yīng)用程序狀態(tài)所做的更改將 “經(jīng)得起失敗”。
什么是 “經(jīng)得起失敗”呢?它由什么組成?這取決于系統(tǒng),一個(gè)設(shè)計(jì)良好的系統(tǒng)將明確地標(biāo)識可以從哪些故障中恢復(fù)過來。在我的桌面工作站上運(yùn)行的事務(wù)數(shù)據(jù)庫,對于系統(tǒng)崩潰和電源故障非常穩(wěn)定健壯,但是對于我的辦公大樓發(fā)生大火災(zāi)卻沒有任何作用。銀行可能不僅僅在數(shù)據(jù)中心具有冗余的磁盤、網(wǎng)絡(luò)以及系統(tǒng),而且還可能在別的城市有冗余的數(shù)據(jù)中心,該冗余數(shù)據(jù)中心通過冗余的通信鏈路連接,目的是允許從嚴(yán)重的故障(如自然災(zāi)害)中進(jìn)行恢復(fù)。軍用的數(shù)據(jù)系統(tǒng)甚至可能有更嚴(yán)格的容錯(cuò)要求。
![]() ![]() |
![]()
|
典型事務(wù)有幾個(gè)參與者 —— 應(yīng)用程序、事務(wù)監(jiān)視器(TPM)以及一個(gè)或多個(gè)資源管理器(RM)。資源管理器存儲應(yīng)用程序狀態(tài),常常是數(shù)據(jù)庫,但也可能是消息隊(duì)列服務(wù)器(在 J2EE 應(yīng)用程序中,它們將是 JMS 提供者)或其他事務(wù)性資源。TPM 協(xié)調(diào) RM 的活動,以確保事務(wù) “要么全有要么全無” 屬性。
當(dāng)應(yīng)用程序請求容器或事務(wù)監(jiān)視器啟動新的事務(wù)時(shí),事務(wù)開始。由于應(yīng)用程序訪問各種各樣的 RM,因此,在事務(wù)中對它們進(jìn)行 征用 。RM 必須使對應(yīng)用程序狀態(tài)所做的任何更改與請求更改的事務(wù)相關(guān)聯(lián)。
當(dāng)發(fā)生以下事件之一或者兩個(gè)事件都發(fā)生時(shí),事務(wù)結(jié)束:事務(wù)應(yīng)用程序 提交 該事務(wù);通過應(yīng)用程序或者由于其中一個(gè) RM 失敗, 回滾 該事務(wù)。如果事務(wù)成功提交,則將寫入與該事務(wù)相關(guān)聯(lián)的更改,以使更改持久化并使其對于新的事務(wù)可見。如果事務(wù)被回滾,則該事務(wù)所做的所有更改都將被丟棄;就好像該事務(wù)從來沒有發(fā)生過一樣。
事務(wù) RM 通過在一個(gè)事務(wù)日志中記錄多個(gè)事務(wù)的結(jié)果,獲得持久性以及可接受的性能。事務(wù)日志存儲為連續(xù)的磁盤文件(有時(shí)存儲在原始分區(qū)中),并且一般只是用于寫入而不用于讀取,回滾或恢復(fù)的情況例外。在我們的銀行賬戶示例中,與賬戶 A 和賬戶 B 相關(guān)聯(lián)的余額將在內(nèi)存中進(jìn)行更新,新的余額和舊的余額將被寫入到事務(wù)日志中。編寫事務(wù)日志的更新記錄不需要將全部數(shù)據(jù)都寫入磁盤(只需要寫入已更改的數(shù)據(jù),而不需要寫入全部磁盤塊),而且所需的磁盤尋道時(shí)間也會更少(原因是所有更改都包含在日志中連續(xù)的磁盤塊中)。此外,與多個(gè)并發(fā)事務(wù)關(guān)聯(lián)的更改可以合并到一起,一次寫入事務(wù)日志,這意味著每次磁盤寫入時(shí)我們可以處理多個(gè)事務(wù),而不需要每個(gè)事務(wù)進(jìn)行幾次磁盤寫入。之后,RM 將根據(jù)所更改的數(shù)據(jù)更新實(shí)際的磁盤塊。
如果系統(tǒng)出現(xiàn)故障,重新啟動時(shí)要做的第一件事就是重新應(yīng)用所有已提交事務(wù)的作用,所有這些已提交的事務(wù)都位于日志中,但是它們的數(shù)據(jù)塊尚未更新。采用這種方式,日志保證了故障之間的持久性,而且還能夠減少所執(zhí)行的磁盤 IO 操作的數(shù)量,或者至少使它們延遲到對系統(tǒng)性能影響更小的時(shí)間。
很多事務(wù)只涉及一個(gè) RM —— 通常是數(shù)據(jù)庫。在這種情況下,RM 通常執(zhí)行提交或回滾事務(wù)所需的大部分工作。(幾乎所有事務(wù) RM 都有它們自己的內(nèi)置的事務(wù)管理器,這個(gè)管理器可以處理 本地事務(wù) —— 只涉及該 RM 的事務(wù))。但是,如果事務(wù)涉及兩個(gè) RM 或多個(gè) RM —— 可能是兩個(gè)單獨(dú)的數(shù)據(jù)庫,或者是一個(gè)數(shù)據(jù)庫和一個(gè) JMS 隊(duì)列,或者是兩個(gè)單獨(dú)的 JMS 提供者 —— 我們想確保 “要么全有要么全無” 的語義不僅僅應(yīng)用于這個(gè) RM 中,而且還應(yīng)用于事務(wù)中的所有 RM。在這種情況下,TPM 將組織一個(gè) 兩階段提交 。在兩階段提交中,TPM 首先向每個(gè) RM 發(fā)送一個(gè) “準(zhǔn)備” 消息,詢問它是否準(zhǔn)備就緒以及是否能夠提交事務(wù);如果它收到來自所有 RM 的確認(rèn)應(yīng)答,則將事務(wù)在其自己的事務(wù)日志中標(biāo)記為已提交,然后指示所有 RM 提交事務(wù)。如果某個(gè) RM 失敗,則重新啟動時(shí)它將向 TPM 詢問有關(guān)失敗時(shí)未處理的所有事務(wù)的狀態(tài),并提交它們或者對它們執(zhí)行回滾操作。
兩個(gè)階段提交類似于社會上的結(jié)婚典禮 —— 牧師或神父詢問雙方 “您愿意讓這個(gè)男人/女人作為您的丈夫/妻子嗎?” 如果雙方都回答是,則將宣布他們成為夫妻;否則,雙方不能結(jié)婚。不管雙方中的哪一方首先說 “我愿意”,在一方?jīng)]有回答時(shí),另一方?jīng)Q不能完成結(jié)婚。
![]() ![]() |
![]()
|
您可能觀察到,事務(wù)向?qū)K進(jìn)行同步的應(yīng)用程序數(shù)據(jù)提供很多與內(nèi)存中數(shù)據(jù)相同的功能 —— 保證原子性、更改的可見性以及顯而易見的排序。但是,當(dāng)同步主要是并發(fā)控制的機(jī)制時(shí),則事務(wù)主要是處理異常的機(jī)制。如果在一個(gè)磁盤不會發(fā)生故障、系統(tǒng)和軟件不會崩潰以及電源是百分百可靠的世界中,我們將不需要事務(wù)。事務(wù)在企業(yè)應(yīng)用程序中所起的作用與合同法在社會上所起的作用一樣 —— 它們規(guī)定,如果一方不能履行他那一部分合同,則交易將失效。當(dāng)我們編寫合同時(shí),我們通常希望它是多余的,令人感到欣慰的是大部分時(shí)候都是如此。
與比較簡單的 Java 程序進(jìn)行類比,事務(wù)在應(yīng)用程序級別所提供的一些優(yōu)勢與
catch
和
finally
塊在方法級別所提供的優(yōu)勢相同;它們使我們不用編寫很多錯(cuò)誤復(fù)原代碼,即可執(zhí)行可靠的錯(cuò)誤復(fù)原。考慮下面這個(gè)方法,該方法將一個(gè)文件復(fù)制到另一個(gè)文件:
public boolean copyFile(String inFile, String outFile) { InputStream is = null; OutputStream os = null; byte[] buffer; boolean success = true; try { is = new FileInputStream(inFile); os = new FileOutputStream(outFile); buffer = new byte[is.available()]; is.read(buffer); os.write(buffer); } catch {IOException e) { success = false; } catch (OutOfMemoryError e) { success = false; } finally { if (is != null) is.close(); if (os != null) os.close(); } return success; } |
忽略為整個(gè)文件分配一個(gè)緩沖區(qū)是一個(gè)不好的想法,但是在這個(gè)方法中哪里錯(cuò)了呢?有很多東西。輸入文件可能不存在,或者該用戶可能沒有這個(gè)文件的讀權(quán)限。用戶可能沒有輸出文件的寫權(quán)限,或者該文件被另一個(gè)用戶鎖定。可能沒有足夠的磁盤空間來完成該文件的寫操作,或者由于沒有足夠的內(nèi)存可用,分配緩沖區(qū)可能失敗。幸運(yùn)的是,所有這些都由
finally
語句來處理,該語句釋放了
copyFile()
所使用的所有資源。
如果您使用原來的 C 語言編寫這個(gè)方法,則對于每個(gè)操作(打開輸入、打開輸出、malloc、讀、寫),必須測試返回狀態(tài),如果操作失敗,則取消以前成功的所有操作,并返回適當(dāng)?shù)臓顟B(tài)代碼。由于需要這么多錯(cuò)誤處理代碼,該代碼可能更大,因此更難閱讀。同時(shí)在錯(cuò)誤處理代碼(它也是最難測試的部分)中很容易出錯(cuò),比如不能釋放資源、對一個(gè)資源釋放兩次或者釋放尚未分配的資源。更復(fù)雜的方法可能涉及更多資源,而不僅僅是兩個(gè)文件和一個(gè)緩沖區(qū),這使得問題變得更加復(fù)雜。在大量錯(cuò)誤復(fù)原代碼中,很難發(fā)現(xiàn)實(shí)際的程序邏輯。
現(xiàn)在,假設(shè)您正在執(zhí)行一個(gè)復(fù)雜的操作,該操作涉及在多個(gè)數(shù)據(jù)庫中插入或更新多個(gè)行,其中一個(gè)操作違反了完整性約束并失敗了。如果您管理自己的錯(cuò)誤復(fù)原,則必須跟蹤已經(jīng)執(zhí)行的操作,并知道在隨后的操作失敗的情況下如何取消每個(gè)操作。如果工作單元分布在多個(gè)方法或組件上,則會更加困難。借助事務(wù)來構(gòu)造應(yīng)用程序,就可以將所有這些清理工作委托給數(shù)據(jù)庫(即進(jìn)行 ROLLBACK),并取消自從事務(wù)開始所執(zhí)行的所有操作。
![]() ![]() |
![]()
|
通過借助事務(wù)構(gòu)造應(yīng)用程序,我們定義一組正確的應(yīng)用程序狀態(tài)轉(zhuǎn)換,并確保應(yīng)用程序始終處于正確的狀態(tài),甚至在系統(tǒng)或組件發(fā)生故障之后也是如此。事務(wù)使我們能夠?qū)⒑芏喈惓L幚砗突謴?fù)工作委托給 TPM 和 RM,從而簡化了我們的代碼,并使我們能夠空出更多時(shí)間來考慮應(yīng)用程序邏輯。
在此系列的第 2 部分中,我們將探討這對于 J2EE 應(yīng)用程序意味著什么 —— J2EE 如何使我們能夠?qū)⑹聞?wù)語義告知 J2EE 組件(EJB 組件、servlet 以及 JSP 頁面);它如何使資源征用對應(yīng)用程序(甚至對于 bean 管理的事務(wù))完全透明;單個(gè)事務(wù)如何透明地遵循從一個(gè) EJB 組件到另一個(gè) EJB 組件,或者從一個(gè) servlet 到一個(gè) EJB 組件,甚至跨越多個(gè)系統(tǒng)的控制流程。
盡管 J2EE 提供了相當(dāng)透明的對象事務(wù)服務(wù),但是應(yīng)用程序設(shè)計(jì)者仍然必須仔細(xì)考慮在哪里劃分事務(wù),以及如何在應(yīng)用程序中使用事務(wù)資源 —— 不正確的事務(wù)劃分可能會使應(yīng)用程序處于不一致的狀態(tài),而不正確地使用事務(wù)資源可能會造成非常嚴(yán)重的性能問題。在此系列的第 3 部分中,我們將討論這些問題并提供一些關(guān)于如何構(gòu)造應(yīng)用程序的建議。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
