速度瓶頸問題的提出在企業級的Java應用中,訪問資料庫是一個必備的環節。資料庫作為資料資源的集散地,往往位於企業級軟體體系的後方,供前方的應用程式訪問。在Java技術的體系中,應用程式是通過JDBC(Java Database Connectivity)介面來訪問資料庫的,JDBC支援"建立串連、SQL語句查詢、處理結果"等準系統。在應用JDBC介面訪問資料庫的過程中,只要根據規範來操作,這些功能的實現不會出差錯。但是,有些時候進行資料查詢的效率著實讓開發人員懊惱不已,明明根據規範編寫的程式,卻得不到預期的運行效果,造成了整個軟體的執行效率不高。起初,我們把問題歸結於Java位元組碼載入和執行速度的緩慢,緊接著硬體的功能普遍得到了增強,證明這樣的想法些許是錯誤的,還沒有抓到真正的根本原因。本文將逐步解剖JDBC訪問資料庫的機制,深層分析造成這種速度瓶頸問題的原因,並提出在現有的Java技術架構下解決這個速度瓶頸問題的思路和方法。JDBC訪問資料庫的機製圖1圖2 圖1和圖2描述了Java應用程式通過JDBC介面訪問資料庫的4種驅動模式,也就是底層實現JDBC介面的模式。對於這些模式,我們逐一介紹:模式4:圖1左邊的分支稱為模式4,它一般是資料庫廠商才能實現的純Java的基於本地協議的驅動,直接調用DBMS(資料庫管理系統)使用的網路通訊協定,對於企業內部互連網來說,是一個實用的解決方案。模式3:圖1右邊的分支稱為模式3,它同樣是一個純Java驅動,不同於模式4的是基於網路通訊協定。它的機制是將JDBC調用轉換為中間網路通訊協定,然後轉換為DBMS協議。中間網路通訊協定層起到一個讀取資料庫的中介軟體的作用,能夠串連許多類型的資料庫,因而是最靈活的JDBC模式。這種模式的產品比較適用於企業內部互連網,如若支援國際互連網,還需添加對安全、穿過防火牆訪問等的支援。模式1:圖2左邊的分支稱為模式1,即通常由Sun公司提供的JDBC-ODBC橋接器。它提供了經由一種或多種ODBC驅動進行訪問的JDBC介面,而ODBC驅動,在很多情況下也即資料庫的用戶端,必須載入到客戶機。因而,它適用於下載和自動安裝Java程式不重要、實驗用途或者沒有其它JDBC驅動可用的情況下。模式2:圖2右邊的分支成為模式2,類似於JDBC-ODBC橋接器,需要載入到客戶機,卻是一個部分用Java實現的驅動介面。它將JDBC調用轉換為對資料庫(Oracle、Sybase、Informix、DB2等)用戶端介面的調用。不同模式的JDBC介面的選擇以上闡述的JDBC介面的模式不同,讓我們可以把JDBC介面按照實現的模式分為四類。有些同仁可能有這樣的體會,選擇不同的JDBC介面會有不同的訪問速度,為何會出現這樣的情況?這個問題的答案是,不同的應用需要不同模式的JDBC介面,因而我們在面對一個應用時,要謹慎選擇JDBC介面。通常的DBMS都支援微軟提出的ODBC規範,因而模式1可當作您在設計和實現軟體時的選擇,它易於配置的特效能夠讓你把選擇JDBC等煩惱的問題暫且拋在一邊,讓自己的Java程式能夠及早地正常工作起來。一般說來,商業DBMS的提供者往往會為自己的資料庫提供一個JDBC介面,應用的是模式4。這種模式的優勢在於和資料庫本身結合比較緊密,而且是純Java的實現,在企業級的軟體應用中,應該是首選。例如,對於Oracle資料庫來說,有Oracle、SilverStream、DataDirect等公司提供這種類型的驅動,其效能往往被評價為最高效的、最可靠的驅動程式。但偶爾也有比較麻煩的情況,例如微軟就不會提供MS SQL的JDBC介面,這時就需要到Sun的網站(http://industry.java.sun.com/products/jdbc/drivers)尋找相關的模式4驅動,上面提到的DataDirect公司(http://www.datadirect-technologies.com/jdbc/jdbc.asp)就提供了支援MS SQL的模式4驅動,只是你需要支付750$購買這個JDBC驅動。同樣是純Java實現的模式3,與模式4相比,優勢在於對多種資料庫的支援,體現了其靈活性。在大型的企業級的軟體應用中,後台資料庫往往不是一個,而且是由不同的廠商支援的。不過,模式3的JDBC驅動往往提供許多企業級的特徵,例如SSL安全、支援分散式交易處理和集中管理等,因而會對你特殊的用途有很大的協助。是否選用,還在於你對擴充應用是否有需求以及對多DBMS的支援。談到這兒,我對模式3和模式4作一個總結:兩者都是純Java實現的驅動,因而不需要資料庫廠商提供附加的軟體,就可以運行在任何標準的Java平台,效能上比較高效、可靠。瞭解上述3種JDBC的實現模式之後,模式2就更容易闡釋了,你可以理解它為前三者利弊平衡的妥協產物:1借鑒模式1利用客戶機本地程式碼程式庫,加速資料訪問的執行,但卻摒除ODBC標準,而是支援廠商自己指定的效能擴充2借鑒模式3利用多層結構,上層用Java實現,利於跨平台應用和支援多資料庫,但下層卻改為本地代碼,加速執行速度3借鑒模式4和資料庫結合緊密的優點,部分用Java實現,更是對資料庫效能有很大的擴充 這種開放和高效能的特徵得到了業界的肯定,因而被主要的資料庫廠商強烈推薦。儘管它需要你下載本地程式碼程式庫到客戶機,但相對於你訪問資料庫速度的提高,這些應該只是舉手之勞了。下面對4種實現JDBC的模式選擇,歸納一下選擇的順序(當然是指你有選擇餘地的時候,不存在的話向後推延):編號選擇過程分析選擇順序1實驗性環境下,儘可能選擇易於配置的驅動,利於Java程式的開發,後期可在對應用環境進行判斷後,再對JDBC模式進行選擇1>2>3>42小型企業級環境下,不需要對多資料庫的支援,因而模式2和3的有些優點並不能體現出來,強烈推薦你選擇模式4的JDBC驅動4>2=3>13大型企業級環境下,需要對多資料庫的支援,模式2和3各有千秋,但是更多情況下是你會選擇速度較快的模式22>3>4>1 對於不同廠商提供的但應用相同模式的JDBC介面,理論上比較不出效率的高低,你只有通過一定的工具,例如Benchmark等,對它們進行比較才能更有利於你的選擇。因為暫時不存在第三方提供的資料比較結果,所以這些問題需要你對上述內容有了透徹理解之後自行解決。Java程式中SQL語句格式的最佳化這個時候,你也許還在為找不到合適的JDBC驅動而一籌莫展,也許為自己在淩晨3點下載的JDBC驅動通過了測試而欣喜若狂,但是並不說明你對程式的最佳化工作已經無關緊要了。切記,對整個軟體系統的最佳化,包括每個環節的最佳化,要不有可能你會前功盡棄。我在這兒不和大家討論Java程式的演算法,而是簡單闡述一下選擇SQL語句格式的必要和如何選擇對自己有利的SQL語句格式。看下面兩段程式片斷:Code Fragment 1:String updateString = "UPDATE COFFEES SET SALES = 75 " + "WHERE COF_NAME LIKE 'Colombian'";stmt.executeUpdate(updateString); Code Fragment 2:PreparedStatement updateSales = con.prepareStatement("UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ? ");updateSales.setInt(1, 75); updateSales.setString(2, "Colombian"); updateSales.executeUpdate(); 片斷2和片斷1的區別在於,後者使用了PreparedStatement對象,而前者是普通的Statement對象。PreparedStatement對象不僅包含了SQL語句,而且大多數情況下這個語句已經被先行編譯過,因而當其執行時,只需DBMS運行SQL語句,而不必先編譯。當你需要執行Statement對象多次的時候,用PreparedStatement對象將會大大降低已耗用時間,當然也加快了訪問資料庫的速度。這種轉換也給你帶來很大的便利,不必重複SQL語句的句法,而只需更改其中變數的值,便可重新執行SQL語句。選擇PreparedStatement對象與否,在於相同句法的SQL語句是否執行了多次,而且兩次之間的差別僅僅是變數的不同。如果僅僅執行了一次的話,它應該和普通的Statement對象毫無差異,體現不出它先行編譯的優越性。軟體模型中對資料庫訪問的設計模式的最佳化在我閱讀J2EE藍圖和JDO草案的過程中,我發現了訪問模式對資料庫訪問的影響,因而想在本文中闡述如何針對自己的軟體需求選擇合適的軟體模式。J2EE藍圖的設計者在Java Pet Store樣本應用中使用了MVC(Model-View-Controller)體系,給許多J2EE設計模式提供了背景。我要談及的三種設計模式是:Data Access Object、Fast Lane Reader、Page-by-Page Iterator,它們為加快資料存取速度提供了一些可以在系統設計階段值得我們借鑒的想法。Data Access Object將商業邏輯從資料存取邏輯中分離出來,把存取的資源改編,從而使資源可以容易和獨立地轉變。依賴於底層資料資源的特殊要素(例如資料庫的供應商)的商業組件,常將商業邏輯和資料存取邏輯配合起來,只能使用特殊類型的資源,而使用不同類型的資源時,複用將會非常困難,因此,只能服務於有限的市場領域。DAO(Data Access Object)即是將資料存取邏輯從EJB中抽去出來抽象為一個獨立的介面,EJB根據介面的操作執行商業邏輯,而介面針對使用的資料資源實現為DAO對象。在Java Pet Shop這個例子中,OrderEJB組件通過關聯的OrderDAO類訪問資料庫,自身則關注於商業邏輯的實現。在調度階段,將配置某一類(OrderDAOCS、OrderDAOOracle或OrderDAOSybase)為OrderDAO的實現,而OrderEJB無須任何更改。圖3更能協助你明白其中的道理:圖3 Data Access Object的設計模式 此舉增加了資料存取的彈性、資源的獨立性和擴充性,但複雜度有相應的提高,其它附帶的問題我們不在這兒討論。Fast Lane Reader拋棄EJB,加速唯讀資料的存取。有些時候,高效地存取資料比獲得最新的資料更重要。在Java Pet Store中,當一個使用者瀏覽商店的目錄時,螢幕與資料庫內容吻合不是至關緊要的,相反,迅速顯示和重新獲得非常重要。FLR模式可以加速從資源中重新獲得大型的列資料項目的速度,它不用EJB,而是更直接地通過DAO來存取資料,從而消除EJB的經常開支(例如遠程方法調用、交易管理和資料序列化等)。在Java Pet Store這個例子中,當一個使用者瀏覽目錄時,通過CatalogDAO(而不是CatalogEJB)從資料庫載入資料項目,而CatalogDAO是一個Fast Lane Reader的執行個體,使得讀訪問變得迅速,4:圖4 Fast Lane Reader設計模式 與DAO模式不同的是,FLR是一個最佳化的模式,但不是要替代原有的訪問機制,而是作為補充使其完備。當你頻繁地唯讀大型的列資料和不必存取最新的資料時,使用FLR將是非常合適的。Page-by-Page Iterator為了高效地存取大型的遠端資料列,一下子通過重新獲得其元素為一個子列的Value Object(提高遠程傳輸效率的設計模式,這兒不詳盡描述)。分散式資料庫的應用經常需要使用者考慮一長列資料項目,例如一個目錄或一個搜尋結果的集合。在這些情況下,立刻提供全列的資料經常不必要(使用者並不是對所有的資料項目感興趣)或不可能(沒有足夠的空間)。此外,當重新獲得一列資料項目時,使用Entity Bean的代價將非常高昂,一個開銷來自於使用遠程探測器來收集Requested Bean,另外,更大的開銷來自對每個Bean產生遠程調用以從使用者獲得資料。通過Iterator,客戶機對象能一下子重新獲得一個子列或者頁的Value Object,每一頁都剛好滿足客戶機的需求,因此,程式使用較少的資源滿足了客戶機的立刻需求。在Java Pet Store這個例子中,JSP頁面product.jsp任何時候只顯示一個資料列的一部分,從ProductItemListTag(Page-by-Page Iterator)重新獲得資料項目,當客戶機希望看到列中的別的資料項目,product.jsp再次調用Iterator重新獲得這些資料項目,流程見圖5:圖5 Page-by-Page Iterator設計模式 以上設計模式的應用向我們表明,在某些特殊情況下,最佳化對資料庫的訪問模型,既可滿足使用者的需求又可提高訪問資料庫的效率。這給我們一個思路,就是:在你的硬體環境可能會產生瓶頸的情況下,可以通過對軟體模型的最佳化來達到滿足需求的目的。上述三種設計模式的應用情形為:Data Access Object需要將商業邏輯和資料存取邏輯分離;在調度的時刻,需要支援選擇資料來源的類型;使用的資料來源的類型的變化,對商業對象或其它用戶端完成資料存取沒有影響。Fast Lane Reader面對大型的列資料,需要經常的唯讀訪問;訪問最新的資料並不是至關緊要的事情。Page-by-Page Iterator存取大型的伺服器端資料列;任何時刻,使用者只對列的一部分內容感興趣;整個列的資料不適合在用戶端顯示;整個列的資料不適合在儲存空間中儲存;傳輸整個列的資料將耗費太多的時間。 在顯示商品目錄的時候,我們選擇了DAO和FLR的結合,因為它們兩者的條件都得到了滿足(需要分離商業邏輯和資料存取邏輯,經常的唯讀訪問和對即時性不敏感),此時應用將會大大發揮它們的優點。而在進行內容檢索的時候,我們會選擇PPI,因為也許檢索出了上千條的記錄,但是使用者沒有興趣立即閱讀全部內容,而是一次十條地閱讀,或者他在閱讀完前十條記錄後發覺自己的目的已經達到,接下來瀏覽別的網頁了,都不必我們一次性地傳輸上千條記錄給他,所以也是PPI的應用條件得到了滿足,結果則是此模式的優點得到了發揮,又不影響全域的資料訪問。在進行軟體模型的設計時,整體的架構可以應用某些優秀的、通用的設計模式,這樣既加快模型的建立速度,又能和其它系統整合。但是,碰到一些瓶頸問題的情況下,我們就需要對局部的設計模式做一些調整,以最佳化整個系統,上述三個模式就是對原有體系的補充,它們並沒有對整體的架構做出巨大的改變,卻突破了某些瓶頸(瓶頸往往是局部的)障礙,讓我們的產品更好地服務於使用者。將深入研究的問題開篇至今,我們主要探討了軟體層次上的解決問題,但是,必須肯定一點,如果你的硬體環境非常差(運行Java都有困難)或非常好(額外的儲存空間、超快的運算速度和富裕的網路頻寬),上述途徑對你來說很難有大的協助。前一種情況,我建議你升級硬體裝置到軟體廠商推薦的配置(強烈反對最小配置),以使應用伺服器、資料庫、Java等軟體能夠運行自如;後一種情況,我沒什麼話可說,花錢是解決這個問題最好的辦法。本文並未談及線程池和告訴緩衝這兩個非常重要的概念,因為筆者認為,它們是針對局部時間高訪問量的瓶頸問題的解決,不能理解為簡單的速度瓶頸問題,所以我會在下一篇文章中分析這種特殊的情況和提出解決問題的辦法。也許你對這一點更關心一些,認為自己的問題就出在這個地方,這是非常好的思考問題的方式,你已經抓住了問題的關鍵。但是,我還是建議你通讀一下本文,讓自己對速度瓶頸問題有更好的理解,並掌握在解決問題的過程中,分辨常態和暫態,從而選擇不同的思路入手。其實,本文談及的就是速度瓶頸問題的常態,而下一篇文章討論的將會是暫態,希望你能夠漸入佳境。JDO(Java Data Object)是需要我們關注的一個API,它定義了新的資料存模數型,直接借鑒了DAO設計模式。不同的資料來源,有不同的資料存取技術,就有不同的API供開發人員使用。JDO正是為瞭解決這個問題而產生的,它實現了隨插即用的資料存取的實現和持久資訊(包括企業資料和本機存放區的資料)以Java為中心的視圖。因此,開發人員只關注建立那些實現商業邏輯的類和用它們來表現資料來源的資料,而這些類和資料來源之間的映射則由那些EIS領域的專家來完成。如果大家對JDO感興趣的話,那麼我會寫第三篇文章把其詳細介紹給大家,並給出樣本應用。