天天看點

EhCache RMI 分布式緩存/緩存叢集

EhCache 系統簡介

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

EhCache 的主要特性有:

  1. 快速、精幹
  2. 簡單;
  3. 多種緩存政策;
  4. 緩存資料有兩級:記憶體和磁盤,是以無需擔心容量問題;
  5. 緩存資料會在虛拟機重新開機的過程中寫入磁盤;
  6. 可以通過 RMI、可插入 API 等方式進行分布式緩存;
  7. 具有緩存和緩存管理器的偵聽接口;
  8. 支援多緩存管理器執行個體,以及一個執行個體的多個緩存區域;
  9. 提供 Hibernate 的緩存實作;

EhCache叢集解決的問題: 

  由 于 EhCache 是程序中的緩存系統,一旦将應用部署在叢集環境中,每一個節點維護各自的緩存資料,當某個節點對緩存資料進行更新,這些更新的資料無法在其它節點中共享, 這不僅會降低節點運作的效率,而且會導緻資料不同步的情況發生。例如某個網站采用 A、B 兩個節點作為叢集部署,當 A 節點的緩存更新後,而 B 節點緩存尚未更新就可能出現使用者在浏覽頁面的時候,一會是更新後的資料,一會是尚未更新的資料。 

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

   

EhCache叢集方案:

• Terracotta 

• RMI 

• JMS : 依賴 ehcache-jmsreplication.jar 

• JGroups : 依賴ehcache-jgroupsreplication.jar 

• EhCache Server

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

EhCache叢集疑問

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

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

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

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

EhCache叢集基本概念

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

2、成員發現(Peer Discovery):Ehcache進行叢集的時候有一個cache組的概念。每個cache都是其他cache的一個peer,沒有主cache的存在。成員發現(Peer Discovery)正是用來解決 “你如何知道叢集環境中的其他緩存?” 這個問題的。Ehcache提供了兩種機制用來進行成員發現,即:自動成員發現和手動成員發現。要使用一個内置的成員發現機制要在ehcache的配置檔案中指定cacheManagerPeerProviderFactory元素的class屬性為 

net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory。

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

<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="peerDiscovery=automatic, multicastGroupAddress=230.0.0.1,
multicastGroupPort=4446, timeToLive=32"/>
<!-- timeToLive
    0是限制在同一個伺服器
    1是限制在同一個子網
    32是限制在同一個網站
    64是限制在同一個region
    128是限制在同一個大洲
    255是不限制-->      

 手動的發現方式需要知道每個監聽器的IP位址和端口。叢集成員(也就是伺服器)不能在運作時動态地添加和移除。ehcache.xml配置示例代碼如下:

<!-- 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。 

  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()來得到。 

port – 監聽器監聽的端口。 

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

CacheReplicators 

  每個要進行同步的cache都需要設定一個用來向CacheManager的成員複制消息的緩存事件監聽器。這個工作要通過為每個cache的配置增加一個cacheEventListenerFactory元素來完成。 

  代碼:

<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>      

  cacheEventListenerFactory 支援以下屬性

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。

Cache屬性說明:

<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">  
        </cacheEventListenerFactory>
        <bootstrapCacheLoaderFactory
          class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"
               properties="bootstrapAsynchronously=true"> 
        </bootstrapCacheLoaderFactory>
    </cache>      

必須屬性: 

  name:緩存名稱。 

  maxElementsInMemory:緩存最大個數。 

  eternal:對象是否永久有效,一但設定了,timeout将不起作用。 

  overflowToDisk:當記憶體中對象數量達 

  maxElementsInMemory時,Ehcache将會對象寫到磁盤中。 

   diskSpoolBufferSizeMB:這個參數設定DiskStore(磁盤緩存)的緩存區大小。預設是30MB。每個Cache都應該有自己的一個緩沖區。 

  maxElementsOnDisk:硬碟最大緩存個數。 

