WebSphere Commerce高速緩存技術
引言
前面章節中,對網絡流量的“瘦身”方面提出了建議來提升產品性能。本章節將主要介紹一下 WebSphere Commerce 產品中所提供的高速緩存來改善產品性能。
高速緩存技術在當前的互聯網應用中已經變得越 來越重要了,尤其是對像 WebSphere Commerce 這樣的企業級電子商務應用。主要原因當然是能夠在一定的硬 件拓撲結構和配置條件下,對站點中訪問最頻繁的頁面及對象進行緩存來充分提高服務器的性能,使頁面訪問 起來非常快,提高用戶體驗。
但是緩存技術就像一把雙刃劍,用的好,可以獲得預期的高性能;用不 好,不但提高不了性能,反而會導致性能下降,甚至引起嚴重的數據不一致的問題。
動態高速緩存作 為一種提高性能的手段,需要和其他產品設計一樣,在整個產品或項目的生命周期中考慮的越早越好。如果只 是在後期性能測試階段發現性能不達標,期望動態高速緩存來解決問題,往往難以如願。
高速緩存概 述
動態高速緩存的核心思想就是重用。在一個典型的 WebSphere Commerce 企業應用的設計架構中,如圖 1 所示,從數據庫端,應用服務器,web 服務器,到第三方服務商提供的網絡高速緩存(邊緣緩存),以及浏覽 器本身的緩存,我們可以看到緩存無處不在。可以看出緩存的位置離客戶越近,緩存的效果越好(速度越快) ,但是緩存失效刷新的代價越大。產品架構師需要從整體上去考慮和把握系統的緩存策略。
本文不再 詳細介紹浏覽器緩存以及邊緣緩存(常見的為 CDN 緩存),主要介紹一下 WebSphere Commerce 產品中所提 供的高速緩存以及最佳實踐。
圖 1. 典型的 WebSphere Commerce 系統架構
在 WebSphere Commerce 應用中, 我們使用的是由 WebSphere Application Server 提供的 WebSphere 緩存機制(DynaCache),可以把常用的 緩存內容分為以下幾種類型:
Servlet/JSP 內容高速緩存,緩存由 servlet 或 JSP 產生的整個頁面或頁面片段(Fragment)。
命令(Command)高速緩存,可以緩存命令,EJB 及 WebService 的返回對象。
數據(Data)緩存,緩存數據庫查詢結果。動態緩存的底層機制是通過 Distributed Map 實現的。
片段(fragment)緩存
實際系統中的頁面一般都比較復雜,一個頁面中既有相對穩定的信息,又有動態變化或是個性化的信息。 如圖 2,頭部工具條,迷你購物車,比較工具以及頁面上的其他片段都是可以被重復使用的。這種情況下,我 們就會把一整個頁面分成若干個不同的片段來進行緩存,“do-not-consume”屬性可以使某個片段獨立於整個 頁面,被單獨緩存。從而達到緩存對象和頁面最大程度的可重用,進而減少服務器端的負載,來提高系統性能 。
圖 2. 頁面片段示例
下面是如何緩存購物車片段的代碼。
清單 1. 購物車片段緩存代碼
<cache-entry> <class>servlet</class> <name>/Madisons/include/MiniShopCartDisplay.jsp</name> <property name="do-not-consume">true</property> <property name="save-attributes">false</property> <cache-id> <component id="DC_storeId" type="attribute"> <required>true</required> </component> <component id="DC_userId" type="attribute"> <required>false</required> <not-value>-1002</not-value> </component> <component id="DC_lang" type="attribute"> <required>true</required> </component> <component id="DC_curr" type="attribute"> <required>true</required> </component> <priority>1</priority> <timeout>3600</timeout> <inactivity>600</inactivity> </cache-id> <dependency-id>DC_storeId <component id="DC_storeId" type="attribute"> <required>true</required> </component> </dependency-id> <dependency-id>DC_userId <component id="DC_userId" type="attribute"> <required>true</required> </component> </dependency-id> <dependency-id>MiniCart</dependency-id> <dependency-id>MiniCart:DC_storeId <component id="DC_storeId" type="attribute"> <required>true</required> </component> </dependency-id> <dependency-id>MiniCart:DC_userId <component id="DC_userId" type="attribute"> <required>true</required> </component> </dependency-id> <dependency-id>MiniCart:DC_storeId:DC_userId <component id="DC_storeId" type="attribute"> <required>true</required> </component> <component id="DC_userId" type="attribute"> <required>true</required> </component> </dependency-id> </cache-entry>
另外還需要注意一個重要的屬性“do-not-cache”。我們知道緩存命中率對 動態高速緩存的效率也非常重要。對於 Servlet/JSP 緩存,待緩存的頁面中有些片段是不經常變化的(容易 命中),有些片段則是頻繁變化的(很難命中)。所以,WebSphere 動態高速緩存技術允許基於片段的緩存方 式。即便如此,對於某些基本不可能命中的片段仍然很難處理,這就影響了整體緩存命中率,降低了動態高速 緩存的效率。引入“do-not-cache”屬性,可以徹底不緩存這樣的片段,從而根本上解決了這個問題。
命令緩存
WAS 提供的基於 Command 的模式可以幫助用戶緩存 command,EJB,websphere 以及 java 對象的執行結 果。使用命令緩存的 Java 類必須從 com.ibm.websphere.command.cachableCommandImpl 派生。 該類的主要 執行邏輯為三部分。
Set:設置處理的輸入參數
Execute:處理的執行邏輯
Get:獲取處 理的輸出結果
圖 3. 命令緩存的系統運行模式
命令緩存 可以通過下面的方式進行設置。拷貝粘貼 全部或部分的 <cache-entry> 標簽內容從 <WC_HOME>/samples/dynacache/cachespec.xml 到 <WAS_HOME>/Stores.war/WEB- INF/cachespec.xml 或是 <WAS_HOME>/InitializationServlet.war/WEB-INF/cachespec.xml,這種方 法會使數據被緩存到“baseCache”的動態緩存實例中。這種配置方法提供了一定程度的靈活性,使客戶可以 對各被緩存項的一些子屬性比如 <priority>,<timeout>,<inactivity> 進行微調。通 常我們需要監控系統吞吐率,來調整這些屬性以獲得最好的性能。所有的命令緩存都定義在 default 的 DistributedMap 中。
清單 2 命令緩存的代碼:
<cache-entry> <class>command</class> <sharing-policy>not-shared</sharing-policy> <name>com.ibm.commerce.dynacache.commands.MemberGroupsCacheCmdImpl </name> <cache-id> <component type="method" id="getUserId"> <required>true</required> </component> <priority>1</priority> <timeout>86400</timeout> <inactivity>300</inactivity> </cache-id> <dependency-id>DC_userId</dependency-id> <dependency-id>DC_userId <component type="method" id="getUserId"> <required>true</required> </component> </dependency-id> </cache-entry>
數據緩存(data cache)
數據緩存從廣義上講,其實也屬於命令緩存的一種。為了與 WebSphere Commerce 中用到的其他命令緩存 加以區分。本文中所用提到的數據緩存,主要是指定義在特定的 DistributedMap 實例中的 objectCache。數 據緩存的配置參數不是在 CacheSpec.xml 裡配置的,而是在單獨的配置文件中配置的。
我們通過拷貝 粘貼 <WC_HOME>/samples/dynacache/cacheinstances.properties 的內容到產品環境中定制的 <WAS_HOME>/Stores.war/WEB-INF/classes/cacheinstances.properties 文件。在這個 cacheinstance 文件中,不同的 DistributedMap 實例可以定義不同的屬性,比如 cacheSize,enableDiskOffLoad, flushToDiskOnStop,disableDependenceId 等等。
需要注意的是, cache.instance.n.enable.CacheReplication 和 cache.instance.n.replicationDomain 這兩個屬性只能用 於集群環境中的設置,在缺省的 cacheinstances.properties 文件中是不存在的,因為單節點下設置這兩個 屬性會引起一些問題,這兩個屬性允許不同節點之間緩存的復制。
失效刷新(Invalidation)機制
緩存中最難也是最重要的就是要保證緩存內容的有效性,例如當產品的基本信息,或庫存或價格信息等發 生變更的時候,需保證客戶能夠浏覽到最新更新的內容。因此失效機制尤為重要。當數據庫中有數據被創建, 更新或刪除時,相應的緩存條目就會變成無效並應該被清除更新。對於 Servlet 緩存和命令緩存,通常需要 顯示地定義失效刷新機制。常用的失效刷新機制是基於 command 的主動失效刷新和基於時間的被動失效刷新 機制。
基於時間的失效機制
最簡單的失效機制是基於時間,例如下面的配置定義了此緩存項的 失效時間是 3600 秒。
清單 3. 時間失效示例
<cache-id> <component id="storeId" type="parameter"> <required>true</required> </component> <component id="DC_userId" type="attribute"> <required>true</required> </component> <timeout>3600</timeout> </cache-id>
基於命令的失效機制
基於命令的失效機制是通過 dependencyId 和 invalidationId 的匹配來使相關的緩存項被清除。動態緩存的框架會攔截被緩存的 command 的 performExecute() 方法,使其產生相應的緩存刷新事件,此事件中的 invalidation ID 是根據 cachespec.xml 中定義的規則產生。緩存框架在接受到緩存刷新事件後,會一一比對所有的數據緩存 dependency ID,所有跟此 invalidationID 匹配的緩存項都會被清除。
例如以下的失效機制定義了當 CatalogUpdateCmdImpl 被執行時,InvalidationId 為 catalogId:xxx 的緩存刷新事件將會產生:
清單 4. 命令失效示例
<cache-entry> <class>command</class> <name>com.ibm.commerce.catalogmanagement.commands.CatalogUpdateCmdImpl</name> <invalidation>catalogId <component id=”catalogId” type=”parameter”> <required>true</required> </component> </invalidation> </cache-entry>
當我們帶著參數“storeId=10001&catalogId=10010”,來執行命令 CatalogUpdateCmdImpl
時,就會產生 invalidation ID 為“catalogId:10010”的緩存刷新事 件。所有緩存條目中,dependency ID 為 catalogId:10010 的緩存對象都會被刪除。
對於數據緩存, 當數據發生變化時,WebSphere Commerce 觸發數據變化的代碼 (EJB bean 和 DSL) 會調用 WebSphere 的緩 存無效 API 直接刷新失效的數據緩存條目。開發人員通常不需要作額外處理來干預失效刷新。Commerce 的數 據緩存還提供了以下兩種途徑來方便緩存的失效:
第一:如果數據的變化是通過其他方式,例如是直 接通過操作數據庫 ( 如 massload) 來完成的,我們就需要參考 <WC_HOME>/schema/<database>/wcs.cacheivl.trigger.sql 示例中的代碼定義數據庫的 trigger,這些 triggers 會在數據插入,刪除,更新時在 CACHEIVL 表中插入相應的記錄 ( 其 DATAID 列即 InvalidationId)。DynaCacheInvalidationCmd 定時任務會定時處理 CACHEIVL 表並發出正確的緩存無效指令 。需要注意的是,從數據庫數據更新到 DynaCacheInvalidationCmd 定時任務執行中間有一段時間間隔,即存 在一段時間緩存中的內容是過期的數據。在實際生產環境中,我們應該與業務人員充分溝通,在保證數據正確 性與性能優化之間做一個權衡。
第二:如果您直接調用 JDBC 命令對使用了數據緩存的表進行了插入 ,刪除,更新,那麼客戶需要調用 WCDataCache.generateAndIssueInvalidationsForTable 方法來觸發緩存 的無效清除。
高速緩存的策略
緩存策略設計是以數據為中心的分析和設計過程。針對應用中涉及到的各種數據,分析其可以復用的程度 和變化的頻率,確定是否能緩存,在什麼地方緩存,以及如何失效刷新。緩存策略的調整和實現貫穿整個應用 的開發生命周期。緩存效果的好壞取決於緩存的命中率和緩存前後開銷的差異,對於緩存前後開銷差異較大的 數據訪問,或者是緩存命中率較高的數據訪問,都可以考慮緩存。具體實現可以依賴於架構師的個人經驗,也 可以來自於對以往系統中訪問模式的分析。整體上,我們推薦以下的緩存策略:
被頻繁訪問的對象或是頁面;
在一段時間內保持穩定的對象或是頁面;
包含各種用戶通用內容的對象或是頁面;
可以被很多部分所重用的對象或是片段;
但是包含用戶敏感信息的對象或是頁面是不允許被緩存的。
像網站的目錄結構,產品信息等不敏感的數據很明顯是可以被緩存到離用戶更近的,可以進行邊緣緩存, 甚至緩存到浏覽器。而對於敏感信息,則可以緩存到應用服務器端。
業務數據(operational data) ,是指由業務(transaction)操作觸發數據更新的數據。如訂單數據、庫存數據等。這類數據的特點是數據 的更新事件不可預知或控制。一般情況認為業務數據是不能緩存或不宜緩存的。由於業務數據對數據准確性要 求比較高,緩存之後有可能造成某些場景下的數據錯誤而導致用戶投訴。但實際上,業務數據的變更(刷新) 也是由具體事件觸發的。如果在數據變更的間隔內有很多次訪問(即緩存命中率高),也是值得緩存的。利用 WAS 動態緩存的規則刷新機制,可以做到在具體事件發生的時候(如提交訂單),觸發特定緩存條目的刷新。 但多數情況下,這種緩存機制只適用於服務器端緩存。如果想把數據推到邊緣緩存,則需要把緩存采用較短的 timeout(比如 5 分鐘)的方式進行被動刷新。即在性能和業務需求之間做一個權衡,既犧牲一部分的數據准 確性,也犧牲一定的緩存效率(降低了緩存命中率)。
個性化數據是指和特定用戶綁定(或高度相關 )的數據,如單個用戶的購物車列表、是否登錄、浏覽歷史等。通常的做法,個性化數據一般都緩存在客戶端 (服務器端可能還有一份副本數據)。由於客戶端緩存的容量限制(如果采用類似 cookie 的緩存機制),在 緩存的時候需要對緩存數據進行精簡。另外,通過對緩存數據的分析,可能其中包含一些變化不頻繁的內容數 據。如購物車和浏覽歷史中所含產品的詳細信息。這些信息其實是內容數據。比較好的做法是在客戶端緩存中 只存放產品 ID 和產品名,當客戶需要查看產品詳細信息的時候,再做異步加載(加載的時候,這些內容數據 很可能被邊緣緩存,所以性能也會很好)。
除指定要緩存的內容外,緩存設計還包括:指定緩存的實 例及每個實例的具體屬性(例如緩存大小,flushToDisk 屬性等等),整頁緩存或者片段緩存,制定各緩存無 效化機制等等。
與 WebSphere Extreme Scale 集成
在大型集群環境中,由於傳統的動態高速緩存是在每個應用服務器節點都有各自的緩存實例來存儲相應的 緩存條目,盡管我們不必保證所有節點的緩存條目必須一致,我們仍需要保證所有節點緩存條目一致的失效調 度,必然會帶來額外的網絡帶寬,CPU 以及 I/O 的消耗,從而導致大型集群環境中的動態高速緩存所產生的 正面性能提升和架構設計的預期存在著較大的差距。
WebSphere Extreme Scale 產品的天然特性可以 從一定程度上解決這樣的問題。WebSphere Extreme Scale 針對所有的應用服務器節點只有一個邏輯上的緩存 實例,也就意味著所有的緩存條目只有一個拷貝,不需要在各個緩存實例間進行緩存同步,緩存的失效刷新更 加方便簡單而不必依賴於 DRS (Data Replication Service)。並且 WebSphere Extreme Scale 是推薦被安 裝在獨立的硬件資源上,不會占用應用服務器的資源從而減少由於緩存刷新引起的應用服務器節點上的 GC, I/O 和 CPU 的影響。
由於每個 WXS 網絡可以采用獨立的拓撲結構和配置參數,我們推薦不同的緩存 實例保存在不同的 WXS 網格中。每個緩存實例中保存不同特性的緩存對象。這樣就可以根據不同緩存對象的 特性對 WXS 的配置進行優化。
常見的問題分析和最佳實踐
我已經啟動並更新了 cachespec.xml 文件,為什麼緩存沒有生效?
首先你要確 認是否把 cachespec.xml 文件放到正確的位置上,並且 cachespec.xml 文件內容定義符合規范,然後檢查系 統日志看是否有下面類似的信息,從而確認 cachespec.xml 文件已經被成功加載。
清單 5. 日志示例
[12/1/11 2:02:51:132 EST] 00000012 ConfigManager I DYNA0062I: Successfully loaded cache-instances from configuration file / opt/WebSphere/AppServer/profiles/demo/installedApps/WC_demo_cell /WC_demo.ear/Stores.war/WEB-INF/cachespec.xml. [12/1/11 2:02:51:140 EST] 00000012 ConfigManager I DYNA0047I: Successfully loaded cache-entries from cache configuration file /opt/WebSphere/AppServer/profiles/demo /installedApps/WC_demo_cell/WC_demo.ear/Stores.war/WEB-INF/cachespec.xml.
你可以通過 安裝部署Cache Monitor,監控和管理動態高速緩存是否正常使用。
為什麼緩存後看到 的頁面內容有重復?
使用 JSP 包含頁面片段,並使用緩存技術後,可能會出現頁面內容重 復的情況。以前面的迷你購物車為例,在同一個產品目錄頁面中可能出現兩個同樣的迷你購物車。出現這種情 況往往是因為 JSP 的 flush 屬性。
如果沒有 flush 屬性,動態高速緩存的頁面組合器將不知道 JSP 的頁面組合器何時寫完包含頁面的內容,從而導致被包含片段的內容在父頁面中也出現一次。解決方法是在使 用 JSP 包含的時候使用 flush 屬性。如下所示:
清單 6. flush 示例
<jsp:include path="fragment.jsp" flush="true"> <jsp:param name="paramname" value="${paramvalue}" /> </jsp:include>
清單 7. 使用 JSTL 的示例
<%out.flush ();%> <c:import url="fragment.jsp"> <c:param name="paramname" value="${paramvalue}"/> </c:import> <%out.flush();%>
合理設置緩存對象數目,達到預期效果
我們通 過設置緩存條目的多少來控制內存緩存的大小(緩存條目數)。實際大多數網站的訪問遵循 80%-20% 原則, 即 80% 的客戶只訪問 20% 的產品。那麼一個有 500 個產品目錄和 5000 個產品的網站,只需要考慮在內存 中保留 20% 的產品目錄頁面((5 000+500)× 20%=1 100),其他產品目錄頁面可以使用磁盤緩存,此外還 需要考慮迷你購物車和命令緩存的數量。假定同時有 500 個用戶訪問,每個用戶需要兩個緩存條目(迷你購 物車和命令緩存),這樣還需要 1 000 個內存緩存條目。但是考慮到不是每個訪問客戶都會在購物車中添加 商品(所有客戶的空購物車應該使用同一個緩存頁面),所以實際緩存條目會少一些。這樣,總的內存緩存數 量,采用默認值 2 000 就應該足夠了。如果你設置的緩存條目很少,比如 1000,就會導致,某個頁面的緩存 條目剛被存到內存中,還沒有來得及被使用,由於條目總數的限制,需要存儲新的緩存條目,該頁面的緩存條 目就被逐出了,加上緩存機制本身的維護花銷,很有可能由於緩存前後開銷差異不大最終導致性能的提升離預 期的性能效果很大。緩存數目的設置,可以根據實際監視結果運行過程中進行調整。
緩存設 置引起的 OutOfMemory 異常
如果整體緩存條目所占用的內存已經接近 JVM 堆的最大值,那 麼預留給應用程序本身運轉的內存空間就非常有限,必然會導致產生大量的內存碎片,從而導致 OutOfMemory 的異常。因此設置緩存條目時,需要考慮 JVM 堆的大小來保證有足夠的內存來保證應用程序本身的運轉和緩 存的存儲。內存中緩存條目所占用空間的估計公式如下:
內存緩存條目大小 =o + c + ( k* ( dp + tm + 128) )
其中:o 為緩存條目本身的大小;c 為緩存標識 (cache ID) 的大小; k 為 4 (32 位平台 ) 或 8(64 位平台 );dp 為緩存模板的數量;tm 該緩存條目對應的相關性標識數量。
緩存條目本身的大小可以通過磁盤卸載的方法間接計算,具體步驟為:先將緩存大小設為固定值,開 啟 flushToDisk 選項,然後通過預加載的方法填滿此緩存空間並關閉應用服務器。flushToDisk 選項能確保 當關閉應用服務器時強制將緩存中的緩存項卸載到磁盤中,當應用服務器 啟動時,自動從磁盤中加載緩存項 。此時磁盤上將生成此緩存項對應的 dependence 及 objectCache 的目錄及文件,用 objectCache ( 在目錄 <WAS_HOME>/profiles/<instance_name>/temp/<node_name>/server1/_dynacache/servic es_cache_WCSearchAttributeDistributeMapCache) 的大小除以緩存的條目數就可以計算出單個緩存項的大小 。
對於大多數 Web 應用而言,影響緩存大小的因素是緩存頁面的大小。我們可以通過公式簡單計算出 緩存需要多少內存空間。一般推薦,在緩存之後,JVM 的堆大小還能有 40% 的剩余空間。
啟 動磁盤緩存
WebSphere 動態高速緩存服務的設計思想是針對內存緩存的(所有緩存條目都是 內存中的 Java 對象),磁盤緩存只是作為內存緩存溢出時的臨時緩沖區。大型企業應用都會采用內存緩存和 磁盤緩存並重的策略。
此外,我們還可以將 persist-to-disk 屬性設置為 false,來保證訪問頻率高 的緩存條目不會被轉移到磁盤上。以電子商務應用為例,動態高速緩存主要用來緩存產品目錄。其中,產品分 類頁(CategoryDisplay)的數量遠遠小於產品內容頁(ProductDisplay),但訪問頻率高得多。此時,就可 以將產品分類頁的緩存條目定義在內存中,這樣就可以避免產品內容頁的緩存被頻繁地調入內存及淘汰寫回磁 盤,造成不必要的磁盤 IO 操作。
緩存預加載(warm-up)
為了避免緩存的 冷啟動,一般建議在有條件的情況下,在批量更新產品的目錄結構和產品項時,利用一些自動化工具預加載最 常被訪問的頁面(URL),來減少應用服務器的壓力,保證系統的穩定性。該方法使用於各種層面的緩存,可 以推送到邊緣緩存或是緩存到浏覽器端。
DRS 緩存數據同步機制
生產環境 中的集群的不同節點的緩存數據同步管理是通過 DRS 來實現的。當集群中的節點個數相當大時,DRS 的任何 同步策略都會給系統帶來巨大的負載,就需要引入 WebSphere Extreme Scale 產品來解決(前面 WXS 部分有 介紹)。在中小規模的集群情況下,首先選擇最簡單的 Shared-Push 策略,尤其是針對開銷很大的緩存條目 。對像迷你購物車和不易變化的會話建議不進行數據同步。也就是說針對不同的緩存數據,我們可以根據數據 特點選擇不同的同步機制來達到性能最大化。
提高緩存覆蓋率以及緩存命中率
常見的電子商務的首頁面主要產品目錄結構,熱點產品(廣告推薦)以及熱點關鍵詞構成, 這些都屬於內容數據,但是其生命周期不同。目錄樹可能很長時間不變(超過一天);熱點搜索關鍵詞一天不 變,廣告的生命周期較短(業務人員可能不定期的添加 / 刪除廣告,而且對刷新實效性要求較高)。則頁面 上不同生命周期和特性的內容數據在頁面設計時就應該分成不同的 URL 請求,推薦采用片段緩存把頁面存在 服務器端,並使用規則機制進行失效刷新。如果部分內容需要邊緣緩存,推薦針對不同生命周期和特性的內容 數據分別進行規則失效刷新或是 timeout 刷新。
最重要的還是要使用一些有效的工具來監控頁面 (URL)的訪問頻率,被緩存對象的緩存命中率等數據,來幫助我們確定哪些數據是需要被緩存,哪些數據的 緩存命中率是需要提高的(最好能達到 90% 以上),對不同特性和生命周期的數據對象進行細分類,及時調 整緩存策略。
整體網絡架構的梳理
如果產品環境支持同時內網和外網用戶 的訪問,則需要根據業務情況對內外網設置獨立的 web 服務器,應用服務器,減少相互干擾,同時可以獲得 正確的緩存命中率等相關數據,以便采取正確的緩存策略和實現手段。
小結
動態高速緩存可以 極大的提高 WebSphere Commerce 的系統性能,但是需要從系統設計階段就進行動態高速緩存的設計,並陪以 相應的監控工具實時調整緩存策略來保證其最佳效果。
下一章節將通過介紹相應的關鍵技術參數,來 從另一層面在一定程度上改善產品性能。