版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1434902
参考网址:
EhCache 是一个纯 Java 的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认的 CacheProvider。Ehcache 是一种广泛使用的开源 Java 分布式缓存。主要面向通用缓存,Java EE 和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个 gzip 缓存 servlet 过滤器,支持 REST 和 SOAP api 等特点。
优点:
缺点:
由于 EhCache 是进程中的缓存系统,一旦将应用部署在集群环境中,每一个节点维护各自的缓存数据,当某节点对缓存数据进行更新,这些更新的数据无法在其它节点中共享,这不仅会降低节点运行的效率,而且会导致数据不同步的情况发生。例如某个网站采用 A, B 两个节点作为集群部署,当 A 节点的缓存更新后,而 B 节点缓存尚未更新就可能出现用户在浏览页面的时候,一会是更新后的数据,一会是尚未更新的数据,尽管我们也可以通过 Session Sticky 技术来将用户锁定在某个节点上,但对于一些交互性比较强或者是非 Web 方式的系统来说,Session Sticky 显然不太适合。所以就需要用到 EhCache 的集群解决方案。
EhCache 从 1.7 版本开始,支持五种集群方案,分别是:
本文主要介绍笔者用到的 RMI 方式。RMI 是 Java 的一种远程方法调用技术,是一种点对点的基于 Java 对象的通讯方式。EhCache 从 1.2 版本开始就支持 RMI 方式的缓存集群。在集群环境中 EhCache 所有缓存对象的键和值都必须是可序列化的,也就是必须实现 java.io.Serializable 接口,这点在其它集群方式下也是需要遵守的。下图是 RMI 集群模式的结构图:
在 RMI 集群模式中,集群中每个节点都是平等关系,并不存在主节点或从节点的概念。因此节点间必须有一个机制能够互相认识对方,必须知道其它节点的信息,包括主机地址、端口号等。
在 EhCache 中提供了两种节点的发现方式:手动发现和自动发现。手动发现配置的方式需要在每个节点中配置其他所有节点的 IP, 端口等连接信息,如果集群中某节点发生变化,则需要对缓存重新配置。由于笔者工程的集群环境 IP 不是稳定不变的,而且为了追求更高的动态性,所以笔者主要对自动发现方式进行学习和总结。
自动发现方式使用 tcp 广播来建立和包含一个广播组,它的特征是最小配置和对成员组的自动添加和管理。每个节点都是同等级的,没有任何节点存在优先级的概念。对等点每一秒中向广播组发送心跳,如果一个对等点在五秒钟内没发送过来,则此对等点将会被删除,如果有新的,则会被加入集群。
笔者将以自己的工程环境进行略微修改,然后进行说明。
笔者想搭建一个简单的 EhCache 项目:在 IP 为 192.168.22.2(称为 LSL 的主机)与 IP 为 192.168.22.3(称为 GRQ 的主机)之间建立 EhCache 缓存,使得在两主机之间可以将元素 put 到缓存中,也可以通过 get 方法将缓存中的元素取出来(包括自己 put 进缓存的,也包括其他主机 put 进入的)。
EhCache 依赖的 jar 包不多,仅仅依赖于一个 junit。Maven 配置方法很简繁,如下所示:
<?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>
EhCache 项目的使用,核心在于 EhCache 的 xml 配置文件的编写。如果使用手动配置,两个主机的 xml 配置文件会略有不同,但差别不大。但如果设置为自动发现方式,则用完全相同的设置即可。
以 GRQ 主机的 xml 配置文件 ehcache-cluster.xml 文件为例,对 ehcache 配置文件内容逐个部分进行讲解。
<?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" />
<!--用于初始化缓存,以及自动设置-->
<bootstrapCacheLoaderFactory
class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"/>
</cache>
</ehcache>
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="peerDiscovery=automatic, multicastGroupAddress=230.0.0.1,
multicastGroupPort=4446, timeToLive=32" />
cacheManagerPeerProviderFactory 属性如下:
- 0: 限制在同一个服务器;
- 1: 限制在同一个子网;
- 32: 是限制在同一个网站;
- 64: 是限制在同一个region;
- 128: 是同一块大陆;
- **注:**组播地址可以指定 D 类 IP 地址空间,范围从 224.0.1.0 到 238.255.255.255 中的任何一个地址。
cacheManagerPeerListenerFactory 是用来监听从集群发送过来的信息,有 class 与 properties 两个属性。配置 CacheManagerPeerListener 需要指定一个 CacheManagerPeerListenerFactory,它以插件的机制实现。
Ehcache 有一个内置的基于 RMI 的分布系统。它的监听器是 RMICacheManagerPeerListener,这个监听器可以用 RMICacheManagerPeerListenerFactory 来配置。配置如下:
<cacheManagerPeerListenerFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
properties="port=40001,socketTimeoutMillis=2000" />
属性内容如下:
- 在 CacheManager 初始化的时候,会检查 hostname 是否可用;
- 如果 hostName 不可用,CacheManager 将拒绝启动并抛出一个连接被拒绝的异常。
- 如果没有指定 hostName,hostName 将用 InetAddress.getLocalHost().getHostAddress() 来得到。
- **注:**当 socket 同步缓存请求地址比较远,即不是本地局域网时,可能需要把这个时间配置大些,不然很可能延时导致同步缓存失败。
<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>
必填属性如下:
可选的属性如下:
- 仅当 eternal=false 时,对象才是有时效性的,否则缓存永久存储;
- 默认值是 0,也就是可闲置时间无穷大;
- 仅当 eternal=false 时,对象才是有时效性的;
- 默认值是 0,也就是对象存活时间无穷大;
- 最大时间介于创建时间和失效时间之间;
- 默认值是 false;
- LRU: 最近最少使用,默认值;
- FIFO: 先进先出;
- LFU: 较少使用;
在 cache 标签中,还有一个子标签 cacheEventListenerFactory。它用来注册相应的的缓存监听类,用于处理缓存事件,如 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 用于注册相应的的缓存监听类,这些类用于处理缓存事件。
- true: 异步复制操作
- false: 同步复制操作
- 默认值: true
- true: 将更新后的对象复制到集群中其他缓存;
- false: 不复制对象,只向集群中的其他缓存发布一个对象更新的消息;
- 默认值: true
- **注:**笔者在参考其他网站的设置时,有些教程是将该项设置为 false 的。对于自动发现方式的集群部署,应该设置为 true,这样才能令不同机器之间的缓存同步复制删除更新。
- 笔者设置该项为 false 时,会出现意外情况:第一次在集群中任意一台机器上 put 一个数据对 (key, value),缓存中正常;第二次在**集群中的一台机器** put 一个拥有相同 key 值,不同 value 值的数据对 (key, newValue),集群中其他机器的缓存中的 (key, value) 数据对也消失了。后来将该项设置为 false,该现象得以解决。
笔者在两台机器上部署同样的工程,工程中的 ehcache 配置文件在上面已经完整给出,测试程序如下:
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");
// 主机 1 的标识: GRQServer
Element element = new Element("GRQServer-" + System.currentTimeMillis(), "GRQServer");
// 主机 2 的标识: 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);
}
}
}
}
两台主机上同时运行程序,通过试验,发现调用不同的 EhCache 方法可以实现集群之间缓存的增删改查操作。