作者:自由Java傳道士 來自:水木清華 初識高速緩存和連接配接池 設想這樣一種情形:你突然口渴,需要一杯水來緩解,從心情上來講,當然是越快越好了。通常,一杯水的産生包括從水源(井水、河水或江水、甚至海水等)抽取,通過管道傳輸和裝置淨化,才到達你飲水的容器中。上述過程是必須的,但并不是每一杯水的産生都必須把上述過程重複一次。你可以用一個大一點的容器(例如缸或罐等)來盛大量的水,喝水之前分到杯子小部分中即可,你的代價隻是把水從缸轉移到杯子;你還可以在大量用水(例如洗澡或洗衣服等)時,隻需打開水閥,而不必臨時鋪設通往水源的管道和購買淨化水的裝置。因為水是人們生活不可缺少的東西,每時每刻都在被大量地使用,而且實體本質也完全相同,是以政府會鋪設管道和建設水處理站,完成那些比較困難的工作,達到資源共享的目的,而你也可針對自己的需求,用容器來盛那些具有特定用途的水。本文将要和你讨論的高速緩存和連接配接池與上述特定容器和傳輸管道有很多相似之處,它們都達到了同一個目的:在滿足使用者意願的前提下,盡可能地共享資源,以提高整個系統的性能。 高速緩存和連接配接池是資料通路中的重要技術,某些情況下的應用對通路資料庫的性能有巨大的提高,而且都得到了資料庫業界的普遍支援。前者由DBMS廠商針對自己的資料庫實作,提供可供使用者配置的方案;後者是JDBC的一個标準接口,由支援J2EE技術的應用伺服器廠商提供具體的實作,而你的Java程式代碼無需更改。本文将向你簡單介紹高速緩存和連接配接池的概念和機制,并以PointBase資料庫為例向你展示高速緩存的應用,而一個簡單的連接配接池應用場景将向你描述應用的條件和提高的性能。 Cache(高速緩存)和Connection Pool(連接配接池)的概念和機制 它們不是資料庫獨有的技術,但卻得到資料庫業界的普遍支援,并在其它資料存取和對象複用領域有很多類似的應用。 Cache(高速緩存) 作為個人計算機的日常使用者,你肯定聽說過這些名詞:Cache(高速緩存)、Memory(記憶體)、Hard disk(硬碟)。它們都是資料存取單元,但存取速度卻有很大差異,呈依次遞減的順序。對于CPU來說,它可以從距離自己最近的Cache高速地存取資料,而不是從記憶體和硬碟以低幾個數量級的速度來存取資料。而Cache中所存儲的資料,往往是CPU要反複存取的資料,有特定的機制(或程式)來保證Cache内資料的命中率(Hit Rate)。是以,CPU存取資料的速度在應用高速緩存後得到了巨大的提高。 對于資料庫來說,廠商的做法往往是在記憶體中開辟相應的區域來存儲可能被多次存取的資料和可能被多次執行的語句,以使這些資料在下次被通路時不必再次送出對DBMS的請求和那些語句在下次執行時不必再次編譯。 因為将資料寫入高速緩存的任務由Cache Manager負責,是以對使用者來說高速緩存的内容肯定是隻讀的。需要你做的工作很少,程式中的SQL語句和直接通路DBMS時沒有分别,傳回的結果也看不出有什麼差别。而資料庫廠商往往會在DB Server的配置檔案中提供與Cache相關的參數,通過修改它們,可針對我們的應用優化Cache的管理。下圖是在Win2K中配置MS Access資料源的界面,在"驅動程式"部分你可設定的頁逾時和緩沖區大小就是和Cache有關的參數。在後面的讨論中,我将展示一個更複雜的資料庫,向你解釋Cache對通路資料庫性能的影響和如何尋找最優的配置方案。 Connection Pool(連接配接池) 池是一個很普遍的概念,和緩沖存儲有機制相近的地方,都是縮減了通路的環節,但它更注重于資源的共享。下圖展示了建立"數據機池"以共享數據機資源的VPN撥号方案: 對于通路資料庫來說,建立連接配接的代價比較昂貴,是以,我們有必要建立"連接配接池"以提高通路的性能。我們可以把連接配接當作對象或者裝置,池中又有許多已經建立的連接配接,通路本來需要與資料庫的連接配接的地方,都改為和池相連,池臨時配置設定連接配接供通路使用,結果傳回後,通路将連接配接交還。 JDBC 1.0标準及其擴充中沒有定義連接配接池,而在JDBC 2.0标準的擴充中定義了與連接配接池相關的接口。與接口對應的類由應用伺服器廠商實作,你可在對伺服器的管理過程中調節某個資料庫連接配接池的參數。下圖簡略地描述了連接配接池的運作機制: 高速緩存的參數設定 在PointBase資料庫DB Server的配置參數清單中,我們可以找到這幾個參數:cache.checkpointinterval、cache.size、SQLCaching.size等。下面是對各個參數的描述: cache.checkpointinterval 檢查點的時間間隔 cache.size 高速緩存的最大頁數(素數時,性能最好) SQLCaching.size 高速緩存中SQL語句的個數 對于cache.checkpointinterval來說,和前面Access界面中的頁逾時是一個概念,它指定了頁面内容更新的時間間隔,這取決于你的應用對時效性的要求程度。 對于cache.size來說,指定了頁面的個數,一般應設定為符合你查詢結果的需求。至于為何是素數,我也納悶,不過還是遵照廠商的訓示吧。 對于SQLCaching.size來說,指定了存儲的經過編譯的SQL語句的個數,你可以把它設定為0,進而取消這個選項。 使用Cache後,性能到底有多大提高?我打開PointBase的Console通過JDBC驅動通路Server,将SQL菜單下的Timing Mode選上,以顯示各個步驟的耗費時間。執行語句是: SELECT * FROM PRODUCT_TBL 第一次通路,總計耗時1082毫秒,而編譯耗時771毫秒。 緊接着的第二次通路,總計耗時僅為160毫秒,而編譯耗時為0。 再接着的第三次通路,總計耗時僅為91毫秒,編譯耗時也為0。 關閉Console,等待超過30秒之後,重新開啟Console,執行相同的語句,總計耗時210毫秒,編譯耗時為20毫秒。 自等待超過30秒之後,執行語句,總計耗時101毫秒,編譯耗時為0。 由此可以看出,高速緩存的應用大大提高了通路資料庫的性能,而其參數的設定則要依據前面對它們的描述來進行,需要你仔細閱讀資料庫的配置文檔。 連接配接池的設定和應用 我選擇IBM公司的應用伺服器平台WebSphere來給大家示範連接配接池的設定,使你面對友好的Web界面,可以體驗到非常簡易的操作場景。 首先,我們進入WebSphere的管理控制台,這是一個非常漂亮的Web界面: 緊接着,我標明一個資料源:Session Persistence datasource,就可看到這個資料源的屬性配置了。在這兒,僅僅列舉和連接配接池有關的屬性: Minimum Pool Size 池中保持的連接配接的最小數目;有新的請求,且沒有激活連接配接可供使用時,池中連接配接數将增大,到最大連接配接數為止 Maximum Pool Size 池中保持的連接配接的最大數目;當這個數目達到,且沒有激活連接配接可供使用時,新的請求将等待 Connection Timeout 當連接配接數達到最大值,且激活連接配接都在被使用時,新的請求等待的時間 Idle Timeout 連接配接可在池中閑置的時間;超過将釋放資源,到最小連接配接數為止 Orphan Timeout 連接配接在被應用控制時,可閑置的時間;超過将傳回池中 你可以根據需要來修改這些數值,以滿足你的應用需要。接下來,我們讨論一下連接配接池的應用。 EJB通路資料庫(場景1,使用JDBC 1.0) import java.sql.*; import javax.sql.*; ... public class AccountBean implements EntityBean { ... public Collection ejbFindByLastName(String lName) { try { String dbdriver = new initialContext().lookup("java:comp/env/DBDRIVER").toString(); Class.forName(dbdriver).newInstance(); Connection conn = null; conn = DriverManager.getConnection("java:comp/env/DBURL", "userID", "password"); ... conn.close(); } ... } 如果EntityBean是一個共享元件,那麼每次客戶請求時,都要建立和釋放與資料庫的連接配接,這成為影響性能的主要問題。 EJB通路資料庫(場景2,使用JDBC 2.0) import java.sql.*; import javax.sql.*; // import here vendor specific JDBC drivers public ProductPK ejbCreate() { try { // initialize JNDI lookup parameters Context ctx = new InitialContext(parms); ... ConnectionPoolDataSource cpds = (ConnectionPoolDataSource)ctx.lookup(cpsource); ... // Following parms could all come from a JNDI look-up cpds.setDatabaseName("PTDB"); cpds.setUserIF("XYZ"); ... PooledConnection pc = cpds.getPooledConnection(); Connection conn = pc.getConnection(); ... // do business logic conn.close(); } ... } EJB元件利用JNDI的lookup()方法定位資料庫的連接配接池資源,利用getConnection()方法得到已經打開的資料庫連接配接,而用close()來釋放連接配接,放回池中。是以,與場景1相比,少了與資料庫建立實體連接配接的損耗。對于原本要頻繁打開和關閉實體連接配接的應用來說,通過這種建立邏輯連接配接并複用的方法,性能肯定能夠得到大幅度提高。 性能問題的深遠思索 性能問題并不局限于資料庫的應用上,而是存在于每個軟體系統中。我們希望軟體系統付出的最小,而獲得的最大,因而無時無刻不在優化它們。通過《Java程式通路資料庫的速度瓶頸問題的分析和解決》和本文,我對Java程式通路資料庫的性能問題做了分析,并提供了優化Java程式的解決方案,希望對你有所幫助。 也許你會關心碰到類似的性能問題時應如何分析和解決,我就針對此次探讨"通路資料庫的速度瓶頸"問題的過程中碰到的難題、網友的意見和自己的體會,作一個關于"方法論"的經驗總結,希望能夠抛磚引玉,更好地解決類似的問題。 解決性能問題的幾條經驗 1. 不要讓硬體的低配置成為軟體正常運作的障礙,後者有更新前者的需求,請立即滿足;經常碰到這樣的問題"P166+64M的機子跑Win2K+MySql+JBoss,能跑麼?"我在回答"可以"的同時隻有對着螢幕發呆了。 2. 盡量使用商業軟體,并享受良好的售後技術支援;如果你沒有黑客精神,請不要使用自由軟體。 3. 分析好自己的問題,也許它的本質和他人的不同;沒有一把鑰匙可打開的任一把鎖。 4. 确定瓶頸環節的位置;解決了瓶頸問題,往往整個的性能問題就解決了,千萬不要抓着邊緣的問題不放。 5. 将瓶頸環節細分為多個順序的流程,用逐個替代的方法來試探瓶頸的核心位置;細分問題使你都問題有更進一步的了解。 6. 做自己能做的和該做的事情,始終面向自己的現實問題;不要嘗試那些應該由廠商解決的問題(例如,自己寫個JDBC驅動)。 7. 他人的方案隻供自己參考,解決要靠自己思考;你我應用的情形不同,應用解決方案要在了解他人的方案之後。 8. 觀察新技術,應用新技術;它往往包含了前人對問題解決的思路,隻是對你來說不可見。 9. 問題得到解決後,立即罷手,并彙報結果;在現實問題得到解決後,沒有必要花費精力在非核心的問題上,也許它們永遠不會被碰到,不要假想問題讓自己解決。 上述經驗為個人即興的總結,并未有嚴謹的邏輯推導,僅代表我解決技術問題的思路,供你碰到類似問題時參考。 |