天天看點

EhCache 分布式緩存/緩存叢集

開發環境:

System:Windows

JavaEE Server:tomcat5.0.2.8、tomcat6

JavaSDK: jdk6+

IDE:eclipse、MyEclipse 6.6

開發依賴庫:

JDK6、 JavaEE5、ehcache-core-2.5.2.jar

Email:[email protected]

Blog:javascript:void(0)

javascript:void(0)

http://hoojo.blogjava.net

前面2篇文章介紹到Ehcache 整合Spring 使用頁面、對象緩存 javascript:void(0) 

在Spring、Hibernate中使用Ehcache緩存 javascript:void(0)

一、緩存系統簡介

EhCache 是一個純 Java 的程序内緩存架構,具有快速、精幹等特點,是 Hibernate 中預設的 CacheProvider。

EhCache 應用架構圖,下圖是 EhCache 在應用程式中的位置:

EhCache 分布式緩存/緩存叢集
EhCache 的主要特性有:

1. 快速、精幹;

2. 簡單;

3. 多種緩存政策;

4. 緩存資料有兩級:記憶體和磁盤,是以無需擔心容量問題;

5. 緩存資料會在虛拟機重新開機的過程中寫入磁盤;

6. 可以通過 RMI、可插入 API 等方式進行分布式緩存;

7. 具有緩存和緩存管理器的偵聽接口;

8. 支援多緩存管理器執行個體,以及一個執行個體的多個緩存區域;

9. 提供 Hibernate 的緩存實作;

由于 EhCache 是程序中的緩存系統,一旦将應用部署在叢集環境中,每一個節點維護各自的緩存資料,當某個節點對緩存資料進行更新,這些更新的資料無法在其它節點中共享,這不僅會降低節點運作的效率,而且會導緻資料不同步的情況發生。例如某個網站采用 A、B 兩個節點作為叢集部署,當 A 節點的緩存更新後,而 B 節點緩存尚未更新就可能出現使用者在浏覽頁面的時候,一會是更新後的資料,一會是尚未更新的資料,盡管我們也可以通過 Session Sticky 技術來将使用者鎖定在某個節點上,但對于一些互動性比較強或者是非 Web 方式的系統來說,Session Sticky 顯然不太适合。

是以就需要用到 EhCache 的叢集解決方案。

從1.2版本開始,Ehcache可以使用分布式的緩存了。EhCache 從 1.7 版本開始,支援五種叢集方案,分别是:

• Terracotta

• RMI

• JMS

• JGroups

• EhCache Server

其中的三種最為常用叢集方式,分别是 RMI、JGroups 以及 EhCache Server 。本文主要介紹RMI的方式。

分布式這個特性是以plugin的方式實作的。Ehcache自帶了一些預設的分布式緩存插件實作,這些插件可以滿足大部分應用的需要。如果需要使用其他的插件那就需要自己開發了,開發者可以通過檢視distribution包裡的源代碼及JavaDoc來實作它。盡管不是必須的,在使用分布式緩存時了解一些ehcahce的設計思想也是有幫助的。這可以參看分布式緩存設計的頁面。以下的部分将展示如何讓分布式插件同ehcache一起工作。

下面列出的是一些分布式緩存中比較重要的方面:

• 你如何知道叢集環境中的其他緩存?

• 分布式傳送的消息是什麼形式?

• 什麼情況需要進行複制?增加(Puts),更新(Updates)或是失效(Expiries)?

• 采用什麼方式進行複制?同步還是異步方式?

為了安裝分布式緩存,你需要配置一個PeerProvider、一個CacheManagerPeerListener,

它們對于一個CacheManager來說是全局的。每個進行分布式操作的cache都要添加一個cacheEventListener來傳送消息。

二、叢集緩存概念及其配置

正确的元素類型

隻有可序列化的元素可以進行複制。一些操作,比如移除,隻需要元素的鍵值而不用整個元素;在這樣的操作中即使元素不是可序列化的但鍵值是可序列化的也可以被複制。

成員發現(Peer Discovery)

Ehcache進行叢集的時候有一個cache組的概念。每個cache都是其他cache的一個peer,沒有主cache的存在。剛才我們問了一個問題:你如何知道叢集環境中的其他緩存?這個問題可以命名為成員發現(Peer Discovery)。

