文章目錄
- 緩衝概述
- Declarative Caching Services for Spring
- 聲明式快取服務的優點
- 聲明式快取服務的工作原理
- 配置聲明式快取服務
- 下載
- 結束語
緩衝概述
簡而言之,緩衝涉及對檢索開銷大的資料的臨時性儲存,它通過避免從未經處理資料源檢索附加資料,從而實現了對用戶端的快速響應。例如,緩衝避免了應用伺服器對資料庫的調用,若應用伺服器與資料庫不在一處,則此類調用可能要涉及網路互動,因此,緩衝將帶來可觀的效能獲益。使用緩衝,應用程式回應時間將大大最佳化,這是因為降低了資料庫工作負載並釋放了網路頻寬。
快取服務非常適用於分布式系統。遠程調用的速度極慢,而且要佔用寶貴的網路頻寬,這就帶來了效能問題。對遠程服務的調用從建立請求開始,這是通過將其參數編組為一種可通過線路傳輸的格式(如XML或位元組流等)而完成的。一旦接收到請求,必須在伺服器端解組以便開始處理。類似的過程也適用於服務所產生的響應,這又增加了額外的開銷。緩衝能夠消除(至少是減少)遠程調用,因而能夠大大提升效能。
儘管有著種種好處,但緩衝也會使公司專屬應用程式程式的設計、開發和部署的複雜程度大大提高。不應在沒有清楚瞭解業務需求的情況下使用緩衝,此外,還必須使用測試結果和其他可靠的證據來協助決策。
緩衝的基本指導原則如下:
- 通常,添加緩衝來減少遠程調用是值得的。這將通過避免網路互動而提升效能。
- 唯讀、引用資料的緩衝是必要的最佳化措施。例如,緩衝美國各州列表可避免重複檢索那些基本上不會發生變化的資料。
- 對於給定的參數值集合總是返回相同資訊的服務響應非常適合應用緩衝技術。
- 若應用程式能夠容納陳舊資料,則可實現動態資料——即讀/寫資料——的緩衝。新聞Web網站是一個不錯的例子,緩衝為其帶來的效能提升使其在幾秒內即可顯示新聞。
- 要緩衝的資料量應在可控制的範圍之內,否則將佔用過多的記憶體空間。
- 在整個叢集內緩衝動態資料並在每個節點處同步資料是非常困難的。考慮選用第三方緩衝解決方案不失為明智之舉。
- 應避免緩衝即時資料(如股市報價)和敏感性資料(如口令和社會保險號碼)。
- 緩衝可能會造成一些與安全性和審計相關的問題。緩衝可能截獲請求並不經安全檢查地處理這些請求,從而導致安全屏蔽失效。
實現自己的緩衝並非易事。您或許不得不應對一些複雜問題,如線程管理、陳舊資料清除和叢集支援等。目前有許多高品質的第三方緩衝實現,包括開源實現和商業實現,它們可使您不必為建立(更重要的是)維護自己的緩衝而煩惱。
Declarative Caching Services for Spring
Spring Framework 是一個開源應用程式框架,其功能之一就是為普通java對象提供企業服務。使用Spring建立的應用程式大多為多層的資料中心型公司專屬應用程式程式,且具有多個並發使用者。此類應用程式可能還會與某種類型的遠端技術(如遠程EJB、Web services、RMI或CORBA等)相整合。公司專屬應用程式程式對於核心業務非常重要,人們期望這些應用程式能交付高效能。
Declarative Caching Services for Spring為那些Spring驅動的應用程式提供了聲明式緩衝。聲明式緩衝不涉及任何編程,因此是應用和調優快取服務的更輕鬆、快捷的方式。快取服務的配置可完全在Spring loC容器內完成。聲明式緩衝:
- 提供對不同類型的緩衝提供者的支援,如 EHCache、JBoss Cache、Java Caching System (JCS)、OSCache 和 Tangosol Coherence。
- 提供了一個比大多數前述API更簡單易用的統一API,它可通過編程方式使用快取服務。請注意,如果沒有特殊的需求,我們並不推薦您通過編程方式使用緩衝。
- 聲明式快取服務還提供了聲明式緩衝重新整理,以避免儲存陳舊資料。
- 支援不同的聲明式配置策略。
- 可輕鬆擴充聲明式快取服務以支援附加的緩衝提供者。
聲明式快取服務的優點
為更好地理解聲明式緩衝及其優點,讓我們查看以下程式碼片段。這是一個客戶管理程式的簡單實現,它根據給定的客戶ID從資料來源中檢索客戶資訊。為提升效能,從資料訪問層中檢索到的Customer對象編程地儲存在一個Coherence緩衝內。此外,為避免儲存陳舊資料,每更新一位客戶的資訊,包含客戶資訊的緩衝就被重新整理一次:
public class CustomerManager { private CustomerDao customerDao; private CacheKeyGenerator keyGenerator; public Customer load(long customerId) { validateCustomerId(customerId); Customer customer = null; Serializable key = keyGenerator.generateKey(customerId); NamedCache cache = getCache(); customer = (Customer) cache.get(key); if (customer == null) { customer = customerDao.load(customerId); cache.put(key, customer); } return customer; } public void update(Customer customer) { customerDao.update(customer); Serializable key = keyGenerator.generateKey(customer.getId()); NamedCache cache = getCache(); cache.remove(key); } private NamedCache getCache() { return CacheFactory.getCache("customerCache"); } // rest of class implementation}
儘管緩衝的使用提升了應用程式的效能,它也帶來了額外的(不必要的)複雜性。編程式地使用緩衝帶來了以下問題:
- 代碼更難以理解。要瞭解核心功能尤為困難。在我們的樣本中,只有那些以粗體顯示的程式碼實現了方法的真正功能。
- 代碼更難以維護。緩衝提供者的調用為嵌入式的,並且延伸到整個應用程式中。因此,任何涉及緩衝的維護工作都很可能會影響系統的核心功能。此外,對緩衝功能的任何更改都要求存取碼段的每一個副本,更糟糕的是,彼時我們可能並不知道它們究竟在哪裡。
- 代碼更難以測試。在我們的樣本中,我們需要一個Coherence CacheFactory,或者至少是一個類比它的模仿對象,這樣才能測試方法的商務邏輯。
- 代碼更難以重用。在緩衝需求不同或根本不需要緩衝的應用程式中,重用CustomerManager類將是一項艱難(甚至完全不可能)的任務。
聲明式緩衝封裝了緩衝的執行方法,並從Java代碼內消除了對緩衝實現的依賴。引入聲明式緩衝後,上例中的方法僅實現其核心需求:
public class CustomerManager { private CustomerDao customerDao; public Customer load(long customerId) { validateCustomerId(customerId); return customerDao.load(customerId); } public void update(Customer customer) { customerDao.update(customer); } // rest of class implementation}
以下的XML程式碼片段闡明了在Spring loC容器中(而不是在Java代碼中)如何為CustomerManager執行個體配置快取服務:
<bean id="customerDaoTarget" class="org.springmodules.cache.samples.dao.CustomerDao" /> <!-- Properties --></bean><coherence:proxy id="customerDao" refId="customerDaoTarget"> <coherence:caching methodName="load" cacheName="customerCache" /> <coherence:flushing methodName="update" cacheNames="customerCache" /></coherence:proxy><bean id="customerManager" class="org.springmodules.cache.samples.business.CustomerManager" /> <property name="customerDao" ref="customerDao" /></bean>
聲明式緩衝配置有效地將緩衝功能從應用程式的核心需求中分離出來,解決了上文提及的眾多問題,還帶來了以下優點:
- 更明晰的責任分離。系統中的模組僅負責其核心需求,不再負責緩衝功能。這將帶來更出色的可追溯性。
- 更高的模組化程度。將緩衝功能從核心模組中分離出來減少了重複代碼(支援“一次且僅一次”原則),並有助於避免代碼混亂。
- 設計決策的後期綁定。使用聲明式緩衝,開發人員可將與緩衝實現和調優有關的決策制訂延後。開發人員可將精力集中在應用程式的當前核心需求上。聲明式配置支援 YAGNI(“you aren't gonna need it”的縮寫,意為“您將不會需要它)原則,僅在開發人員確實需要的時候才允許他們為應用程式添加緩衝,而且不需要進行系統級的更改。
聲明式快取服務的工作原理
聲明式快取服務使用運行時方法攔截為Spring託管的bean交付透明、代碼無關的緩衝和重新整理。配置只涉及選擇應截獲哪些方法以及應如何將這些快取服務應用於這些方法。下面給出了聲明式快取服務的工作原理概要。
圖1中的活動圖表展示了緩衝方法攔截器的工作原理:
圖 1. 運行中的緩衝
緩衝方法攔截器首先使用所截獲方法的簽名和所提供的參數值產生一個惟一鍵。隨後,攔截器在緩衝中搜尋儲存在所產生的鍵下的對象。若找到了對象,就立即返回給用戶端,繞過所截獲方法的執行。若攔截器未在緩衝中找到任何值,則執行所截獲的方法,並將傳回值儲存在緩衝中(使用產生的鍵),然後將緩衝的對象返回給用戶端。緩衝條目的鍵是通過可插入的鍵產生器自動產生的。目前僅提供了一條策略,HashCodeCacheKeyGenerator,它根據所截獲方法的散列碼及其參數建立惟一鍵。可通過實現CacheKeyGenerator介面輕鬆建立鍵產生器。
圖2中的活動圖表展示了緩衝重新整理方法攔截器的工作原理:
圖 2. 運行中的緩衝重新整理
緩衝重新整理方法攔截器可配置為在執行所截獲方法之前或之後重新整理緩衝。一個攔截器可重新整理一個緩衝的一個或多個地區,也可重新整理整個緩衝,具體取決於其配置方式及所使用的緩衝提供者。
配置聲明式快取服務
聲明式快取服務提供了一種簡單而直觀的配置。各緩衝提供者都有其需在Spring loC容器配置中聲明的名稱空間。以下樣本展示了如何聲明coherence名稱空間以及如何指定其位置:
< xml version="1.0" encoding="UTF-8" ><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:coherence="http://www.springmodules.org/schema/coherence" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springmodules.org/schema/coherence http://www.springmodules.org/schema/cache/springmodules-tangosol.xsd">
在設定將作為快取服務目標的Spring bean之前,我們需要將一些系統級的配置設定包含進來,它們將影響快取服務的行為方式:
<coherence:config failQuietly="false" serializableFactory="XSTREAM" />
緩衝的首要目標是要透明地提升一個應用程式的效能。緩衝不應影響核心商務邏輯,也不應改變應用程式的行為。若緩衝出現故障,應用程式不應停止執行,特別是在生產中更應如此。從另外一個角度來說,在某些情況下,您可能希望應用程式在出現任何故障時停止運行,包括緩衝故障在內——例如,在執行開發期間的整合測試時,必須在將應用程式投入生產之前找到配置錯誤。
failQuietly屬性賦予開發人員決定緩衝故障是否應停止執行應用程式的能力。將此屬性設定為true時,任何緩衝失敗都不會對應用程式造成任何影響。其預設值為false。
一旦緩衝準備好開始運行,您將會發現,在某些情況下,需要在檔案系統中儲存緩衝對象,或者將緩衝中的更改複製到一個叢集的所有節點中。緩衝提供者能夠在Java序列化的協助下執行這些功能,這就意味著要儲存在緩衝中的對象應實現java.io.Serializable介面。
當需要將那些不可序列化和不受控制的對象(例如,由JAXB產生的對象)儲存在緩衝中時,這種需求將引發問題。serializableFactory屬性提供了一種解決方案,它對不可序列化的對象強制執行序列化,從而解決了這個問題。它僅提供了一種策略,XStreamSerializableFactory,它使用XStream 將對象序列化為XML,之後才將其儲存在緩衝中。在您閱讀本文時,一種使用 JBoss Serialization 的新策略應已問世。
強制執行對象序列化的一種替代方案就是使用AspectJ的內部型別宣告讓類實現java.io.Serializable。以下樣本假設類Customer不可序列化,且需要儲存在一個叢集緩衝中:
public aspect SerializationAspect { declare parents : Customer implements java.io.Serializable;}
如需獲得內部型別宣告的更多資訊或AspectJ的簡介,請參見AspectJ文檔。
聲明式快取服務提供了不同的配置策略,使開發人員有機會選擇最適合於其應用程式需求的策略。下面幾節介紹了所提供的各種配置策略。
按bean配置
此配置策略非常簡單,且極易設定。快取服務的配置在一個proxy元素內執行,此元素擁有一個指向單一Spring託管bean的引用。必須在refId屬性內提供要引用的bean的ID。還需要選擇應用快取服務要攔截的方法。caching和flushing元素分別指定哪些方法應觸發資料緩衝以及哪些方法應觸發緩衝重新整理。這兩個元素提供了methodName屬性,指定要攔截的方法名稱。根據不同的緩衝提供者,這些元素還包含不同的其它屬性,用於指定特定緩衝的緩衝方式及緩衝重新整理方式。以下樣本使用Coherence展示了按bean配置:
<bean id="customerDaoTarget" class="org.springmodules.cache.samples.dao.CustomerDao" /> <!-- Properties --></bean><coherence:proxy id="customerDao" refId="customerDaoTarget"> <coherence:caching methodName="load" cacheName="customerCache" /> <coherence:flushing methodName="update" cacheNames="customerCache" when="after" /></coherence:proxy>
請注意,應使用選定緩衝提供者的名稱空間限制每一個與緩衝相關的XML元素。在樣本中,我們設定快取服務來將CustomerDao.load方法的傳回值以customerCache為名稱儲存在Coherence緩衝中,並在執行完CustomerDao.update方法後重新整理同名緩衝。這裡要重點提示一下,要配置依賴於CustomerDao的bean,應使用proxy元素的id,而不是原始bean的id:
<bean id="customerManager" class="org.springmodules.cache.samples.business.CustomerManager"/> <property name="customerDao" ref="customerDao" /></bean>
另一種替代方案是在proxy元素內聲明bean定義,這將使配置更簡潔:
<coherence:proxy id="customerDao"> <bean class="org.springmodules.cache.samples.dao.CustomerDao" /> <!-- Properties --> </bean> <coherence:caching methodName="load" cacheName="customerCache" /> <coherence:flushing methodName="update" cacheNames="customerCache" when="after" /></coherence:proxy>
按bean配置提供了一種設定快取服務的簡便方法。由於需要為每個bean定義一個beanRef元素,因此最好在應用程式中需要應用快取服務的對象數量不多時使用這種方法。
原始碼級中繼資料屬性
儘管按bean配置提供了一種配置快取服務的直觀方式,但若應用程式中包含大量Spring託管的bean,則很有可能會得到大量與配置相關的XML。為簡化配置,可以使用原始碼級中繼資料屬性來“修飾”應用程式,這些屬性指出應攔截哪些方法以應用快取服務。聲明式緩衝支援 Commons Attributes 和 J2SE 5.0注釋。
Commons Attributes允許使用J2SE 1.4或更早版本嚮應用程式添加中繼資料屬性。由於Java編譯器不識別這種類型的中繼資料屬性,因此需要一個額外的編譯過程。Commons Attributes中繼資料的編譯會產生必須使用應用程式的源檔案編譯的原始碼。不存在對Commons Attributes中繼資料的IDE支援,這表明重構之類的常見軟體開發實踐也不受支援。以下樣本展示了Commons Attributes的使用方法。請注意,以下配置與前一節中的配置等效:
public class CustomerDao { /** * @@Cached(modelId="cachingModel") */ public Customer load(long customerId) { // method implementation } /** * @@FlushCache(modelId="flushingModel") */ public void update(Customer customer) { // method implementation } // rest of class implementation}
原始碼級中繼資料屬性僅限於描述是否應該應用快取服務,而不是描述應如何執行緩衝。應如何執行緩衝是在Spring loC容器中描述的。在我們的樣本中,中繼資料僅指明了應緩衝CustomerDao.load方法的傳回值,然後是指定在id為cachingModel的模型中的設定。此模型是在Spring loC容器中聲明的,其中包含特定於緩衝的設定,描述了緩衝中儲存值的方式。同樣的規則也適用於重新整理。以下樣本展示了中繼資料屬性所引用的模型的聲明:
<coherence:commons-attributes> <coherence:caching id="cachingModel" cacheName="customerCache" /> <coherence:flushing id="flushingModel" cacheNames="customerCache" when="after" /></coherence:commons-attributes><bean id="customerDao" class="org.springmodules.cache.samples.dao.CustomerDao" /> <!-- Properties --></bean>
id為cachingModel的模型指明了對象應使用customerCache名稱儲存在Coherence緩衝中。Id為flushingModel的模型以同樣的方式指明了執行完截獲的方法後應重新整理同一緩衝。任何與緩衝提供者相關的更改都應該僅在Spring loC容器內執行,且不應影響與緩衝相關的中繼資料屬性。此類更改不需重新編譯Java原始碼。此外還要注意,要處理與緩衝相關的中繼資料,還需在Spring容器中聲明一個以原始碼級中繼資料修飾的類的執行個體(在我們的樣本中為customerDao bean)。
J2SE 5.0注釋是一種更為結構化的為應用程式添加中繼資料屬性的格式。由於注釋是內建語言屬性,Java編譯器將其識別為不同的語言結構,從而克服了與Commons Attributes相關的問題。使用J2SE 5.0注釋的快取服務配置幾乎與使用Commons Attributes的配置完全相同。實際上,這隻需替換Java原始碼中的幾行代碼:
@Cacheable(modelId = "cachingModel") public Customer load(long customerId) { // method implementation } @CacheFlush(modelId = "flushingModel") public void update(Customer customer) { // method implementation } // rest of class implementation}
如需瞭解有關J2SE 5.0注釋的更多資訊,請參閱 JDK 1.5文檔。在Spring loC容器中從Commons Attributes切換到J2SE 5.0注釋僅需以annotations元素替換commons-attributes元素:
<coherence:annotations> <coherence:caching id="cachingModel" cacheName="customerCache" /> <coherence:flushing id="flushingModel" cacheNames="customerCache" when="after" /></coherence:annotations><bean id="customerDao" class="org.springmodules.cache.samples.dao.CustomerDao" /> <!-- Properties --></bean>
在Spring loC容器中,使用原始碼級中繼資料屬性的快取服務僅設定一次。您可以按照自己的需要添加儘可能多的bean,而無需進行額外配置。因此,在需要對大量對象應用快取服務時,使用原始碼級中繼資料屬性進行配置是理想的解決方案。
BeanNameAutoProxyCreator
類似於中繼資料屬性方法,BeanNameAutoProxyCreator允許在Spring loC容器內為大量bean配置聲明式服務,而無需進行額外配置。與中繼資料屬性方法不同的是,BeanNameAutoProxyCreator不需要對Java原始碼進行任何更改。
BeanNameAutoProxyCreator允許對大量bean應用一個或多個方法攔截器。僅需配置快取服務一次,隨後向代理建立器提供bean的名稱即可。以下樣本展示了如何在BeanNameAutoProxyCreator中指定與緩衝相關的方法攔截器:
<coherence:methodMapInterceptors cachingInterceptorId="cachingInterceptor" flushingInterceptorId="flushingInterceptor"> <coherence:caching methodFQN="org.springmodules.cache.samples.dao.CustomerDao.load" cacheName="customerCache" /> <coherence:flushing methodFQN="org.springmodules.cache.samples.dao.CustomerDao.update" cacheNames="customerCache" /></coherence:methodMapInterceptors><bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <idref local="customerDao" /> </list> </property> <property name="interceptorNames"> <list> <value>cachingInterceptor</value> <value>flushingInterceptor</value> </list> </property></bean><bean id="customerDao" class="org.springmodules.cache.samples.dao.CustomerDao" /> <!-- Properties --></bean>
如您所料,上例中顯示的配置與按bean配置和原始碼級中繼資料屬性兩節中的配置結果完全相同。Spring Framework提供BeanNameAutoProxyCreator。如需瞭解更多資訊,請參閱Spring 線上文檔。
下載
下載本文範例程式碼: caching.zip
結束語
儘管緩衝能大大提升公司專屬應用程式程式的效能,但它也會為應用程式生命週期帶來嚴重的複雜性。本文介紹了Declarative Caching Services for Spring,這是一個封裝此類複雜性的架構,它為由Spring驅動的應用程式提供了代碼無關的廣域網路快取和緩衝重新整理。
以Spring的精神為導向,聲明式緩衝為多種開源和商業緩衝提供者提供了開箱即用的支援。它還提供了不同的配置策略。由開發人員來選擇最適於項目需求的組合方案。
使用聲明式緩衝,開發人員可以將精力集中在其應用程式的核心需求上,最終得到易於理解、維護、測試和重用的代碼。由於快取服務是在Spring loC容器內配置的,因此可僅在真正需要的時候添加它們,而且無需進行系統級更改。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=2046377