接到配合架構部要求配合測試需求,對EhCache 進行測試,在此之前,未接觸過ehcache緩存,之前知道一些緩存,但是還真沒了解過內存緩存。於是百度,看書,查資料,先惡補一下ehcache的一些知識,然后整理如下:
EhCache 是一個分布式緩存,分布式緩存系統簡介:
Ehcache是一個純java的進程內緩存框架,也就是說Ehcache是一個進程中的緩存系統。
Ehcache的主要特性有:
- 快速、精干,簡單
- 多種緩存策略
- 緩存數據有兩級:內存和磁盤,因此無需擔心容量問題
- 緩存數據會在虛擬機重啟的過程中寫入磁盤
- 可以通過RMI、可插入API等方式進行分布式緩存
- 具有緩存和緩存管理器的偵聽借口
- 支持多緩存管理器實例,以及一個實例的多個緩存區域
- 提供Hibernate 的緩存實現。
注意:由於Ehcache是進程中的緩存系統,一旦將應用部署到集群環境中, 每一個節點維護各自的緩存數據,當某個節點對緩存數據進行更新, 這些更新的數據無法再其他節點中共享, 不僅會降低節點運行的效率, 而且會導致數據不同步的情況發生。 例如某個網站采用A、B 兩個節點作為集群部署, 當A節點的緩存更新后, 而B節點緩存尚未更新就可能出現用戶在瀏覽頁面的時候,一會是更新后的數據, 一會是尚未更新的數據。
所以就需要用到EhCache的集群解決方案
EhCache 從 1.7 版本開始,支持五種集群方案,分別是:
• Terracotta
• RMI
• JMS
• JGroups
• EhCache Server
其中的三種最為常用集群方式,分別是 RMI、JGroups 以及 EhCache Server 。本文主要介紹RMI的原理及測試方法,
RMI方式緩存集群/配置分布式緩存
RMI是java的一種遠程方法調用技術,是一種點對點的基於java對象的通訊方法。Ehcache從1.2版本開始支持RMI方式的緩存集群。在集群環境中Ehcache所有緩存對象的鍵和值都必須是可序列化的,也就是說必須實現java.io.Serializable 接口,這點在其他集群方式下也是需要遵守的。
下面是RMI集群模式的結構圖:
采用RMI集群模式時,集群中的每個節點都是對等關系,並不存在主節點或者從節點的概念,因此節點間必須有一個機制能夠互相認識對方,必須知道其他節點的信息,包括主機ip,端口號等。Ehcache提供兩種節點的發現方式:手工配置和自動發現。手工配置方式要求在每個節點中配置其他所有的節點連接信息,一旦集群中的節點發生變化是,需要對緩存進行重新配置。
由於RMI 是java中內置支持的技術, 因此使用RMI集群模式時, 無需引入其他的jar包,Ehcache 本身就帶有支持RMI集群的功能, 使用RMI集群模式需要在ehcache.xml配置文件中定義cacheManagerPeerProviderFactory 節點。
分布式同步緩存要讓這邊的cache知道對方的cache,叫做Peer Discovery(成員發現) EHCache實現成員發現的方式有兩種:
手動查找配置方法:
A、 在ehcache.xml中配置PeerDiscovery成員發現對象
Server1配置,配置本地hostName、port是400001,分別監聽192.168.8.32:400002的mobileCache和192.168.5.231:400003 的mobileCache。注意這里的mobileCache是緩存的名稱,分別對應着server2、server3的cache的配置。
<?xml version="1.0" encoding="gbk"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <diskStore path="java.io.tmpdir"/> <!-- 集群多台服務器中的緩存,這里是要同步一些服務器的緩存 server1 hostName:192.168.8.9 port:400001 cacheName:mobileCache server2 hostName:192.168.8.32 port:400002 cacheName:mobileCache server3 hostName:192.168.8.231 port:400003 cacheName:mobileCache 注意:每台要同步緩存的服務器的RMI通信socket端口都不一樣,在配置的時候注意設置 --> <!-- server1 的cacheManagerPeerProviderFactory配置 --> <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="hostName=localhost, port=400001, socketTimeoutMillis=2000, peerDiscovery=manual, rmiUrls=//192.168.8.32:400002/mobileCache|//192.168.5.231:400003/mobileCache" /> </ehcache>
同樣在你的另外2台服務器上增加配置
Server2,配置本地host,port為400002,分別同步192.168.8.9:400001的mobileCache和192.168.5.231:400003的mobileCache
<!-- server2 的cacheManagerPeerProviderFactory配置 --> <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="hostName=localhost, port=400002, socketTimeoutMillis=2000, peerDiscovery=manual, rmiUrls=//192.168.8.9:400001/mobileCache|//192.168.5.231:400003/mobileCache" />
Server3,配置本地host,port為400003,分別同步192.168.8.9:400001的mobileCache緩存和192.168.8.32:400002的mobileCache緩存
<!-- server3 的cacheManagerPeerProviderFactory配置 --> <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="hostName=localhost, port=400003, socketTimeoutMillis=2000, peerDiscovery=manual, rmiUrls=//192.168.8.9:400001/mobileCache|//192.168.8.32:400002/mobileCache" />
這樣就在三台不同的服務器上配置了手動查找cache的PeerProvider成員發現的配置了。 值得注意的是你在配置rmiUrls的時候要特別注意url不能重復出現,並且端口、地址都是對的。
如果指定,hostname將使用InetAddress.getLocalHost().getHostAddress()來得到。
注意:不要將localhost配置為本地地址127.0.0.1,因為它在網絡中不可見將會導致不能從遠程服務器接收信息從而不能復制。在同一台機器上有多個CacheManager的時候,你應該只用localhost來配置。