Ehcache提供了兩種機制用來進行成員發現,就像一輛汽車:手動檔和自動檔。要使用一個内置的成員發現機制要在ehcache的配置檔案中指定cacheManagerPeerProviderFactory元素的class屬性為

net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory。

自動的成員發現

自動的發現方式用TCP廣播機制來确定和維持一個廣播組。它隻需要一個簡單的配置可以自動的在組中添加和移除成員。在叢集中也不需要什麼優化伺服器的知識,這是預設推薦的。

成員每秒向群組發送一個“心跳”。如果一個成員 5秒種都沒有發出信号它将被群組移除。如果一個新的成員發送了一個“心跳”它将被添加進群組。

任何一個用這個配置安裝了複制功能的cache都将被其他的成員發現并辨別為可用狀态。

要設定自動的成員發現,需要指定ehcache配置檔案中cacheManagerPeerProviderFactory元素的properties屬性,就像下面這樣:

peerDiscovery=automatic

multicastGroupAddress=multicast address | multicast host name

multicastGroupPort=port

timeToLive=0-255 (timeToLive屬性詳見常見問題部分的描述)

示例

假設你在叢集中有兩台伺服器。你希望同步sampleCache1和sampleCache2。每台獨立的伺服器都要有這樣的配置:

配置server1和server2

<cacheManagerPeerProviderFactory      
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"      
properties="peerDiscovery=automatic, multicastGroupAddress=230.0.0.1,      
multicastGroupPort=4446, timeToLive=32"/>      

手動進行成員發現

進行手動成員配置要知道每個監聽器的IP位址和端口。成員不能在運作時動态地添加和移除。在技術上很難使用廣播的情況下就可以手動成員發現,例如在叢集的伺服器之間有一個不能傳送廣播封包的路由器。你也可以用手動成員發現進行單向的資料複制,隻讓server2知道server1,而server1不知道server2。

配置手動成員發現,需要指定ehcache配置檔案中cacheManagerPeerProviderFactory的properties屬性,像下面這樣:

peerDiscovery=manual rmiUrls=//server:port/cacheName, //server:port/cacheName ...

rmiUrls配置的是伺服器cache peers的清單。注意不要重複配置。

假設你在叢集中有兩台伺服器。你要同步sampleCache1和sampleCache2。下面是每個伺服器需要的配置:

配置server1

<cacheManagerPeerProviderFactory      
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"      
properties="peerDiscovery=manual,      
rmiUrls=//server2:40001/sampleCache11|//server2:40001/sampleCache12"/>      
配置server2
<cacheManagerPeerProviderFactory      
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"      
properties="peerDiscovery=manual,      
rmiUrls=//server1:40001/sampleCache11|//server1:40001/sampleCache12"/>      

配置CacheManagerPeerListener

每個CacheManagerPeerListener監聽從成員們發向目前CacheManager的消息。配置CacheManagerPeerListener需要指定一個CacheManagerPeerListenerFactory,它以插件的機制實作,用來建立CacheManagerPeerListener。

cacheManagerPeerListenerFactory的屬性有:

class – 一個完整的工廠類名。

properties – 隻對這個工廠有意義的屬性,使用逗号分隔。

Ehcache有一個内置的基于RMI的分布系統。它的監聽器是RMICacheManagerPeerListener,這個監聽器可以用

RMICacheManagerPeerListenerFactory來配置。

<cacheManagerPeerListenerFactory      
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"      
properties="hostName=localhost, port=40001,      
socketTimeoutMillis=2000"/>      

有效的屬性是:

hostname (可選) – 運作監聽器的伺服器名稱。标明了做為叢集群組的成員的位址,同時也是你想要控制的從叢集中接收消息的接口。

在CacheManager初始化的時候會檢查hostname是否可用。

如果hostName不可用,CacheManager将拒絕啟動并抛出一個連接配接被拒絕的異常。

如果指定,hostname将使用InetAddress.getLocalHost().getHostAddress()來得到。

警告:不要将localhost配置為本地位址127.0.0.1,因為它在網絡中不可見将會導緻不能從遠端伺服器接收資訊進而不能複制。在同一台機器上有多個CacheManager的時候,你應該隻用localhost來配置。

port – 監聽器監聽的端口。

socketTimeoutMillis (可選) – Socket逾時的時間。預設是2000ms。當你socket同步緩存請求位址比較遠,不是本地區域網路。你可能需要把這個時間配置大些,不然很可能延時導緻同步緩存失敗。

