http://www.knowsky.com/366521.html
在實際的任何一個系統中,查詢都是必不可少的一個功能,而查詢設計的好壞又影響到系統的響應時間和性能這兩個要害指標,尤其是當數據量變得越來越大時,于是如何處理大數據量的查詢成了每個系統架構設計時都必須面對的問題。本文將從數據及數據查詢的特點分析出發,結合討論現有各種解決方案的優缺點及其適用范圍,來闡述J2EE平臺下如何進行查詢框架的設計。
Value List Handler模式及其局限性
在J2EE應用中,對于大數據量查詢的處理有許多好的成功經驗,比如Value List Handler設計模式就是其中非常經典的一個,見圖1。該模式創建一個ValueListHandler對象來控制查詢的執行以及結果集的緩存,它通過DAO(Data Access Object)來執行查詢,并將數據庫返回的結果集(傳輸對象Transfer Object的集合)緩存起來,接下來的客戶端查詢請求將直接從緩存中獲得。它的特點主要體現在兩點:服務器端緩存數據,每次只返回客戶端本次操作所需的數據,通過這兩個措施來減少數據庫的訪問次數以及增加客戶端的響應速度,達到最優的查詢效果。當然,這里面隱含一個前提就是客戶端采用分頁的方式來瀏覽數據。關于該模式的具體介紹,請參考[Core J2EE Patterns]一書。
但是在實際的應用過程中,會發現該模式存在一定的局限性,其實可以說是該模式應用具有一些前提條件:
1、由于緩存是以內存來換性能,這對于小數據量會工作得很好,但是假如結果集很大,內存消耗將會非常嚴重。同時,消耗在處理結果集上的時間也會越來越長,比如要循環讀取記錄集中的數據,然后依次填充每個傳輸對象,想想看幾百萬條數據這樣處理起來肯定讓人不能忍受。過長的處理時間不僅降低反應速度,同時還會占用寶貴的數據庫連接資源,造成其它地方無連接可用。雖然,在DAO模式中利用CachedRowSet,Read Only RowSet ,RowSet Wrapper List等策略(詳見參考資料)來代替Transfer Object Collection策略,有效地提高了處理速度,但是仍然存在著在大集合數據中進行定位、遍歷等問題。試想一想,即使在CachedRowSet中的absolute(2000000)也是非常費時的操作。所有這一切的根源就在于緩存是一次性讀取所有的數據,雖然有時你可以利用業務邏輯來強制性增加一些限制條件(比如產品查詢必須選擇大類和次類),但這種限制往往是不牢靠的或者說只是一時的權宜之計。也有人提出,可以不必緩存所有的查詢結果,而采取只緩存部分結果集,比如500,1000條,但這樣一來,就涉及到復雜的查詢數據是否越界的控制,增加了復雜度,同時也不易實現。
2、既然使用緩存,那就不得不面對一個數據更新的問題,使用緩存,實際上就假定了在數據緩存期間,數據庫中的數據不會改變,或者這些改變可以不被反映出來。但是,在很多場合下(比如常見的業務系統中)這些數據庫中的數據經常會發生變化,而且這些改變需要及時反映給客戶端。
3、緩存其實存在一個基本前提,就是緩存的數據會被客戶端反復查詢使用,具體到分頁查詢就是客戶會選擇不同的頁數來查看數據。假如客戶端的查詢條件始終變化,或者用戶基本上只關心第一頁的數據(仔細琢磨一下用戶的習慣,這在很多中應用場合都很常見),那緩存就失去了應有的意義,變得多此一舉了。
數據分析
所以說,在決定是否應用某種設計模式前,我們需要對被查詢數據的特點以及這些數據以何種方式被使用(查詢的特點)進行一個分析,根據不同的結論來決定采用何種處理策略。而且,數據本身的特點和被使用的方式往往交織在一起,需要綜合起來考慮,但這其中主要的考量點還是數據查詢的特點。
一般來說,可以從以下幾個方面來分析數據:
1、 數據量大。
這是我們今天討論的數據的一個最基本特點,這個特點在查詢框架設計時要引起足夠的重視。
注重:大數據量的查詢是指查詢時匹配條件的數據量大,而不是指表中的數據量大,雖然大部分時候這兩者都是一致的。因為在某些情況下,業務邏輯可以限制或者只需要一次獲取很少量的數據,而查詢的表中的數據量卻可能很大,那這種情況就不屬于本文的討論范圍。
2、 關聯復雜,多表關聯。
越是簡單的數據可能關聯越少,而越是復雜的數據往往都是多表關聯,這樣很多時候你需要將這幾張表作為一個整體來考慮。
3、 變化頻率。
從這個角度出發,可以大致將數據分為以下幾類:幾乎不變化的睡眠數據;有規律定時更新的數據,比如招聘網站的職位信息;經常性無規律更新的數據。
4、 成長性。
數據是否具有成長性,要預見數據的成長性,并在現有方案中考慮這種成長性,避免到時候查詢框架的重新設計,象大部分的業務數據都具有這種成長性。
注重:這里也要非凡注重區分數據本身的成長性和數據查詢的成長性,這看似等同的兩者其實還是存在很大的區別。就拿招聘網站來說,有效職位的數據肯定是一天天在增加,具有高成長性,但是在某個區間(比如一個月,一個星期)內的有效職位查詢則變化不會太大,不具有成長性。而后者卻往往是實際系統中最常碰到的查詢情況。
5、 數據查詢的頻率和方式。
所有的數據查詢不可能被等同地使用,你要分清楚系統中的幾個要害查詢,這些查詢使用頻率高,響應要快。試想一想,假如一個電子商務系統的產品查詢每次都要讓顧客等上十秒鐘,結果就可想而知。
用戶的使用習慣分析
除了對數據查詢本身需要進行分析之外,我們還需要去分析一下用戶如何來使用或者看待這些數據,用戶的使用習慣如何。有人可能覺得這作用不大,或者很難去分析,其實查詢的最終使用者是用戶,他們的一些習慣會很大程度上左右你的設計。
1、 用戶關心數據哪些方面的特性,不關心哪些方面的特性。
上面我們分析了數據本身的許多特性,那用戶對其中哪些特性最敏感呢?比如說對臟數據非凡不能接受,那我們就必須在查詢框架設計時非凡照顧到這一點。因為再好的框架設計都不可能在每個方面都能達到最優的效果,當必須有所取舍的時候,我們就要明白哪些特性是客戶最關心的。
2、 用戶如何來使用數據。
現在一般查詢的客戶端都采用分頁的方式,一個查詢可能會存在十幾頁甚至幾十頁結果。對于某些查詢,用戶可能往往只關心第一頁或者前幾頁的結果,比如用戶需要查詢出最近完成的工單,而對于另外一些查詢,用戶可能對所有頁結果都很關注,比如用戶查詢出最近三天新增的招聘職位。這不同類型的查詢在查詢框架設計的時候都需要有所考慮并給予不同的處理策略。
查詢框架的設計
對數據及用戶使用習慣進行了仔細的分析,接下來就可以根據這些分析來設計你的查詢框架了。在J2EE架構下,對于大數據量的查詢主要采取以下兩種方法:
基于緩存的方式:
從數據庫得到全部(部分)數據,并將其在服務器端進行緩存,接下來的客戶端請求,將直接從緩存中取得需要的數據。這其實就是Value List Handler模式的原理,它主要適用于數據量不是非常大,變化不是很頻繁(或者變化頻繁但是有規律)且不具有成長性的情況,比如招聘網站或者電子商務網站的大部分查詢就非常適合采取這種方式。
采用這種方式,要非凡注重第一次查詢問題,避免響應性能達不到要求,因為每個查詢第一次都需要連接數據庫,從中獲取數據并緩存起來,所以第一次查詢會比接下來的查詢都顯得更慢一些。
對于數據的緩存,有以下幾種實現方式:
? 直接緩存在服務器端
Value List Handler模式就采取這種方式,并且可以根據不同的情況采取不同的緩存策略,比如Transfer Object集合,CachedRowSet等,這取決于你的DAO實現策略。
? 用臨時表來保存查詢結果
WLDJ(www.sys-con.com/weblogic/)雜志2004年第7期上有一篇名為“Handling Large Database Result Sets”的文章,它具體介紹了如何利用臨時表來改良Value List Handler模式以支持大型的J2EE應用。
當然除了以上這些方法以外,實現緩存也可以求助于操作系統的特定實現,以前我在IBM DW發表過一篇探討MMF在Java中應用的文章(見參考資料),可惜未有深入,有愛好的朋友可以參考一下。
在使用Value List Handler模式時,要非凡注重以下幾點:
1、 該模式一般和DAO模式搭配使用。
2、 該模式有POJO,stateful session bean兩種實現策略。
3、 假如采取stateful session bean實現策略,則默認該緩存的時間長度為整個用戶會話。
前面我們也提到過,假如數據不是絕對不變的,那緩存就面臨更新的問題,一旦更新就可能存在著數據不一致,假如恰巧客戶也希望能夠看到變化的效果,這個時候就需要采取某種措施來保證這種一致性。常見的措施可以是設置一個標志位,每次發生數據更新后都將其對應的標志位更新,查詢時假如發現標志位更新了,就直接從數據庫獲取數據,而不是從緩存中獲取數據。另外一種方式就是數據更新的同時主動去清空session中的緩存,假如采用stateful session bean實現策略的話。
當然,采取緩存方式的大數據量查詢一般來說都不大可能碰到設置更新標志位的問題,因為這種應用方式決定了數據不大可能變化,或者數據變化不要求馬上反應給用戶。比如招聘網站新增加了一些職位信息,假如這些更新恰巧發生在某些用戶的會話期間,且沒有設置更新標志位,那這些新增信息就不會反應到用戶的查詢結果中,這種處理方式也是可以接受的。