本文參考網(wǎng)址:
《Spring+EhCache緩存實(shí)例》
《集群環(huán)境中使用 EhCache 緩存系統(tǒng)》
《EhCache 系統(tǒng)簡(jiǎn)介》
《ehcache 集群使用 rmi方式》
《ehcache緩存配置說明》
一. Ehcache 的簡(jiǎn)介
EhCache 是一個(gè)純 Java 的進(jìn)程內(nèi)緩存框架川蒙,具有快速勉痴、精干等特點(diǎn)蚕脏,是 Hibernate 中默認(rèn)的 CacheProvider。Ehcache 是一種廣泛使用的開源 Java 分布式緩存。主要面向通用緩存例证,Java EE 和輕量級(jí)容器滑废。它具有內(nèi)存和磁盤存儲(chǔ),緩存加載器斜友,緩存擴(kuò)展,緩存異常處理程序垃它,一個(gè) gzip 緩存 servlet 過濾器鲜屏,支持 REST 和 SOAP api 等特點(diǎn)烹看。
優(yōu)點(diǎn):
- 快速
- 簡(jiǎn)單
- 多種緩存策略
- 緩存數(shù)據(jù)有兩級(jí):內(nèi)存和磁盤,因此無需擔(dān)心容量問題
- 緩存數(shù)據(jù)會(huì)在虛擬機(jī)重啟的過程中寫入磁盤
- 可以通過 RMI洛史、可插入 API 等方式進(jìn)行分布式緩存
- 具有緩存和緩存管理器的偵聽接口
- 支持多緩存管理器實(shí)例惯殊,以及一個(gè)實(shí)例的多個(gè)緩存區(qū)域
- 提供 Hibernate 的緩存實(shí)現(xiàn)
缺點(diǎn):
- 使用磁盤 Cache 的時(shí)候非常占用磁盤空間:這是因?yàn)?DiskCache 的算法簡(jiǎn)單,該算法簡(jiǎn)單也導(dǎo)致 Cache 的效率非常高也殖。它只是對(duì)元素直接追加存儲(chǔ)土思。因此搜索元素的時(shí)候非常的快。如果使用 DiskCache 的忆嗜,在很頻繁的應(yīng)用中己儒,很快磁盤會(huì)滿。
- 不能保證數(shù)據(jù)的安全:當(dāng)突然 kill 掉 Java 的時(shí)候霎褐,可能會(huì)產(chǎn)生沖突址愿,EhCache 的解決方法是如果文件沖突了,則重建 cache冻璃。這對(duì)于 Cache 數(shù)據(jù)需要保存的時(shí)候可能不利响谓。當(dāng)然,Cache 只是簡(jiǎn)單的加速省艳,而不能保證數(shù)據(jù)的安全娘纷。如果想保證數(shù)據(jù)的存儲(chǔ)安全,可以使用 Bekeley DB Java Edition 版本跋炕。這是個(gè)嵌入式數(shù)據(jù)庫赖晶。可以確保存儲(chǔ)安全和空間的利用率辐烂。
由于 EhCache 是進(jìn)程中的緩存系統(tǒng)遏插,一旦將應(yīng)用部署在集群環(huán)境中,每一個(gè)節(jié)點(diǎn)維護(hù)各自的緩存數(shù)據(jù)纠修,當(dāng)某節(jié)點(diǎn)對(duì)緩存數(shù)據(jù)進(jìn)行更新胳嘲,這些更新的數(shù)據(jù)無法在其它節(jié)點(diǎn)中共享,這不僅會(huì)降低節(jié)點(diǎn)運(yùn)行的效率扣草,而且會(huì)導(dǎo)致數(shù)據(jù)不同步的情況發(fā)生了牛。例如某個(gè)網(wǎng)站采用 A, B 兩個(gè)節(jié)點(diǎn)作為集群部署,當(dāng) A 節(jié)點(diǎn)的緩存更新后辰妙,而 B 節(jié)點(diǎn)緩存尚未更新就可能出現(xiàn)用戶在瀏覽頁面的時(shí)候鹰祸,一會(huì)是更新后的數(shù)據(jù),一會(huì)是尚未更新的數(shù)據(jù)密浑,盡管我們也可以通過 Session Sticky 技術(shù)來將用戶鎖定在某個(gè)節(jié)點(diǎn)上蛙婴,但對(duì)于一些交互性比較強(qiáng)或者是非 Web 方式的系統(tǒng)來說,Session Sticky 顯然不太適合尔破。所以就需要用到 EhCache 的集群解決方案街图。
EhCache 從 1.7 版本開始背传,支持五種集群方案,分別是:
- RMI
- Terracotta
- JMS
- JGroups
- EhCache Server
本文主要介紹筆者用到的 RMI 方式台夺。RMI 是 Java 的一種遠(yuǎn)程方法調(diào)用技術(shù),是一種點(diǎn)對(duì)點(diǎn)的基于 Java 對(duì)象的通訊方式痴脾。EhCache 從 1.2 版本開始就支持 RMI 方式的緩存集群颤介。在集群環(huán)境中 EhCache 所有緩存對(duì)象的鍵和值都必須是可序列化的鸭栖,也就是必須實(shí)現(xiàn) java.io.Serializable 接口聋伦,這點(diǎn)在其它集群方式下也是需要遵守的。下圖是 RMI 集群模式的結(jié)構(gòu)圖:
在 RMI 集群模式中玛臂,集群中每個(gè)節(jié)點(diǎn)都是平等關(guān)系前域,并不存在主節(jié)點(diǎn)或從節(jié)點(diǎn)的概念辕近。因此節(jié)點(diǎn)間必須有一個(gè)機(jī)制能夠互相認(rèn)識(shí)對(duì)方,必須知道其它節(jié)點(diǎn)的信息匿垄,包括主機(jī)地址移宅、端口號(hào)等。
在 EhCache 中提供了兩種節(jié)點(diǎn)的發(fā)現(xiàn)方式:手動(dòng)發(fā)現(xiàn)和自動(dòng)發(fā)現(xiàn)椿疗。手動(dòng)發(fā)現(xiàn)配置的方式需要在每個(gè)節(jié)點(diǎn)中配置其他所有節(jié)點(diǎn)的 IP, 端口等連接信息漏峰,如果集群中某節(jié)點(diǎn)發(fā)生變化,則需要對(duì)緩存重新配置届榄。由于筆者工程的集群環(huán)境 IP 不是穩(wěn)定不變的浅乔,而且為了追求更高的動(dòng)態(tài)性,所以筆者主要對(duì)自動(dòng)發(fā)現(xiàn)方式進(jìn)行學(xué)習(xí)和總結(jié)铝条。
二. EhCache 自動(dòng)發(fā)現(xiàn)工程的構(gòu)建
自動(dòng)發(fā)現(xiàn)方式使用 tcp 廣播來建立和包含一個(gè)廣播組靖苇,它的特征是最小配置和對(duì)成員組的自動(dòng)添加和管理。每個(gè)節(jié)點(diǎn)都是同等級(jí)的班缰,沒有任何節(jié)點(diǎn)存在優(yōu)先級(jí)的概念贤壁。對(duì)等點(diǎn)每一秒中向廣播組發(fā)送心跳,如果一個(gè)對(duì)等點(diǎn)在五秒鐘內(nèi)沒發(fā)送過來鲁捏,則此對(duì)等點(diǎn)將會(huì)被刪除芯砸,如果有新的给梅,則會(huì)被加入集群假丧。
筆者將以自己的工程環(huán)境進(jìn)行略微修改,然后進(jìn)行說明动羽。
筆者想搭建一個(gè)簡(jiǎn)單的 EhCache 項(xiàng)目:在 IP 為 192.168.22.2(稱為 LSL 的主機(jī))與 IP 為 192.168.22.3(稱為 GRQ 的主機(jī))之間建立 EhCache 緩存包帚,使得在兩主機(jī)之間可以將元素 put 到緩存中,也可以通過 get 方法將緩存中的元素取出來(包括自己 put 進(jìn)緩存的运吓,也包括其他主機(jī) put 進(jìn)入的)渴邦。
2.1 Maven 配置
EhCache 依賴的 jar 包不多疯趟,僅僅依賴于一個(gè) junit。Maven 配置方法很簡(jiǎn)繁谋梭,如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.grq.mySpringMvc</groupId>
<artifactId>mySpringMvc</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.8.3</version>
</dependency>
</dependencies>
</project>
2.2 EhCache 配置文件編寫
EhCache 項(xiàng)目的使用信峻,核心在于 EhCache 的 xml 配置文件的編寫。如果使用手動(dòng)配置瓮床,兩個(gè)主機(jī)的 xml 配置文件會(huì)略有不同盹舞,但差別不大。但如果設(shè)置為自動(dòng)發(fā)現(xiàn)方式隘庄,則用完全相同的設(shè)置即可踢步。
以 GRQ 主機(jī)的 xml 配置文件 ehcache-cluster.xml 文件為例,對(duì) ehcache 配置文件內(nèi)容逐個(gè)部分進(jìn)行講解丑掺。
<?xml version="1.0" encoding="utf-8"?>
<ehcache>
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="peerDiscovery=automatic, multicastGroupAddress=230.0.0.1,
multicastGroupPort=4446, timeToLive=32" />
<cacheManagerPeerListenerFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
properties="port=40001,socketTimeoutMillis=2000" />
<!--demo 緩存-->
<cache name="auto_cache" maxElementsInMemory="1000" eternal="false"
timeToIdleSeconds="500" timeToLiveSeconds="500" overflowToDisk="true"
diskSpoolBufferSizeMB="30" maxElementsOnDisk="1000000"
diskPersistent="false" diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true,
replicateUpdatesViaCopy=true, replicateRemovals=true,asynchronousReplicationIntervalMillis=1000" />
<!--用于初始化緩存获印,以及自動(dòng)設(shè)置-->
<bootstrapCacheLoaderFactory
class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"/>
</cache>
</ehcache>
2.2.1 cacheManagerPeerProviderFactory
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="peerDiscovery=automatic, multicastGroupAddress=230.0.0.1,
multicastGroupPort=4446, timeToLive=32" />
cacheManagerPeerProviderFactory 屬性如下:
- peerDiscovery:automatic 為自動(dòng),manual 為手動(dòng)街州;
- mulicastGroupAddress: 廣播組地址兼丰,該項(xiàng)目中我們使用的廣播地址是 230.0.0.1;
- mulicastGroupPort: 廣播組端口菇肃,該項(xiàng)目中我們使用的端口時(shí) 40001地粪;
-
timeToLive: 搜索某個(gè)網(wǎng)段上的緩存;
- 0: 限制在同一個(gè)服務(wù)器琐谤;
- 1: 限制在同一個(gè)子網(wǎng)蟆技;
- 32: 是限制在同一個(gè)網(wǎng)站;
- 64: 是限制在同一個(gè)region斗忌;
- 128: 是同一塊大陸质礼;
-
hostName:主機(jī)名或者 ip,用來接受或者發(fā)送信息的接口织阳;
- 注:組播地址可以指定 D 類 IP 地址空間眶蕉,范圍從 224.0.1.0 到 238.255.255.255 中的任何一個(gè)地址。
2.2.2 cacheManagerPeerListenerFactory
cacheManagerPeerListenerFactory 是用來監(jiān)聽從集群發(fā)送過來的信息唧躲,有 class 與 properties 兩個(gè)屬性造挽。配置 CacheManagerPeerListener 需要指定一個(gè) CacheManagerPeerListenerFactory,它以插件的機(jī)制實(shí)現(xiàn)弄痹。
Ehcache 有一個(gè)內(nèi)置的基于 RMI 的分布系統(tǒng)饭入。它的監(jiān)聽器是 RMICacheManagerPeerListener,這個(gè)監(jiān)聽器可以用 RMICacheManagerPeerListenerFactory 來配置肛真。配置如下:
<cacheManagerPeerListenerFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
properties="port=40001,socketTimeoutMillis=2000" />
屬性內(nèi)容如下:
-
hostname: 運(yùn)行監(jiān)聽器的服務(wù)器名稱谐丢。該項(xiàng)是可選項(xiàng),標(biāo)明了做為集群群組的成員的地址,同時(shí)也是你想要控制的從集群中接收消息的接口乾忱。
- 在 CacheManager 初始化的時(shí)候讥珍,會(huì)檢查 hostname 是否可用;
- 如果 hostName 不可用窄瘟,CacheManager 將拒絕啟動(dòng)并拋出一個(gè)連接被拒絕的異常衷佃。
- 如果沒有指定 hostName,hostName 將用 InetAddress.getLocalHost().getHostAddress() 來得到蹄葱。
- port: 監(jiān)聽器監(jiān)聽的端口纲酗。
-
socketTimeoutMillis: Socket 超時(shí)的時(shí)間,默認(rèn)值是 2000ms新蟆。
- 注:當(dāng) socket 同步緩存請(qǐng)求地址比較遠(yuǎn),即不是本地局域網(wǎng)時(shí)右蕊,可能需要把這個(gè)時(shí)間配置大些琼稻,不然很可能延時(shí)導(dǎo)致同步緩存失敗。
2.2.3 cache
<cache name="auto_cache" maxElementsInMemory="1000" eternal="false"
timeToIdleSeconds="500" timeToLiveSeconds="500" overflowToDisk="true"
diskSpoolBufferSizeMB="30" maxElementsOnDisk="1000000"
diskPersistent="false" diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
....
</cache>
必填屬性如下:
- name: 緩存名稱饶囚;
- maxElementsInMemory: 緩存最大個(gè)數(shù)帕翻;
- eternal: 對(duì)象是否永久有效。一旦設(shè)置了萝风,timeout 將不起作用嘀掸;
- overflowToDisk:當(dāng)內(nèi)存中對(duì)象數(shù)量達(dá) maxElementsInMemory 時(shí),Ehcache 將會(huì)對(duì)象寫到磁盤中规惰;
- diskSpoolBufferSizeMB:這個(gè)參數(shù)設(shè)置 DiskStore 即磁盤緩存的緩存區(qū)大小睬塌。默認(rèn)是 30 MB。每個(gè) Cache 都應(yīng)該有自己的一個(gè)緩沖區(qū)歇万;
- maxElementsOnDisk:硬盤最大緩存?zhèn)€數(shù)揩晴;
可選的屬性如下:
-
timeToIdleSeconds:設(shè)置對(duì)象在失效前的允許閑置時(shí)間(單位:秒)。
- 僅當(dāng) eternal=false 時(shí)贪磺,對(duì)象才是有時(shí)效性的硫兰,否則緩存永久存儲(chǔ);
- 默認(rèn)值是 0寒锚,也就是可閑置時(shí)間無窮大劫映;
-
timeToLiveSeconds:設(shè)置對(duì)象在失效前允許存活時(shí)間(單位:秒);
- 僅當(dāng) eternal=false 時(shí)刹前,對(duì)象才是有時(shí)效性的泳赋;
- 默認(rèn)值是 0,也就是對(duì)象存活時(shí)間無窮大腮郊;
- 最大時(shí)間介于創(chuàng)建時(shí)間和失效時(shí)間之間摹蘑;
-
diskPersistent:是否 disk store 在虛擬機(jī)啟動(dòng)時(shí)持久化;
- 默認(rèn)值是 false轧飞;
-
memoryStoreEvictionPolicy:當(dāng)達(dá)到 maxElementsInMemory 限制時(shí)衅鹿,Ehcache 將會(huì)根據(jù)指定的策略去清理內(nèi)存撒踪。
- LRU: 最近最少使用,默認(rèn)值大渤;
- FIFO: 先進(jìn)先出制妄;
- LFU: 較少使用;
- diskExpiryThreadIntervalSeconds:磁盤失效線程運(yùn)行時(shí)間間隔泵三,默認(rèn)是 120 秒耕捞。
- clearOnFlush:內(nèi)存數(shù)量最大時(shí)是否清除。
- bootstrapCacheLoaderFactory: 子選項(xiàng)烫幕,指定相應(yīng)的 BootstrapCacheLoader俺抽,用于在初始化緩存,以及自動(dòng)設(shè)置较曼。
2.2.4 cacheEventListenerFactory
在 cache 標(biāo)簽中磷斧,還有一個(gè)子標(biāo)簽 cacheEventListenerFactory。它用來注冊(cè)相應(yīng)的的緩存監(jiān)聽類捷犹,用于處理緩存事件弛饭,如 put, remove, update, expire。配置文件中如下所示:
<cache ...>
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true,
replicateUpdatesViaCopy=true, replicateRemovals=true,asynchronousReplicationIntervalMillis=1000" />
</cache>
具體元素如下所示:
cacheEventListenerFactory 用于注冊(cè)相應(yīng)的的緩存監(jiān)聽類萍歉,這些類用于處理緩存事件侣颂。
-
replicateAsynchronously: 復(fù)制模式的同步/異步,值取 true / false
- true: 異步復(fù)制操作
- false: 同步復(fù)制操作
- 默認(rèn)值: true
- replicatePuts: 當(dāng)新對(duì)象被放入緩存枪孩,集群內(nèi)其他緩存也放入憔晒。值為 true / false,默認(rèn)為 true蔑舞;
- replicateUpdates: 對(duì)于具有相同 key 對(duì)象丛晌,新對(duì)象是否覆蓋這個(gè)具有相同 key 的對(duì)象。值為 true / false斗幼,默認(rèn)值為 true
-
replicateUpdatesViaCopy: 是否直接將更新后的對(duì)象復(fù)制到集群中的其他緩存澎蛛;
- true: 將更新后的對(duì)象復(fù)制到集群中其他緩存;
- false: 不復(fù)制對(duì)象蜕窿,只向集群中的其他緩存發(fā)布一個(gè)對(duì)象更新的消息谋逻;
- 默認(rèn)值: true
- <font color=red>注:筆者在參考其他網(wǎng)站的設(shè)置時(shí),有些教程是將該項(xiàng)設(shè)置為 false 的桐经。對(duì)于自動(dòng)發(fā)現(xiàn)方式的集群部署毁兆,應(yīng)該設(shè)置為 true,這樣才能令不同機(jī)器之間的緩存同步復(fù)制刪除更新阴挣。</font>
- 筆者設(shè)置該項(xiàng)為 false 時(shí)气堕,會(huì)出現(xiàn)意外情況:第一次在集群中任意一臺(tái)機(jī)器上 put 一個(gè)數(shù)據(jù)對(duì) (key, value),緩存中正常;第二次在集群中的一臺(tái)機(jī)器 put 一個(gè)擁有相同 key 值茎芭,不同 value 值的數(shù)據(jù)對(duì) (key, newValue)揖膜,集群中其他機(jī)器的緩存中的 (key, value) 數(shù)據(jù)對(duì)也消失了。后來將該項(xiàng)設(shè)置為 false梅桩,該現(xiàn)象得以解決壹粟。
- replicateRemovals: 集群中在某機(jī)器上將對(duì)象移除后,是否復(fù)制狀態(tài)到集群中其他緩存宿百;默認(rèn)值為 true.
三. EhCache 集群間測(cè)試程序
筆者在兩臺(tái)機(jī)器上部署同樣的工程趁仙,工程中的 ehcache 配置文件在上面已經(jīng)完整給出,測(cè)試程序如下:
package com.grq.xStreamTest;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import java.net.URL;
import java.util.Scanner;
public class ehcacheTest {
public static void main(String[] args) throws InterruptedException {
URL url = ehcacheTest.class.getClassLoader().getResource("ehcache-cluster-auto.xml");
CacheManager manager = new CacheManager(url);
Cache cache = manager.getCache("auto_cache");
// 主機(jī) 1 的標(biāo)識(shí): GRQServer
Element element = new Element("GRQServer-" + System.currentTimeMillis(), "GRQServer");
// 主機(jī) 2 的標(biāo)識(shí): LSLServer
// Element element = new Element("LSLServer-" + System.currentTimeMillis(), "LSLServer");
cache.put(element);
// 鍵盤輸入流
Scanner in = new Scanner(System.in);
int i = 0;
while (true) {
String inputKey = null;
String inputValue = null;
Element inputElement = null;
// 輸入方法
System.out.println();
System.out.println("Method: ");
String method = in.nextLine();
switch (method) {
case "query":
break;
case "add":
case "update":
System.out.print("Key: ");
inputKey = in.nextLine();
System.out.print("Value: ");
inputValue = in.nextLine();
Element outputElement = new Element(inputKey, inputValue);
cache.put(outputElement);
break;
case "delete":
System.out.print("Key: ");
inputKey = in.nextLine();
cache.remove(inputKey);
break;
default:
System.out.println("ERROR: Can't Recognize this Method \"" + method + "\"");
break;
}
// 輸出緩存中所有 (Key, Value) 值
for(Object key : cache.getKeys()) {
Element obj = cache.get(key);
if(obj == null) {
continue;
}
Object value = obj.getObjectValue();
if(value == null) {
continue;
}
System.out.println(key + " : " + value);
}
}
}
}
兩臺(tái)主機(jī)上同時(shí)運(yùn)行程序垦页,通過試驗(yàn)雀费,發(fā)現(xiàn)調(diào)用不同的 EhCache 方法可以實(shí)現(xiàn)集群之間緩存的增刪改查操作。