配置CacheReplicators

每個要進行同步的cache都需要設定一個用來向CacheManagerr的成員複制消息的緩存事件監聽器。這個工作要通過為每個cache的配置增加一個cacheEventListenerFactory元素來完成。
<!-- Sample cache named sampleCache2. -->      
<cache name="sampleCache2"      
maxElementsInMemory="10"      
eternal="false"      
timeToIdleSeconds="100"      
timeToLiveSeconds="100"      
overflowToDisk="false">      
<cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"      
properties="replicateAsynchronously=true,replicatePuts=true, replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true "/>      
</cache>      

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。

你可以使用ehcache的預設行為進而減少配置的工作量,預設的行為是以異步的方式複制每件事;你可以像下面的例子一樣減少RMICacheReplicatorFactory的屬性配置:

<!-- Sample cache named sampleCache4. All missing RMICacheReplicatorFactory properties default to true -->      
<cache name="sampleCache4"      
maxElementsInMemory="10"      
eternal="true"      
overflowToDisk="false"      
memoryStoreEvictionPolicy="LFU">      
<cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"/>      
</cache>      

常見的問題

Windows上的Tomcat

有一個Tomcat或者是JDK的bug,在tomcat啟動時如果tomcat的安裝路徑中有空格的話,在啟動時RMI監聽器會失敗。參見http://archives.java.sun.com/cgi-bin/wa?A2=ind0205&L=rmi-users&P=797和http://www.ontotext.com/kim/doc/sys-doc/faq-howto-bugs/known-bugs.html。

由于在Windows上安裝Tomcat預設是裝在“Program Files”檔案夾裡的,是以這個問題經常發生。

廣播阻斷

自動的peer discovery與廣播息息相關。廣播可能被路由阻攔,像Xen和VMWare這種虛拟化的技術也可以阻攔廣播。如果這些都打開了,你可能還在要将你的網卡的相關配置打開。一個簡單的辦法可以告訴廣播是否有效,

那就是使用ehcache remote debugger來看“心跳”是否可用。

廣播傳播的不夠遠或是傳得太遠

你可以通過設定badly misnamed time to live來控制廣播傳播的距離。用廣播IP協定時,timeToLive的值指的是資料包可以傳遞的域或是範圍。約定如下:

0是限制在同一個伺服器

1是限制在同一個子網

32是限制在同一個網站

64是限制在同一個region

128是限制在同一個大洲

255是不限制

譯者按:上面這些資料翻譯的不夠準确,請讀者自行尋找原文了解吧。

在Java實作中預設值是1,也就是在同一個子網中傳播。改變timeToLive屬性可以限制或是擴充傳播的範圍。

三、 RMI方式緩存叢集/配置分布式緩存

RMI 是 Java 的一種遠端方法調用技術,是一種點對點的基于 Java 對象的通訊方式。EhCache 從 1.2 版本開始就支援 RMI 方式的緩存叢集。在叢集環境中 EhCache 所有緩存對象的鍵和值都必須是可序列化的,也就是必須實作 java.io.Serializable 接口,這點在其它叢集方式下也是需要遵守的。

     下圖是 RMI 叢集模式的結構圖:

EhCache 分布式緩存/緩存叢集

采用 RMI 叢集模式時,叢集中的每個節點都是對等關系,并不存在主節點或者從節點的概念,是以節點間必須有一個機制能夠互相認識對方,必須知道其它節點的資訊,包括主機位址、端口号等。EhCache 提供兩種節點的發現方式:手工配置和自動發現。手工配置方式要求在每個節點中配置其它所有節點的連接配接資訊,一旦叢集中的節點發生變化時,需要對緩存進行重新配置。

由于 RMI 是 Java 中内置支援的技術,是以使用 RMI 叢集模式時,無需引入其它的 Jar 包,EhCache 本身就帶有支援 RMI 叢集的功能。使用 RMI 叢集模式需要在 ehcache.xml 配置檔案中定義 cacheManagerPeerProviderFactory 節點。

      分布式同步緩存要讓這邊的cache知道對方的cache,叫做Peer Discovery(成員發現) EHCache實作成員發現的方式有兩種:

1、手動查找

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>      

以上注意cacheManagerPeerProviderFactory元素出現的位置在diskStore下

同樣在你的另外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不能重複出現,并且端口、位址都是對的。

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>      
C、 這樣就完成了3台伺服器的配置,下面給出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>      