B、 下面配置緩存和緩存同步監聽,需要在每台服務器中的ehcache.xml文件中增加cache配置和cacheEventListenerFactory、cacheLoaderFactory的配置 <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="30" timeToLiveSeconds="30" overflowToDisk="false"/> <!-- 配置自定義緩存 maxElementsInMemory:緩存中允許創建的最大對象數 eternal:緩存中對象是否為永久的,如果是,超時設置將被忽略,對象從不過期。 timeToIdleSeconds:緩存數據空閑的最大時間,也就是說如果有一個緩存有多久沒有被訪問就會被銷毀,如果該值是 0 就意味着元素可以停頓無窮長的時間。 timeToLiveSeconds:緩存數據存活的時間,緩存對象最大的的存活時間,超過這個時間就會被銷毀,這只能在元素不是永久駐留時有效,如果該值是0就意味着元素可以停頓無窮長的時間。 overflowToDisk:內存不足時,是否啟用磁盤緩存。 memoryStoreEvictionPolicy:緩存滿了之后的淘汰算法。 每一個小時更新一次緩存(1小時過期) --> <cache name="mobileCache" maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="1800" timeToLiveSeconds="3600" memoryStoreEvictionPolicy="LFU"> <!-- RMI緩存分布同步查找 class使用net.sf.ehcache.distribution.RMICacheReplicatorFactory 這個工廠支持以下屬性: replicatePuts=true | false – 當一個新元素增加到緩存中的時候是否要復制到其他的peers。默認是true。 replicateUpdates=true | false – 當一個已經在緩存中存在的元素被覆蓋時是否要進行復制。默認是true。 replicateRemovals= true | false – 當元素移除的時候是否進行復制。默認是true。 replicateAsynchronously=true | false – 復制方式是異步的指定為true時,還是同步的,指定為false時。默認是true。 replicatePutsViaCopy=true | false – 當一個新增元素被拷貝到其他的cache中時是否進行復制指定為true時為復制,默認是true。 replicateUpdatesViaCopy=true | false – 當一個元素被拷貝到其他的cache中時是否進行復制指定為true時為復制,默認是true。 asynchronousReplicationIntervalMillis=1000 --> <!-- 監聽RMI同步緩存對象配置 注冊相應的的緩存監聽類,用於處理緩存事件,如put,remove,update,和expire --> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true "/> <!-- 用於在初始化緩存,以及自動設置 --> <bootstrapCacheLoaderFactory class="net.sf.ehcache.bootstrap.BootstrapCacheLoaderFactory"/> </cache>
下面給出server1的完整的ehcache.xml的配置:

<?xml version="1.0" encoding="gbk"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <diskStore path="java.io.tmpdir"/> <!-- 集群多台服務器中的緩存,這里是要同步一些服務器的緩存 server1 hostName:192.168.8.9 port:400001 cacheName:mobileCache server2 hostName:192.168.8.32 port:400002 cacheName:mobileCache server3 hostName:192.168.8.231 port:400003 cacheName:mobileCache 注意每台要同步緩存的服務器的RMI通信socket端口都不一樣,在配置的時候注意設置 --> <!-- server1 的cacheManagerPeerProviderFactory配置 --> <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="hostName=localhost, port=400001, socketTimeoutMillis=2000, peerDiscovery=manual, rmiUrls=//192.168.8.32:400002/mobileCache|//192.168.5.231:400003/mobileCache" /> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="30" timeToLiveSeconds="30" overflowToDisk="false"/> <!-- 配置自定義緩存 maxElementsInMemory:緩存中允許創建的最大對象數 eternal:緩存中對象是否為永久的,如果是,超時設置將被忽略,對象從不過期。 timeToIdleSeconds:緩存數據空閑的最大時間,也就是說如果有一個緩存有多久沒有被訪問就會被銷毀, 如果該值是 0 就意味着元素可以停頓無窮長的時間。 timeToLiveSeconds:緩存數據存活的時間,緩存對象最大的的存活時間,超過這個時間就會被銷毀, 這只能在元素不是永久駐留時有效,如果該值是0就意味着元素可以停頓無窮長的時間。 overflowToDisk:內存不足時,是否啟用磁盤緩存。 memoryStoreEvictionPolicy:緩存滿了之后的淘汰算法。 每一個小時更新一次緩存(1小時過期) --> <cache name="mobileCache" maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="1800" timeToLiveSeconds="3600" memoryStoreEvictionPolicy="LFU"> <!-- RMI緩存分布同步查找 class使用net.sf.ehcache.distribution.RMICacheReplicatorFactory 這個工廠支持以下屬性: replicatePuts=true | false – 當一個新元素增加到緩存中的時候是否要復制到其他的peers。默認是true。 replicateUpdates=true | false – 當一個已經在緩存中存在的元素被覆蓋時是否要進行復制。默認是true。 replicateRemovals= true | false – 當元素移除的時候是否進行復制。默認是true。 replicateAsynchronously=true | false – 復制方式是異步的指定為true時,還是同步的,指定為false時。默認是true。 replicatePutsViaCopy=true | false – 當一個新增元素被拷貝到其他的cache中時是否進行復制指定為true時為復制,默認是true。 replicateUpdatesViaCopy=true | false – 當一個元素被拷貝到其他的cache中時是否進行復制指定為true時為復制,默認是true。 asynchronousReplicationIntervalMillis=1000 --> <!-- 監聽RMI同步緩存對象配置 注冊相應的的緩存監聽類,用於處理緩存事件,如put,remove,update,和expire --> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true "/> <!-- 用於在初始化緩存,以及自動設置 --> <bootstrapCacheLoaderFactory class="net.sf.ehcache.bootstrap.BootstrapCacheLoaderFactory"/> </cache> </ehcache>
Ehcache測試方法
本次測試服務由架構部提供的兩個Ehcacahe服務端。一個做put操作,一個做同步操作。
jdk版本: jdk1.7.0_79
tomcat版本: Tomcat/7.0.57
修改echcahe.xml,參照上面的配置方式配置測試服務器ip地址
Put端xml代碼如下:
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="false" monitoring="autodetect" dynamicConfig="true"> <cacheManagerPeerListenerFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory" properties="hostName = 10.27.82.170, port = 50001, socketTimeoutMillis=10000" /> <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="peerDiscovery=manual, rmiUrls=//10.27.82.168:40001/clusterCache"/> <cache name="clusterCache" maxEntriesLocalHeap="999999999" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" overflowToDisk="false"> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" /> </cache> </ehcache>
同步端xml配置:
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="false" monitoring="autodetect" dynamicConfig="true" > <cacheManagerPeerListenerFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory" properties="hostName=172.19.136.197, port=40001, socketTimeoutMillis=2000" /> <cache name="clusterCache" maxEntriesLocalHeap="10" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" overflowToDisk="false"> <cacheEventListenerFactory class="com.suning.servlet.MyCacheEventListenerFactory"/> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" /> <bootstrapCacheLoaderFactory class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" properties="bootstrapAsynchronously=false, maximumChunkSizeBytes=5000000"/> </cache> </ehcache>
注:本次測試是采用手動配置同步信息方式,需要配置服務器端的ip+端口
修改成功后,將工程導出成war包,然后分別部署到兩台測試服務器上,部署成功后,打開tomcat后台日志。
瀏覽器訪問路徑:
put數據url:
同步訪問路徑:
http://ip:8080/EhCacheTest2/monitor.htm
測試結果如下:
Put端日志打印:
同步端后台日志打印如下:
開啟put端后,往put端中塞數據,然后往同步端進行同步,觀察耗時,差值,以及系統資源消耗,帶寬流程等,后續補充,基本大概的思路流程是這樣