可選的屬性: 

  timeToIdleSeconds:設定對象在失效前的允許閑置時間(機關:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,預設值是0,也就是可閑置時間無窮大。 

  timeToLiveSeconds:設定對象在失效前允許存活時間(機關:秒)。最大時間介于建立時間和失效時間之間。僅當eternal=false對象不是永久有效時使用,預設是0.,也就是對象存活時間無窮大。 

  diskPersistent:是否disk store在虛拟機啟動時持久化. The default value is false. 

  memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache将會根據指定的政策去清理記憶體。預設政策是LRU(最近最少使用)。你可以設定為FIFO(先進先出)或是LFU(較少使用)。 

  diskExpiryThreadIntervalSeconds:磁盤失效線程運作時間間隔,預設是120秒。 

  clearOnFlush:記憶體數量最大時是否清除。 

緩存子元素: 

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

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

EhCache RMI 手動方式配置緩存

Tomcat1: 127.0.0.1:8080, Cache Server1: 127.0.0.1:40001 

Tomcat2: 127.0.0.1:8088, Cache Server2: 127.0.0.1:40002

用到的架包: 

ehcache-core-2.6.8.jar 

log4j-1.2.17.jar 

servlet-api.jar (示例中使用了@WebServlet,請確定servler-api的架包版本大于3.0) 

slf4j-api-1.6.1.jar 

slf4j-log4j12-1.6.1.jar

建立Web工程TestEhcache, 工程目錄如下: 

CacheManagerFactory.java

private CacheManager manager;
    private static CacheManagerFactory factory = new CacheManagerFactory();
    private final static String EHCACHEFILE = "/ehcache.xml";

    private CacheManagerFactory() {
    }

    public static CacheManagerFactory getInstance() {
        return factory;
    }

    public CacheManager getCacheManager() {
        if (manager == null) {
            InputStream is = this.getClass().getResourceAsStream(EHCACHEFILE);
            manager = CacheManager.create(is);
        }
        return manager;
    }

    public Cache getCache(String cache) {
        return getCacheManager().getCache(cache);
    }

    public void setCache(Cache cache) {
        getCacheManager().addCache(cache);
    }

    public void setCache(String cache) {
        getCacheManager().addCache(cache);
    }

    public Element getElement(String cacheName, String key) {
        if (getCache(cacheName) == null)
            setCache(cacheName);
        return getCache(cacheName).get(key);
    }

    public void setElement(String cache, Element element) {
        if (getCache(cache) == null)
            setCache(cache);
        getCache(cache).put(element);
    }

    public Boolean continaElementKey(String cacheName, String key) {
        if (getCache(cacheName) == null)
            setCache(cacheName);
        return getCache(cacheName).isKeyInCache(key);
    }      

TesAction.java

@WebServlet("/test")
public class TesAction extends HttpServlet {

    private static final long serialVersionUID = 1L;

    CacheManagerFactory cmf = CacheManagerFactory.getInstance();

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        doPost(request,response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String res = "";
        String key = request.getParameter("key");

        Element element = cmf.getElement("userCache", "map");
        if(element == null){
            Map<String, String> map = new HashMap<String, String>();
            map.put(key, key);
            cmf.setElement("userCache", new Element("map", map));
        }else{
            Map<String, String> map = (Map<String, String>) element.getValue();
            res = map.get(key);
            if(res == null){
                map.put(key, key);
                // 多次測試發現,存在同名Element是,重複put的是無法複制的,是以當遇到兩個節點同步不上的時候,先remove後put。 
                cmf.getCache("userCache").remove("map");
                cmf.setElement("userCache", new Element("map", map));
                res = "0;null";
            }
        }

        response.setContentType("text/html;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        PrintWriter out = response.getWriter();
        out.write(res);
        out.close();
    }

    @Override
    public void init() throws ServletException {
        super.init();
    }

}      

ehcache.xml:

<?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.RMICacheManagerPeerProviderFactory"
        properties="peerDiscovery=manual,rmiUrls=//127.0.0.1:40002/userCache|//127.0.0.1:40002/resourceCache">
    </cacheManagerPeerProviderFactory>

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

    <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="30" timeToLiveSeconds="30" overflowToDisk="false"/>

    <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">    
        </cacheEventListenerFactory>
        <bootstrapCacheLoaderFactory
            class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"
            properties="bootstrapAsynchronously=true"> 
        </bootstrapCacheLoaderFactory>
    </cache>

    <cache 
        name="resourceCache" 
        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">    
        </cacheEventListenerFactory>
        <bootstrapCacheLoaderFactory
            class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"
            properties="bootstrapAsynchronously=true"> 
        </bootstrapCacheLoaderFactory>
    </cache>

</ehcache>      

複制TestEhcache到 TestEhcache1, 修改TestEhcache1下的ehcache.xml ,隻需要修改cacheManagerPeerProviderFactory和cacheManagerPeerListenerFactory修改為如下代碼,其他不變

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

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

為了差別,将TestEhcache1的TesAction.doPost()方法修改為如下代碼,隻get的緩存不put緩存,用于觀察TestEhcache1是否能同步TestEhcache的資料:

String res = "0;null";
        Element element = cmf.getElement("userCache", "map");
        if(element != null){
            Map<String, String> map = (Map<String, String>) element.getValue();
            res = map.get(request.getParameter("key"));
            if(res == null){
                res = "0;null";
            }
        }
        response.setContentType("text/html;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        PrintWriter out = response.getWriter();
        out.write(res);
        out.close();      

将TestEhcache部署到tomcat1, TestEhcache1部署到Tomcat2。依次執行如下代碼: 

localhost:8080/TestEhcache/test?key=125 

localhost:8080/TestEhcache/test?key=125 

localhost:8088/TestEhcache1/test?key=125 

如果輸出内容分别如下,說明叢集ok, 兩節點資料同步沒問題, 否者請仔細檢查配置檔案和TestAction代碼: 

0;null 

125 

125

EhCache RMI 自動方式配置緩存 

将ehcache.xml的cacheManagerPeerProviderFactory代碼改為:

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

EhCache Jgroups 方式配置緩存

在TestEhcache和TestEhcache1工程中添加ehcache-jgroupsreplication-1.7.jar和jgroups-3.6.9.Final.jar。 使用該方式比RMI方式配置簡單。

TestEhcache 的 ehcache.xml

<?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(bind_port=4001):
            TCPPING(initial_hosts=192.168.8.150[4001],192.168.8.150[4002];port_range=10;timeout=3000;num_initial_members=3):
            MERGE2(min_interval=3000;max_interval=5000):  
            FD_ALL(interval=5000;timeout=20000):  
            FD(timeout=5000;max_tries=48;):  
            VERIFY_SUSPECT(timeout=1500):  
            pbcast.NAKACK(retransmit_timeout=100,200,300,600,1200,2400,4800;discard_delivered_msgs=true):  
            pbcast.STABLE(stability_delay=1000;desired_avg_gossip=20000;max_bytes=0):  
            pbcast.GMS(print_local_addr=true;join_timeout=5000)"
        propertySeparator="::">
    </cacheManagerPeerProviderFactory>

    <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"/>
        <bootstrapCacheLoaderFactory
            class="net.sf.ehcache.distribution.jgroups.JGroupsBootstrapCacheLoaderFactory"
            properties="bootstrapAsynchronously=true"> 
        </bootstrapCacheLoaderFactory>
    </defaultCache>
</ehcache>      

複制TestEhcache的ehcache.xml到TestEhcache1,并且修改下列代碼:

connect=TCP(bind_port=4002)      

推薦文章:http://raychase.iteye.com/blog/1545906 

http://blog.csdn.net/tang06211015/article/details/52281551

http://www.ehcache.org/apidocs/2.10.3/index.html

來源:http://blog.csdn.net/xlxxcc/article/details/52350264