2、自動發現

自動發現配置和手動查找的方式有一點不同,其他的地方都基本是一樣的。同樣在ehcache.xml中增加配置,配置如下
<!--      
搜尋某個網段上的緩存      
timeToLive      
0是限制在同一個伺服器      
1是限制在同一個子網      
32是限制在同一個網站      
64是限制在同一個region      
128是限制在同一個大洲      
255是不限制      
-->      
<cacheManagerPeerProviderFactory      
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"      
properties="peerDiscovery=automatic, multicastGroupAddress=192.168.0.1,      
multicastGroupPort=400004, timeToLive=32"      
/>      
其他的配置和手動查找方式的配置是一樣的,這裡就不再贅述了。關于ehcache的其他緩存配置方式這裡将不再介紹,大家可以自己去研究。可以參考:

官方文檔:http://www.ehcache.org/documentation/user-guide/cache-topologies#using-a-cache-server

ibm developerworks文檔:http://www.ibm.com/developerworks/cn/java/j-lo-ehcache/index.html

JGroups 叢集模式

EhCache 從 1.5. 版本開始增加了 JGroups 的分布式叢集模式。與 RMI 方式相比較, JGroups 提供了一個非常靈活的協定棧、可靠的單點傳播和多點傳播消息傳輸,主要的缺點是配置複雜以及一些協定棧對第三方包的依賴。

JGroups 也提供了基于 TCP 的單點傳播 ( Unicast ) 和基于 UDP 的多點傳播 ( Multicast ) ,對應 RMI 的手工配置和自動發現。使用單點傳播方式需要指定其它節點的主機位址和端口,下面是兩個節點,并使用了單點傳播方式的配置:

<cacheManagerPeerProviderFactory
    class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory"
    properties="connect=TCP(start_port=7800):
        TCPPING(initial_hosts=host1[7800],host2[7800];port_range=10;timeout=3000;
        num_initial_members=3;up_thread=true;down_thread=true):
        VERIFY_SUSPECT(timeout=1500;down_thread=false;up_thread=false):
        pbcast.NAKACK(down_thread=true;up_thread=true;gc_lag=100;
	retransmit_timeout=3000):
        pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;
        print_local_addr=false;down_thread=true;up_thread=true)"
propertySeparator="::" />      

使用多點傳播方式配置如下:

<cacheManagerPeerProviderFactory
    class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory"
    properties="connect=UDP(mcast_addr=231.12.21.132;mcast_port=45566;):PING:
    MERGE2:FD_SOCK:VERIFY_SUSPECT:pbcast.NAKACK:UNICAST:pbcast.STABLE:FRAG:pbcast.GMS"
    propertySeparator="::"
/>      

從上面的配置來看,JGroups 的配置要比 RMI 複雜得多,但也提供更多的微調參數,有助于提升緩存資料複制的性能。詳細的 JGroups 配置參數的具體意義可參考 JGroups 的配置手冊。

JGroups 方式對應緩存節點的配置資訊如下:

<cache name="sampleCache2"
    maxElementsInMemory="10"
    eternal="false"
    timeToIdleSeconds="100"
    timeToLiveSeconds="100"
    overflowToDisk="false">
    <cacheEventListenerFactory
        class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory"
        properties="replicateAsynchronously=true, replicatePuts=true,
        replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true" />
</cache>      

使用多點傳播方式的注意事項

使用 JGroups 需要引入 JGroups 的 Jar 包以及 EhCache 對 JGroups 的封裝包 ehcache-jgroupsreplication-xxx.jar 。

在一些啟用了 IPv6 的電腦中,經常啟動的時候報如下錯誤資訊:

java.lang.RuntimeException: the type of the stack (IPv6) and the user supplied addresses (IPv4) don't match: /231.12.21.132.

解決的辦法是增加 JVM 參數:-Djava.net.preferIPv4Stack=true。如果是 Tomcat 伺服器,可在 catalina.bat 或者 catalina.sh 中增加如下環境變量即可:

SET CATALINA_OPTS=-Djava.net.preferIPv4Stack=true      

經過實際測試發現,叢集方式下的緩存資料都可以在 1 秒鐘之内完成到其節點的複制。

EhCache Server

