Vincent

          Vicent's blog
          隨筆 - 74, 文章 - 0, 評論 - 5, 引用 - 0
          數(shù)據(jù)加載中……

          Java 理論和實(shí)踐: 理解 JTS ― 平衡安全性和性能

          為 EJB 組件定義事務(wù)劃分和隔離屬性(attribute)的職責(zé)由應(yīng)用程序裝配人員來承擔(dān)。如果這些屬性設(shè)置不當(dāng),會對應(yīng)用程序的性能、可伸縮性或容錯能力造成嚴(yán)重的后果。不幸的是,并沒有一種必須遵守的規(guī)則用于正確設(shè)置這些屬性,但有一些指導(dǎo)可以幫助我們在并發(fā)危險和性能危險之間找到一種平衡。

          我們在第 1 部分中討論過,事務(wù)主要是一種異常處理機(jī)制。事務(wù)在程序中的用途與合法合同在日常業(yè)務(wù)中的用途相似:如果出了什么問題它們可以幫助恢復(fù)。但由于大多數(shù)時間內(nèi)都沒實(shí)際 發(fā)生什么錯誤,我們就希望能夠盡量減少它們的開銷以及對其余時間的占用。我們在應(yīng)用程序中如何使用事務(wù)會對應(yīng)用程序的性能和可伸縮性產(chǎn)生很大的影響。

          事務(wù)劃分

          J2EE 容器提供了兩種機(jī)制用來定義事務(wù)的起點(diǎn)和終點(diǎn):bean 管理的事務(wù)和容器管理的事務(wù)。在 bean 管理的事務(wù)中,用 UserTransaction.begin()UserTransaction.commit() 在 bean 方法中顯式開始和結(jié)束一個事務(wù)。另一方面,容器管理的事務(wù)提供了更多的靈活性。通過在裝配描述符中為每個 EJB 方法定義事務(wù)性屬性,您可以指定每個方法的事務(wù)性需求并讓容器確定何時開始和結(jié)束一個事務(wù)。無論在哪種情況下,構(gòu)建事務(wù)的基本指導(dǎo)方針都是一樣的。

          進(jìn)來,出去

          事務(wù)劃分的第一條規(guī)則是“盡量短小”。事務(wù)提供并發(fā)控制;這通常意味著資源管理器將代表您獲得您在事務(wù)期間訪問的數(shù)據(jù)項(xiàng)的鎖,并且它必須一直持有這些鎖,直到事務(wù)結(jié)束。(請回憶一下本系列第 1 部分所討論的 ACID特性,其中“ACID”的“I”代表“隔離”(Isolation)。也就是說,一個事務(wù)的結(jié)果影響不到與該事務(wù)并發(fā)執(zhí)行的其它事務(wù)。)當(dāng)您擁有鎖時,任何需要訪問您鎖定的數(shù)據(jù)項(xiàng)的其它事務(wù)將不得不一直等待,直到您釋放鎖。如果您的事務(wù)很長,那些其它的所有事務(wù)都將被鎖定,您的應(yīng)用程序吞吐量將大幅度下降。

          規(guī)則 1:使事務(wù)盡可能短小。

          通過使事務(wù)盡量短小,您可以把阻礙其它事務(wù)的時間縮到最短,從而提高應(yīng)用程序的可伸縮性。保持事務(wù)盡可能短小的最好方法當(dāng)然是不在事務(wù)中間做任何不必要耗費(fèi)時間的事,特別是不要在事務(wù)中間等待用戶輸入。

          開始一個事務(wù),從數(shù)據(jù)庫檢索一些數(shù)據(jù),顯示數(shù)據(jù),然后在仍處于事務(wù)中時請用戶做出一個選擇可能比較誘人。千萬別這么做!即使用戶注意力集中,也要花費(fèi)數(shù)秒來響應(yīng) ― 而在數(shù)據(jù)庫中擁有鎖數(shù)秒的時間已經(jīng)是很長的了。如果用戶決定離開計算機(jī),或許是去吃午餐或者甚至回家一天,會發(fā)生什么情況?應(yīng)用程序?qū)⒅缓脽o奈停機(jī)。在事務(wù)期間執(zhí)行 I/O 是導(dǎo)致災(zāi)難的秘訣。

          規(guī)則 2:在事務(wù)期間不要等待用戶輸入。

          將相關(guān)的操作歸在一起

          由于每個事務(wù)都有不小的開銷,您可能認(rèn)為最好是在單個事務(wù)中執(zhí)行盡可能多的操作以使每個操作的開銷達(dá)到最小。但規(guī)則 1 告訴我們長事務(wù)對可伸縮性不利。那么如何實(shí)現(xiàn)最小化每個操作的開銷和可伸縮性之間的平衡呢?

          我們把規(guī)則 1 設(shè)置為邏輯上的極端 ― 每個事務(wù)一個操作 ― 這樣不僅會導(dǎo)致額外開銷,還會危及應(yīng)用程序狀態(tài)的一致性。假定事務(wù)性資源管理器維護(hù)應(yīng)用程序狀態(tài)的一致性(請回憶一下第 1 部分,其中“ACID”的“C”代表“一致性”(Consistency)),但它們依賴應(yīng)用程序來定義一致性的意思。實(shí)際上,我們在描述事務(wù)時使用的一致性的定義有點(diǎn)圓滑:應(yīng)用程序說一致性是什么意思它就是什么意思。應(yīng)用程序把幾組應(yīng)用程序狀態(tài)的變化組織到幾個事務(wù)中,結(jié)果應(yīng)用程序的狀態(tài)就成了 定義上的(by definition)一致。然后資源管理器確保如果它必須從故障恢復(fù)的話,就把應(yīng)用程序狀態(tài)恢復(fù)到最近的一致狀態(tài)。

          在第 1 部分中,我們給出了一個在銀行應(yīng)用程序中將資金從一個帳戶轉(zhuǎn)移到另一個帳戶的示例。清單 1 展示了這個示例可能的 SQL 實(shí)現(xiàn),它包含 5 個 SQL 操作(一個選擇,兩個更新和兩個插入操作):


          清單 1. 資金轉(zhuǎn)移的樣本 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
          
          												
          										

          如果我們把這個操作作為五個單獨(dú)的事務(wù)來執(zhí)行會發(fā)生什么情況?這樣不僅會使執(zhí)行速度變慢(由于事務(wù)開銷),還會失去一致性。例如,如果一個人從帳戶 A 取了錢,作為執(zhí)行第一次 SELECT(檢查余額)和隨后的記入借方 UPDATE 之間的一個單獨(dú)事務(wù)的一部分,會發(fā)生什么情況?這樣會違反我們認(rèn)為這段代碼會強(qiáng)制遵守的業(yè)務(wù)規(guī)則 ― 帳戶余額應(yīng)該是非負(fù)的。如果在第一次 UPDATE 和第二次 UPDATE 之間系統(tǒng)失敗會發(fā)生什么情況?現(xiàn)在,當(dāng)系統(tǒng)恢復(fù)時,錢已經(jīng)離開了帳戶 A 但還沒有記入帳戶 B 的貸方,并且也無記錄說明原因。這樣,哪個帳戶的所有者都不會開心。

          清單 1 中的五個 SQL 操作是單個相關(guān)操作 ― 將資金從一個帳戶轉(zhuǎn)移到另一個帳戶 ― 的一部分。因此,我們希望要么全部執(zhí)行它們,要么一個也不執(zhí)行,建議在單個事務(wù)中全部執(zhí)行它們。

          規(guī)則 3:將相關(guān)操作歸到單個事務(wù)中。

          理想化的平衡

          規(guī)則 1 說事務(wù)應(yīng)盡可能短小。清單 1 中的示例表明有時候我們必須把一些操作歸到一個事務(wù)中來維護(hù)一致性。當(dāng)然,它要依賴應(yīng)用程序來確定“相關(guān)操作”是由什么組成的。我們可以把規(guī)則 1 和 3 結(jié)合在一起,提供一個描述事務(wù)范圍的一般指導(dǎo),我們規(guī)定它為規(guī)則 4:

          規(guī)則 4:把相關(guān)操作歸到單個事務(wù)中,但把不相關(guān)的操作放到單獨(dú)的事務(wù)中。





          回頁首


          容器管理的事務(wù)

          在使用容器管理的事務(wù)時,不是顯式聲明事務(wù)的起點(diǎn)和終點(diǎn),而是為每個 EJB 方法定義事務(wù)性需求。bean 的 assembly-descriptorcontainer-transaction 部分的 trans-attribute 元素中定義了事務(wù)模式。(清單 2 中顯示了一個 assembly-descriptor 示例。)方法的事務(wù)模式以及狀態(tài) ― 調(diào)用方法是否早已在事務(wù)中被征用 ― 決定了當(dāng) EJB 方法被調(diào)用時容器應(yīng)該進(jìn)行下面幾個操作中的哪一個:

          • 征用現(xiàn)有事務(wù)中的方法。
          • 創(chuàng)建一個新事務(wù),并征用該事務(wù)中的方法。
          • 不征用任何事務(wù)中的方法。
          • 拋出一個異常。

          清單 2. 樣本 EJB 裝配描述符
          												
          														<assembly-descriptor>
            ...
            <container-transaction>
              <method>
                <ejb-name>MyBean</ejb-name>
                <method-name>*</method-name>
              </method>
              <trans-attribute>Required</trans-attribute>
            </container-transaction>
            <container-transaction>
              <method>
                <ejb-name>MyBean</ejb-name>
                <method-name>logError</method-name>
              </method>
              <trans-attribute>RequiresNew</trans-attribute>
            </container-transaction>
            ...
          </assembly-descriptor>
          
          												
          										

          J2EE 規(guī)范定義了六種事務(wù)模式: RequiredRequiresNewMandatorySupportsNotSupportedNever 。表 1 概述了每種模式的行為 ― 在現(xiàn)有事務(wù)中被調(diào)用和不在事務(wù)內(nèi)調(diào)用時的行為 ― 并描述了每種模式受哪些類型的 EJB 組件支持。(一些容器可能允許您在選擇事務(wù)模式時有更多的靈活性,但這種使用要依賴特定于容器的功能,因此不適合跨容器的情況)。

          表 1. 事務(wù)模式

          事務(wù)模式 Bean 類型 在事務(wù) T 內(nèi)被調(diào)用時的行為 在事務(wù)外被調(diào)用時的行為
          Required 會話、實(shí)體、消息驅(qū)動 在 T 中征用 新建事務(wù)
          RequiresNew 會話、實(shí)體 新建事務(wù) 新建事務(wù)
          Supports 會話、消息驅(qū)動 在 T 中征用 不帶事務(wù)運(yùn)行
          Mandatory 會話、實(shí)體 在 T 中征用 出錯
          NotSupported 會話、消息驅(qū)動 不帶事務(wù)運(yùn)行 不帶事務(wù)運(yùn)行
          Never 會話、消息驅(qū)動 出錯 不帶事務(wù)運(yùn)行

          在只使用容器管理的事務(wù)的應(yīng)用程序中,只有組件調(diào)用事務(wù)模式為 RequiredRequiresNew 的 EJB 方法時才啟動事務(wù)。如果容器創(chuàng)建一個事務(wù)作為調(diào)用事務(wù)性方法的結(jié)果,當(dāng)該方法完成時將關(guān)閉該事務(wù)。如果方法正常返回,容器將提交事務(wù)(除非應(yīng)用程序已經(jīng)要求回滾事務(wù))。如果方法通過拋出一個異常退出,容器將回滾事務(wù)并傳播該異常。如果在現(xiàn)有事務(wù) T 中調(diào)用了一個方法,并且事務(wù)模式指定應(yīng)該不帶事務(wù)運(yùn)行該方法或者在新事務(wù)中運(yùn)行該方法,那么事務(wù) T 將被暫掛,一直到方法完成,然后先前的事務(wù) T 被恢復(fù)。

          選擇一種事務(wù)模式

          那么我們應(yīng)該為自己的 bean 方法選擇哪種模式呢?對于會話 bean 和消息驅(qū)動 bean,您通常想使用 Required 來確保每個調(diào)用都被作為事務(wù)的一部分執(zhí)行,但仍將允許方法作為一個更大的事務(wù)的組件。請小心使用 RequiresNew ;只有在確定自己的方法的行為應(yīng)該與調(diào)用您的方法的行為分開提交時,才應(yīng)該使用這種模式。 RequiresNew 一般情況下只和與系統(tǒng)中其它對象關(guān)系很少或沒什么關(guān)系的對象(比如日志對象)一起使用。(把 RequiresNew 與日志對象一起使用比較有意義,因?yàn)槟赡芟M诓还芡鈬聞?wù)是否提交的情況下提交日志消息。)

          RequiresNew 使用不當(dāng)會導(dǎo)致與上面的描述相似的情況,其中,清單 1 中的代碼在五個分開的事務(wù)而不是一個事務(wù)中執(zhí)行,這樣會使應(yīng)用程序處于不一致狀態(tài)。

          對于 CMP(容器管理的持久性,container-managed persistence)實(shí)體 bean,通常是希望使用 RequiredMandatory 也是一個合理的選項(xiàng),特別是在最初開發(fā)時;這將會警告您實(shí)體 bean 方法在事務(wù)外被調(diào)用這種情況,這時可能會指出一個部署錯誤。您幾乎從不希望把 RequiresNew 和 CMP 實(shí)體 bean 一起使用。 NotSupportedNever 旨在用于非事務(wù)性資源,比如 Java 事務(wù) API(Java Transaction API,JTA)事務(wù)中無法征用的外部非事務(wù)性系統(tǒng)或事務(wù)性系統(tǒng)的適配器。

          如果 EJB 應(yīng)用程序設(shè)計得當(dāng),應(yīng)用上面的事務(wù)模式指導(dǎo)往往會自然地產(chǎn)生規(guī)則 4 建議的事務(wù)劃分。原因是 J2EE 體系架構(gòu)鼓勵把應(yīng)用程序分解為最小的方便處理的塊,并且每個塊都作為一個單獨(dú)的請求被處理( 不管是以 HTTP 請求的形式還是作為在 JMS 隊列中排隊的消息的結(jié)果)。





          回頁首


          重溫隔離

          在第 1 部分中,我們定義了 隔離(isolation)的意思是:一個事務(wù)的影響對與該事務(wù)并發(fā)執(zhí)行的其它事務(wù)是不可見的;從事務(wù)的角度來看,好象事務(wù)是連續(xù)執(zhí)行而非并行執(zhí)行。盡管事務(wù)性資源管理器經(jīng)常可以同時處理許多事務(wù)并提供隔離的假象,但有時隔離限制實(shí)際上要求把新事務(wù)延遲到現(xiàn)有事務(wù)完成后才開始。由于完成一個事務(wù)至少包括一個同步磁盤 I/O(寫到事務(wù)日志),這就會把每秒的事務(wù)數(shù)限制到接近每秒的寫磁盤次數(shù),這對可伸縮性不利。

          實(shí)際上,通常是充分放松隔離需求以允許更多的事務(wù)并發(fā)執(zhí)行并使系統(tǒng)響應(yīng)能夠得到改善,使可伸縮性變得更強(qiáng)。幾乎所有的數(shù)據(jù)庫都支持標(biāo)準(zhǔn)隔離級別:讀未提交的(Read Uncommitted)、讀已提交的(Read Committed)、可重復(fù)的讀(Repeatable Read) 和可串行化的(Serializable)。

          不幸的是,為容器管理的事務(wù)管理隔離目前是在 J2EE 規(guī)范的范圍之外。但是,許多 J2EE 容器,比如 IBM WebSphere 和 BEA WebLogic,將提供特定于容器的擴(kuò)展,這些擴(kuò)展允許您以每方法(per-method)為基礎(chǔ)設(shè)置事務(wù)隔離級別,設(shè)置方法與在裝配描述符中設(shè)置事務(wù)模式的方法相同。對于 bean 管理的事務(wù),您可以通過 JDBC 或者其它資源管理器連接設(shè)置隔離級別。

          為闡明隔離級別之間的差異,我們首先把幾個并發(fā)危險分類 ― 這幾種危險是當(dāng)沒有適當(dāng)?shù)馗綦x時一個事務(wù)可能會干涉另一個事務(wù)的情況。下列的所有這些危險都與這種情況( 第二個事務(wù)已經(jīng)啟動后第一個事務(wù)變得對第二個事務(wù) 可見)的結(jié)果有關(guān):

          • 臟讀(Dirty Read):當(dāng)一個事務(wù)的中間(未提交的)結(jié)果對另一個事務(wù)可見時就會發(fā)生這種情況。
          • 不可重復(fù)的讀(Unrepeatable Read):當(dāng)一個事務(wù)讀取一個數(shù)據(jù)項(xiàng),然后重新讀取這個數(shù)據(jù)項(xiàng)并看到不同的值時就是發(fā)生了這種情況。
          • 虛讀(Phantom Read):當(dāng)一個事務(wù)執(zhí)行返回多個行的查詢,稍后再次執(zhí)行同一個查詢并看到第一次執(zhí)行該查詢沒出現(xiàn)的額外行時就是發(fā)生了這種情況。

          四個標(biāo)準(zhǔn)隔離級別與這三個隔離危險相關(guān),如表 2 所示。最低的隔離級別“讀未提交的”并不能保護(hù)事務(wù)不被其它事務(wù)更改,但它的速度最快,因?yàn)樗恍枰獱帄Z讀鎖。最高的隔離級別“可串行化的”與上面給出的隔離的定義相當(dāng);每個事務(wù)好象都與其它事務(wù)的影響完全隔離。

          表 2. 事務(wù)隔離級別

          隔離級別 臟讀 不可重復(fù)的讀 虛讀
          讀未提交的
          讀已提交的
          可重復(fù)的讀
          可串行化的

          對于大多數(shù)數(shù)據(jù)庫,缺省的隔離級別為“讀已提交的”,這是個很好的缺省選擇,因?yàn)樗柚故聞?wù)在事務(wù)中的任何給定的點(diǎn)看到應(yīng)用程序數(shù)據(jù)的不一致視圖。“讀已提交的”是一個很不錯的隔離級別,用于大多數(shù)典型的短事務(wù),比如獲取報表數(shù)據(jù)或獲取要顯示給用戶的數(shù)據(jù)的時候(多半是作為 Web 請求的結(jié)果),也用于將新數(shù)據(jù)插入到數(shù)據(jù)庫的情況。

          當(dāng)您需要所有事務(wù)間有較高級別的一致性時,使用較高的隔離級別“可重復(fù)的讀”和“可串行化的”比較合適,比如在清單 1 示例中,您希望從檢查余額以確保有足夠的資金到您實(shí)際取錢期間賬戶余額一直保持不變;這就要求至少要用“可重復(fù)的讀”隔離級別。在數(shù)據(jù)一致性絕對重要的情況下,比如審核記帳數(shù)據(jù)庫以確保一個帳戶的所有借方金額和貸方金額的總數(shù)等于它目前的余額時,可能還需要防止創(chuàng)建新行。這種情況下就需要使用“可串行化的”隔離級別。

          最低的隔離級別“讀未提交的”很少使用。它適用于您只需要獲得近似值,否則查詢將導(dǎo)致您不希望的性能開銷這種情況。當(dāng)您想要估計一個變化很快的數(shù)量,如定單數(shù)或者今天所下定單的總金額(以美元為單位)時一般使用““讀未提交的”。

          因?yàn)楦綦x和可伸縮性之間實(shí)際是一種此消彼長的關(guān)系,所以您在為事務(wù)選擇隔離級別時應(yīng)該小心行事。選擇太低的級別對數(shù)據(jù)比較危險。選擇太高的級別可能對性能不利,盡管負(fù)載比較輕時可能不會這樣。一般來說,數(shù)據(jù)一致性問題比性能問題更嚴(yán)重。如果拿不準(zhǔn),應(yīng)該以小心為主,選擇一個較高的隔離級別。這就引出了規(guī)則 5:

          規(guī)則 5:使用保證數(shù)據(jù)安全的最低隔離級別,但如果拿不準(zhǔn),請使用“可串行化的”。

          即使您打算剛開始時以小心為主并希望結(jié)果性能可以接受 ―(被稱為“拒絕和祈禱(denial and prayer)”的性能管理技術(shù) ― 很可能是最常用的性能策略,盡管大多數(shù)開發(fā)者都不承認(rèn)這一點(diǎn)),在開發(fā)組件時考慮隔離需求也是有利的。您應(yīng)該努力編寫能夠容忍級別較低但實(shí)用的隔離級別的事務(wù),這樣,當(dāng)稍后性能成為問題時,自己就不會陷入困境。因?yàn)槟枰婪椒ㄕ谧鍪裁匆约斑@個方法中隱藏了什么一致性假設(shè)來正確設(shè)置隔離級別,那么在開發(fā)期間仔細(xì)說明并發(fā)需求和假設(shè),以便在裝配應(yīng)用程序時幫助作出正確的決定也不失為一個好主意。





          回頁首


          結(jié)束語

          本文中提供的許多指導(dǎo)可能看起來有點(diǎn)互相矛盾,因?yàn)橄笫聞?wù)劃分和隔離這種問題本來就是此消彼長的。我們正在努力平衡安全性(如果我們不關(guān)心安全性,那就壓根不必用事務(wù)了)和我們用來提供安全限度的工具的性能開銷。正確的平衡要依賴許多因素,包括與系統(tǒng)故障或當(dāng)機(jī)時間相關(guān)的代價或損害以及組織的風(fēng)險承受能力。

          posted on 2006-08-24 17:38 Binary 閱讀(207) 評論(0)  編輯  收藏 所屬分類: j2se

          主站蜘蛛池模板: 闻喜县| 新田县| 泸水县| 衡阳市| 固始县| 崇信县| 翁牛特旗| 宜城市| 化州市| 郁南县| 凤山县| 招远市| 巢湖市| 深州市| 汶川县| 奉新县| 五台县| 湖南省| 绥中县| 精河县| 广德县| 华蓥市| 舒兰市| 西华县| 贵德县| 鄂尔多斯市| 博客| 武强县| 星子县| 永丰县| 黄平县| 小金县| 永吉县| 新巴尔虎左旗| 丁青县| 西林县| 万州区| 西昌市| 湖北省| 鄂托克旗| 北安市|