Spring-ehcache RMI形式的分布式缓存配置
- 简介
- git 源码
简介
这是本人因为工作需要研究的关于ehcache的分布式RMI模式的使用心得已经自己的一些心得。
git 源码
详细介绍
话不多说,下面结合demo分步做详细的介绍
jar依赖
- ehcache所需jar:ehchache-core
- spring注解所需 spring-context
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.2.7.RELEASE</version>
</dependency>
spring.xml文件中引用spring-ehcache.xml
注解使用cacheManager
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<!-- ehcache config -->
<cache:annotation-driven cache-manager="ehCacheCacheManager"/>
<bean id="ehCacheCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="cacheManagerFactoryBean"/>
<!-- EhCache library setup -->
<bean id="cacheManagerFactoryBean" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="classpath:spring-ehcache.xml" p:shared="true"/>
</beans>
spring-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">
ehcache标签的属性
- name 名称-可选,唯一。名字用于注解或区别Terracotta集群缓存。对于
Terracotta集群缓存,缓存管理器的名字和缓存的名字的组合能够在Terracotta集群缓存中标志一个特定的缓存存储。 - updateCheck 更新检查-一个可选的布尔值标志符,用于标志缓存管理器是否应该通过网络检查Ehcache的新版本。默认为true。
- dynamicConfig 动态配置 - 可选。用于关闭与缓存管理器相关的缓存的动态配置。默认为true,即动态配置为开启状态。动态配置的缓存可以根据缓存对象在运行状态改变自己的TTI,TTL和最大磁盘空间和内在容量
- monitoring 监控 - 可选。决定缓存管理器是否应该自动注册SampledCacheMBean到系统MBean服务器上。
cacheManagerPeerProviderFactory
它分布式缓存管理器提供者,指定一个CacheManagerPeerProviderFactory,它将用于创建一个CacheManagerPeerProvider, CacheManagerPeerProvider侦测集群中的其它事务管理器,实现和分布式环境下的缓存同步。
相关属性介绍:
- propertySeparator 拆分上面properties属性的分隔符
- peerDiscovery=manual 手动的缓存同步
- peerDiscovery=automatic 广播式的缓存同步
- rmiUrls 指的是手动侦测缓存地址,每一次当请求相应的缓存信息时,程序会先从配置的rmiUrls里去分别读取缓存,如果无该缓存信息,则生成缓存,存储在cacheManagerPeerListenerFactory所配置的地址和端口的对应的缓存里
地址与地址之间用|(竖线)来分割。 -
- url填写规则:
//(双斜杠)+cacheManagerPeerListenerFactory属性中配置的hostName+:(冒号)+端口+/(斜杠)+缓存属性名称
- url填写规则:
RMI 手动配置
<!-- 手动配置rmi同步的地址信息(这种模式本人亲测多服务器试验还有问题)-->
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="peerDiscovery=manual,
rmiUrls=//127.0.0.1:40002/testCache|//127.0.0.1:40002/testCache2 "
propertySeparator="," />
RMI 自动广播
这样当缓存改变时,ehcache会向230.0.0.1端口4446发RMI UDP组播包
- mulicastGroupAddress 广播组地址
- mulicastGroupPort 广播组端口
- timeToLive
0是限制在同一个服务器
1是限制在同一个子网
32是限制在同一个网站
64是限制在同一个region
128是限制在同一个大洲
255是不限制
<!-- 自动广播式rmi形式(这种模式本人目前多服务器试验还有问题) -->
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="peerDiscovery=automatic,
multicastGroupAddress=230.0.0.1,
multicastGroupPort=4446,
timeToLive=32"/>
cacheManagerPeerListenerFactory
每个CacheManagerPeerListener监听从成员们发向当前CacheManager的消息。配置 CacheManagerPeerListener需要指定一个CacheManagerPeerListenerFactory,它以插件的机制实现, 用来创建CacheManagerPeerListener。
Ehcache有一个内置的基于RMI的分布系统。它的监听器是RMICacheManagerPeerListener,这个监听器可以用RMICacheManagerPeerListenerFactory来配置
<!-- 本机缓存的信息对应的地址和端口配置监听器的工厂类 -->
<cacheManagerPeerListenerFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
properties="hostName=127.0.0.1, port=40002,socketTimeoutMillis=2000" />
- hostname (可选) – 运行监听器的服务器名称。标明了做为集群群组的成员的地址,同时也是你想要控制的从集群中接收消息的接口。
在CacheManager初始化的时候会检查hostname是否可用。
如果hostName不可用,CacheManager将拒绝启动并抛出一个连接被拒绝的异常。
如果指定,hostname将用InetAddress.getLocalHost().getHostAddress()来得到。 - port – 监听器监听的端口。
- socketTimeoutMillis (可选) – Socket超时的时间。默认是2000ms。当你socket同步缓存请求地址比较远,不是本地局域网。你可能需要把这个时间配置大些,不然很可能延时导致同步缓存失败。
具体cache 对象配置
<!-- 缓存最长存在10分钟后失效,如果5分钟未访问,缓存也会失效 -->
<cache name="testCache"
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicateAsynchronously=false, replicatePuts=true,
replicatePutsViaCopy=true, replicateUpdates=true,
replicateUpdatesViaCopy=true, replicateRemovals=true" />
</cache>
<!-- 缓存最长存在99天后失效,如果99天未访问,缓存也会失效 -->
<cache name="testCache2" maxEntriesLocalHeap="10000" eternal="false"
timeToIdleSeconds="8640000" timeToLiveSeconds="8640000">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicateAsynchronously=false, replicatePuts=true,
replicatePutsViaCopy=true, replicateUpdates=true,
replicateUpdatesViaCopy=true, replicateRemovals=true" />
</cache>
</ehcache>
cache属性介绍
- 必须属性:
name:设置缓存的名称,用于标志缓存,惟一
maxElementsInMemory:在内存中最大的对象数量
maxElementsOnDisk:在DiskStore中的最大对象数量,如为0,则没有限制
eternal:设置元素是否永久的,如果为永久,则timeout忽略
overflowToOffHeap:
overflowToDisk:是否当memory中的数量达到限制后,保存到Disk - 可选的属性:
timeToIdleSeconds:设置元素过期前的空闲时间,缓存自创建日期起至失效时的间隔时间。值为零,意味空闲时间为无穷,默认为零。
timeToLiveSeconds:设置元素过期前的活动时间,缓存创建以后,最后一次访问缓存的日期至失效之时的时间间隔。值为零,意味存活时间为无穷,默认为零。
diskPersistent:是否disk store在虚拟机启动时持久化。默认为false
diskExpiryThreadIntervalSeconds:运行disk终结线程的时间,默认为120秒
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:策略关于Eviction
cacheEventListenerFactory
注册相应的的缓存监听类,用于处理缓存事件,如put,remove,update,和expire bootstrap CacheLoaderFactory:指定相应的BootstrapCacheLoader,用于在初始化缓存,以及自动设置。
- replicatePuts=true|false - 默认为true。新加入的缓存中的元素是否要复制到其它节点中去。
- replicatePutsViaCopy=true|false - 默认为true。新加入的缓存中的元素是否要复制到其它 缓存中,或者一条删除消息是否发送。
- replicateUpdates=true|false - 默认为true。当新加入的元素与已存在的元素键值出现冲突时,是否要覆盖已存在元素。
- replicateRemovals=true - 默认为true。被移去的元素是否要复制。
- replicateAsynchronously=true | false - 默认为true。true表示复制是异步的,false表示复制是同步的。
- replicateUpdatesViaCopy=true | false - 默认为true。
- asynchronousReplicationIntervalMillis= - 默认值为1000,最小值为10。只有在replicateAsynchronously=true,该属性才适用。
bootstrapCacheLoaderFactory
指定相应的BootstrapCacheLoader,用于在初始化缓存,以及自动设置
代码中缓存标记的使用
读取/生成缓存@Cacheable
能够根据方法的请求参数对其结果进行缓存。即当重复使用相同参数调用方法的时候,方法本身不会被调用执行,即方法本身被略过了,取而代之的是方法的结果直接从缓存中找到并返回了。
- value:缓存位置名称,不能为空,如果使用EHCache,就是ehcache.xml中声明的cache的name
- key:缓存的key(保证唯一的参数),默认为空,既表示使用方法的参数类型及参数值作为key,支持SpEL
- 缓存key还可以用如下规则组成,当我们要使用root作为key时,可以不用写root直接@Cache(key=“caches[1].name”)。因为他默认是使用#root的
1.methodName 当前方法名 #root.methodName
2.method 当前方法 #root.method.name
3.target 当前被动用对象 #root.target
4.targetClass 当前被调用对象 Class#root.targetClass
5.args 当前方法参数组成的数组 #root.args[0]
6.caches 当前被调用方法所使用的Cache #root.caches[0],name
7.方法参数 假设包含String型参数str #str
#p0代表方法的第一个参数
假设包含HttpServletRequest型参数request #request.getAttribute(‘usId32’) 调用入参对象的相关包含参数的方法
假设包含User型参数user #user.usId 调用入参对象的无参方法可以直接用此形式
8.字符串 ‘字符串内容’
- condition:触发条件,只有满足条件的情况才会加入缓存,默认为空,既表示全部都加入缓存,支持SpEL
- unless: 触发条件,只有不满足条件的情况才会加入缓存,默认为空,既表示全部都加入缓存,支持SpEL
- #result 可以获得返回结果对象
/**
* 生成缓存,同时下一次在调用此方法优先从缓存中获取信息
* 读取/生成缓存@Cacheable
* 能够根据方法的请求参数对其结果进行缓存。即当重复使用相同参数调用方法的时候,方法本身不会被调用执行,即方法本身被略过了,取而代之的是方法的结果直接从缓存中找到并返回了。
* @param hosId
* @param request
* @return
*/
@Cacheable(value="testCache",key="#hosId+'_'+'createTestCacheSuccess'", condition="#hosId!=null",unless="#result.result!=true or #result.data==null")
@RequestMapping(value = "/{hosId}/createTestCacheSuccess", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
@ResponseBody
public ResultVo createTestCacheSuccess(@PathVariable Long hosId, HttpServletRequest request) {
ResultVo resultVo = new ResultVo();
resultVo.setKind(SUCCESS_CODE);
resultVo.setResult(true);
resultVo.setData((Object)("createTestCacheSuccess成功生成缓存"+System.currentTimeMillis()));
log.debug("进入实际生成缓存方法体,本次请求未使用缓存,本方法可以生成有效缓存,缓存未失效之前调用该方法将不会进入到方法体");
return resultVo;
}
删除缓存@CacheEvict
根据value 和key值来唯一找到缓存记录,并且清理缓存信息
- value:缓存的位置,不能为空。
- key:缓存的key,默认为空。
- condition:触发的条件,只有满足条件的情况才会清楚缓存,默认为空,支持SpEL。
- allEntries:true表示清除value中的全部缓存(可以理解为清空表),默认为false(删除单条数据)。
- beforeInvocation:当我们设置为true时,Spring会在调用该方法之前进行缓存的清除。清除操作默认是在方法成功执行之后触发的。
/**
* 删除缓存
* 删除缓存@CacheEvict
* 根据value 和key值来唯一找到缓存记录,并且清理缓存信息
* @param hosId
* @param request
* @return
*/
@CacheEvict(value="testCache",key="#hosId+'_'+'deleteCreateTestCacheSuccess'", condition="#hosId!=null")
@RequestMapping(value = "/{hosId}/deleteCreateTestCacheSuccess", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
@ResponseBody
public ResultVo deleteCreateTestCacheSuccess(@PathVariable Long hosId, HttpServletRequest request) {
log.debug("删除createTestCacheSuccess生成的缓存");
ResultVo resultVo = new ResultVo();
resultVo.setKind(SUCCESS_CODE);
resultVo.setResult(true);
resultVo.setData((Object)("删除缓存成功"+System.currentTimeMillis()));
return resultVo;
}
更新缓存@CachePut
它虽然也可以声明一个方法支持缓存,但它执行方法前是不会去检查缓存中是否存在之前执行过的结果,而是每次都执行该方法,并将执行结果放入指定缓存中。
- value:缓存的位置,不能为空。
- key:缓存的key,默认为空。
- condition:触发的条件,只有满足条件的情况才会清楚缓存,默认为空,支持SpEL。
/**
* 生成缓存,同时下一次在调用此方法还是会执行该方法并且同时更新缓存内容
*
* 更新缓存@CachePut
* 它虽然也可以声明一个方法支持缓存,但它执行方法前是不会去检查缓存中是否存在之前执行过的结果,而是每次都执行该方法,并将执行结果放入指定缓存中。
* @param hosId
* @param request
* @return
*/
@CachePut(value="testCache",key="#hosId+'_'+'updateTestCacheSuccess'", condition="#hosId!=null",unless="#result.result!=true or #result.data==null")
@RequestMapping(value = "/{hosId}/updateTestCacheSuccess", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
@ResponseBody
public ResultVo updateTestCacheSuccess(@PathVariable Long hosId, HttpServletRequest request) {
log.debug("进入实际生成缓存方法体,本方法可以生成有效缓存,下一次调用该方法依然会进入到方法体");
ResultVo resultVo = new ResultVo();
resultVo.setKind(SUCCESS_CODE);
resultVo.setResult(true);
resultVo.setData((Object)("updateTestCacheSuccess成功生成缓存"+System.currentTimeMillis()));
return resultVo;
}
通过EhCacheCacheManager获取缓存详情
EhCacheCacheManager (管理CacheManager的工具类)是在上面spring.xml 中配置的缓存管理对象
@Resource
EhCacheCacheManager ehCacheCacheManager;
/**
* 从cache 中获取实际缓存信息
* @param cacheName
* @param cacheKey
* @return
*/
@RequestMapping(value = "getResult", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
@ResponseBody
public ResultVo getResult(String cacheName,String cacheKey){
ResultVo resultVo = null;
CacheManager cacheManager=ehCacheCacheManager.getCacheManager();
if (cacheManager!=null){
Ehcache ehcache = cacheManager.getEhcache(CACHE_NEMA_TESTCACHE);
if(ehcache!=null){
Element element = ehcache.get(cacheKey);
if(element!=null && element.getObjectValue()!=null
&& element.getObjectValue() instanceof ResultVo){
resultVo = (ResultVo)element.getObjectValue();
}
}
}
return resultVo;
}
简单信息统计
/**
* 从cache 中获取缓存简单的监控信息(数据量少的时候适合这么干,数据量大的时候需要注意性能问题,一下子遍历所有缓存元素这将是一个灾难)
* @return
*/
@RequestMapping(value = "getCacheStatistic", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
@ResponseBody
public ResultVo getCacheStatistic(){
ResultVo resultVo = new ResultVo();
resultVo.setResult(false);
CacheManager cacheManager=ehCacheCacheManager.getCacheManager();
if (cacheManager!=null){
String []cacheNames=cacheManager.getCacheNames();
if( null != cacheNames && cacheNames.length>0 ){
StringBuffer ehcacheBuffer = new StringBuffer();
ehcacheBuffer.append(StringUtils.rightPad("CacheName", 15));
ehcacheBuffer.append(" | ");
ehcacheBuffer.append(StringUtils.rightPad("Key", 40));
ehcacheBuffer.append(" | ");
ehcacheBuffer.append(StringUtils.rightPad("HintCount", 10));
ehcacheBuffer.append(" | ");
ehcacheBuffer.append(StringUtils.rightPad("CreationTime", 25));
ehcacheBuffer.append(" | ");
ehcacheBuffer.append(StringUtils.rightPad("LastAccessTime", 25));
ehcacheBuffer.append(" | ");
ehcacheBuffer.append(StringUtils.rightPad("TimeToLive(ms)", 15));
ehcacheBuffer.append(" | ");
ehcacheBuffer.append(StringUtils.rightPad("TimeToIdle(ms)", 15));
//这里不打印数据值,因为打印值的话数据量比较大
ehcacheBuffer.append(" | ");
ehcacheBuffer.append("\n");
for (int i = 0; i < cacheNames.length; i++) {
Ehcache ehcache = cacheManager.getCache(cacheNames[i]);
if(ehcache!=null){
List<String> ehcacheKeys = ehcache.getKeys();
if( null!=ehcacheKeys && 0< ehcacheKeys.size() ){
for (String ehcacheKey:ehcacheKeys) {
Element element = ehcache.get(ehcacheKey);
if(element!=null ){
ehcacheBuffer.append(StringUtils.rightPad(ehcache.getName(), 15));//cachenName
ehcacheBuffer.append(" | ");
ehcacheBuffer.append(StringUtils.rightPad(ehcacheKey, 40));//key name
ehcacheBuffer.append(" | ");
ehcacheBuffer.append(StringUtils.rightPad(""+element.getHitCount(), 10));//命中次数
ehcacheBuffer.append(" | ");
ehcacheBuffer.append(StringUtils.rightPad(formatDate(element.getCreationTime()), 25));//创建时间
ehcacheBuffer.append(" | ");
ehcacheBuffer.append(StringUtils.rightPad(formatDate(element.getLastAccessTime()), 25));//最后访问时间
ehcacheBuffer.append(" | ");
ehcacheBuffer.append(StringUtils.rightPad(""+element.getTimeToLive(), 15)); //存活时间
ehcacheBuffer.append(" | ");
ehcacheBuffer.append(StringUtils.rightPad(""+element.getTimeToIdle(), 15)); //空闲时间
ehcacheBuffer.append(" | ");
ehcacheBuffer.append("\n");
}
}
}
}
}
log.debug("\n"+ehcacheBuffer.toString());
resultVo.setData(ehcacheBuffer);
resultVo.setResult(true);
}
}
return resultVo;
}
日志效果
参考资料
[1]: EhCache 缓存系统简介 https://www.ibm.com/developerworks/cn/java/j-lo-ehcache/
[2]: Ehcache配置文件译文 https://dreamzhong.iteye.com/blog/1161954
[3]: EhCache 系统简介 https://www.cnblogs.com/duwanjiang/p/6230113.html
[3]: 本人其他平台早期的文档 https://blog.51cto.com/tianyang10552/1899550