與前面介紹的兩種叢集方案不同的是, EhCache Server 是一個獨立的緩存伺服器,其内部使用 EhCache 做為緩存系統,可利用前面提到的兩種方式進行内部叢集。對外提供程式設計語言無關的基于 HTTP 的 RESTful 或者是 SOAP 的資料緩存操作接口。

下面是 EhCache Server 提供的對緩存資料進行操作的方法:

OPTIONS /{cache}}

擷取某個緩存的可用操作的資訊。

HEAD /{cache}/{element}

擷取緩存中某個元素的 HTTP 頭資訊,例如:

curl --head  http://localhost:8080/ehcache/rest/sampleCache2/2      

EhCache Server 傳回的資訊如下:

HTTP/1.1 200 OK 
X-Powered-By: Servlet/2.5 
Server: GlassFish/v3 
Last-Modified: Sun, 27 Jul 2008 08:08:49 GMT 
ETag: "1217146129490"
Content-Type: text/plain; charset=iso-8859-1 
Content-Length: 157 
Date: Sun, 27 Jul 2008 08:17:09 GMT      

GET /{cache}/{element}

讀取緩存中某個資料的值。

PUT /{cache}/{element}

寫緩存。

由于這些操作都是基于 HTTP 協定的,是以你可以在任何一種程式設計語言中使用它,例如 Perl、PHP 和 Ruby 等等。

下圖是 EhCache Server 在應用中的架構:

圖 3. EhCache Server 應用架構圖
EhCache 分布式緩存/緩存叢集

EhCache Server 同時也提供強大的安全機制、監控功能。在資料存儲方面,最大的 Ehcache 單執行個體在記憶體中可以緩存 20GB。最大的磁盤可以緩存 100GB。通過将節點整合在一起,這樣緩存資料就可以跨越節點,以此獲得更大的容量。将緩存 20GB 的 50 個節點整合在一起就是 1TB 了。

總結

以上我們介紹了三種 EhCache 的叢集方案,除了第三種跨程式設計語言的方案外,EhCache 的叢集對應用程式的代碼編寫都是透明的,程式人員無需考慮緩存資料是如何複制到其它節點上。既保持了代碼的輕量級,同時又支援龐大的資料叢集。EhCache 可謂是深入人心。

2009 年年中,Terracotta 宣布收購 EhCache 産品。Terracotta 公司的産品 Terracotta 是一個 JVM 級的開源群集架構,提供 HTTP Session 複制、分布式緩存、POJO 群集、跨越叢集的 JVM 來實作分布式應用程式協調。最近 EhCache 主要的改進都集中在跟 Terracotta 架構的內建上,這是一個真正意義上的企業級緩存解決方案。

=======================================================================

ehcache提供三種網絡連接配接政策來實作叢集,rmi,jgroup還有jms。同時ehcache可以可以實作多點傳播的方式實作叢集,也可以手動指定叢集主機序列實作叢集

Ehcache支援的分布式緩存支援有三種RMI,JGroups,JMS,這裡介紹下MRI和JGrpups兩種方式,Ehcache使用版本為1.5.0,關于ehcache的其他資訊請參http://ehcache.sourceforge.net/EhcacheUserGuide.html,

關于jgroups的資訊請參考http://www.jgroups.org/manual/html_single/index.html。

環境為兩台機器 server1 ip:192.168.2.154,server2 ip:192.168.2.23

1. RMI方式:

rmi的方式配置要點(下面均是server1上的配置,server2上的隻需要把ip兌換即可)

a. 配置PeerProvider:

<cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="peerDiscovery=manual,rmiUrls=//192.168.2.23:40001/userCache|//192.168.2.23:40001/resourceCache" />      

配置中通過手動方式同步sever2中的userCache和resourceCache。

b. 配置CacheManagerPeerListener:

<cacheManagerPeerListenerFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory" properties="hostName=192.168.2.154, port=40001,socketTimeoutMillis=2000" />      

配置中server1監聽本機40001端口。

c. 在每一個cache中添加cacheEventListener,例子如下:

<cache name="userCache" maxElementsInMemory="10000" eternal="true" overflowToDisk="true" timeToIdleSeconds="0" timeToLiveSeconds="0"diskPersistent="false" diskExpiryThreadIntervalSeconds="120">
<cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true,replicateUpdatesViaCopy= false, replicateRemovals= true " />
</cache>      

屬性解釋:

必須屬性:

        name:設定緩存的名稱,用于标志緩存,惟一

        maxElementsInMemory:在記憶體中最大的對象數量

        maxElementsOnDisk:在DiskStore中的最大對象數量,如為0,則沒有限制

        eternal:設定元素是否永久的,如果為永久,則timeout忽略

        overflowToDisk:是否當memory中的數量達到限制後,儲存到Disk

可選的屬性:

        timeToIdleSeconds:設定元素過期前的空閑時間

        timeToLiveSeconds:設定元素過期前的活動時間

        diskPersistent:是否disk store在虛拟機啟動時持久化。預設為false

   diskExpiryThreadIntervalSeconds:運作disk終結線程的時間,預設為120秒

        memoryStoreEvictionPolicy:政策關于Eviction

緩存子元素:

    cacheEventListenerFactory:注冊相應的的緩存監聽類,用于處理緩存事件,如put,remove,update,和expire

    bootstrapCacheLoaderFactory:指定相應的BootstrapCacheLoader,用于在初始化緩存,以及自動設定。

參考另外一篇學習筆記http://wozailongyou.javaeye.com/blog/230252,也有叢集的說明

2. JGroups方式:

ehcache 1.5.0之後版本支援的一種方式,配置起來比較簡單,要點:

a. 配置PeerProvider,使用tcp的方式,例子如下:

<cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory" properties="connect=TCP(start_port=7800):
TCPPING(initial_hosts=192.168.2.154[7800],192.168.2.23[7800];port_range=10;timeout=3000;
num_initial_members=3;up_thread=true;down_thread=true):
VERIFY_SUSPECT(timeout=1500;down_thread=false;up_thread=false):
pbcast.NAKACK(down_thread=true;up_thread=true;gc_lag=100;retransmit_timeout=3000):
pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;
print_local_addr=false;down_thread=true;up_thread=true)"
propertySeparator="::" />      

b.為每個cache添加cacheEventListener:

<cache name="userCache" maxElementsInMemory="10000" eternal="true" overflowToDisk="true" timeToIdleSeconds="0" timeToLiveSeconds="0"
diskPersistent="false" diskExpiryThreadIntervalSeconds="120">
<cacheEventListenerFactory class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory" properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true"/>
</cache>      
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.sf.net/ehcache.xsd">
<diskStore path="java.io.tmpdir" />
<cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory"
properties="connect=TCP(start_port=7800):
TCPPING(initial_hosts=192.168.2.154[7800],192.168.2.23[7800];port_range=10;timeout=3000;
num_initial_members=3;up_thread=true;down_thread=true):
VERIFY_SUSPECT(timeout=1500;down_thread=false;up_thread=false):
pbcast.NAKACK(down_thread=true;up_thread=true;gc_lag=100;retransmit_timeout=3000):
pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;
print_local_addr=false;down_thread=true;up_thread=true)"
propertySeparator="::" />
<defaultCache maxElementsInMemory="10000" eternal="true"
overflowToDisk="true" timeToIdleSeconds="0" timeToLiveSeconds="0"
diskPersistent="false" diskExpiryThreadIntervalSeconds="120">
<cacheEventListenerFactory class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory"
properties="replicateAsynchronously=true, replicatePuts=true,
replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true"/>
</defaultCache>
<cache name="velcroCache" maxElementsInMemory="10000" eternal="true"
overflowToDisk="true" timeToIdleSeconds="0" timeToLiveSeconds="0"
diskPersistent="false" diskExpiryThreadIntervalSeconds="120">
<cacheEventListenerFactory class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory"
properties="replicateAsynchronously=true, replicatePuts=true,
replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true"/>
</cache>
<cache name="userCache" maxElementsInMemory="10000" eternal="true"
overflowToDisk="true" timeToIdleSeconds="0" timeToLiveSeconds="0"
diskPersistent="false" diskExpiryThreadIntervalSeconds="120">
<cacheEventListenerFactory class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory"
properties="replicateAsynchronously=true, replicatePuts=true,
replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true"/>
</cache>
<cache name="resourceCache" maxElementsInMemory="10000"
eternal="true" overflowToDisk="true" timeToIdleSeconds="0"
timeToLiveSeconds="0" diskPersistent="false"
diskExpiryThreadIntervalSeconds="120">
<cacheEventListenerFactory class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory"
properties="replicateAsynchronously=true, replicatePuts=true,
replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true"/>
</cache>
</ehcache>