ehcache是一个很成熟的基于jvm内存的缓存框架,其效率是要比redis基于socket要高的,一般ehcache可以配合数据库做一个缓存中间件来减少数据库的访问压力。
见过很多人包括我自己之前也有这样的疑问:ehcache比jvm的static 集合好在哪里。
1、首先在存储大小方面 ehcache是和static map存储差不多的。稍微小一丢丢,算是个可忽略的优势
2、ehcache 有自己的缓存管理策略,比如说缓存刷新时机,缓存大小,缓存过期策略等,这是比之static map显而易见的优点,使用static map 是不好控制缓存大小的,而ehcache 可以很轻松的控制 不用我们自己关心
3、多项目缓存共享问题,其实redis在这里才是最好用的,但是个人觉得现在ehcache也有比较不错的集群管理了,而static map 也是无法做到的
进入正题
最最开始 添加依赖
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
首先说一下ehcache的基本使用
ehcache 存储的内容是一个Element ,这东西有个key 和value都是object类型的 其实和map差不多
首先想用它 分三层
第一层:CacheManager 这东西是通过读配置文件ehcache.xml读出来的
package是net.sf.ehcache 这里我使用的是2.6.x版本
CacheManager cacheManager = CacheManager.create(EhcacheManager.class.getClassLoader().getResource("ehcache.xml"));
第二层 Cache 说cache之前 先看一下配置文件了 上面提到的ehcache.xml是个什么呢
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="java.io.tmpdir/Tmp_EhCache" />
<defaultCache eternal="false"
maxElementsInMemory="1000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LRU" />
<!-- server1 的cacheManagerPeerProviderFactory配置 -->
<cache name="articleCache"
eternal="true"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
memoryStoreEvictionPolicy="LRU" >
</cache>
</ehcache>
解释下每个元素的意思 网上太多了
diskStore
diskStore元素:制定一个路径,当EHCache把数据写到硬盘上的时候,
会把数据写到该目录下。user.home - 用户主目录;user.dir - 用户当前工作目录;
java.io.tmpdir - 默认临时文件路径。
defaultCache
设定缓存的默认数据过期策略。
cache
设定具体的命名缓存的数据过期策略。
name
缓存名称。通常为缓存对象的类名;
maxElementsInMemory
设置基于内存的缓存可存放对象的最大数目;
maxElementOnDisk
设置基于硬盘的缓存可存放对象的最大数目;
eternal
如果为true,表示对象永远不会过期,此时会忽略tiemToldleSeconds和timeToLiveSeconds属性
默认为false。
timeToldleSeconds
设置允许对象处于空闲状态的最长时间,以秒为单位。
当对象最近一次被访问后,如果处于空闲状态的时间超过了
timeToldleSeconds属性值,这个对象就会过期。
当对象过期,EHCache将把它从缓存中清空。只有当eternal属性为false.
该属性才有效。如果该属性的值为0,那么就表示该对象可以无限期地存于缓存中。
即缓存被创建后,最后一次访问时间到缓存失效之时,两者之间的间隔,单位为秒(s)
timeToLiveSeconds
必须大于timeToldleSeconds属性,才有意义;
当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,
这个对象就会过期,EHCache将把它从缓存中清除;
即缓存自创建日期起能够存活的最长时间,单位为秒(s)
overflowToDisk
如果为true,表示当基于内存的缓存中的对象数目达到了maxElementsInMemory界限后
会把溢出的对象写到基于硬盘的缓存中。
注意,如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行(也就是序列化);
memoryStoreEvictionPolicy
缓存对象清除策略。
有三种:
FIFO:first in first out
先进先出。
LFU:Less Frequently Used
一直以来最少被使用策略,缓存元素有一个hit属性,hit(命中)值最小的将会被清除出缓存。
LRU:least Recenly used
最近最少被使用,缓存的元素有一个时间戳,当缓存的容量满了,
而又需要腾出地方来缓存新的元素的时候,
那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
diskSpoolBufferSizeMB
写入磁盘的缓冲区大小。
由于diskSpoolBufferSizeMB在内部实际是以字节为单位,
所以最大值是Integer的最大值即2047.99…M,反正不到2G。
所以如果配置的超过2G,将会导致diskSpoolBufferSizeMB为负数,
在put时ehcache误以为磁盘缓存队列已满,每次都执行都会阻塞。
maxElementsOnDisk
在DiskStore(磁盘存储)中的最大对象数量,如为0,则没有限制
diskPersistent
是否disk store在虚拟机启动时持久化。默认为false
diskExpiryThreadIntervalSeconds
Ehcache后台线程专门做Ellment失效监测以及清除工作。
此值不宜设置过低,否则会导致清理线程占用大量CPU资源。
默认值是120秒。
clearOnFlush
当调用flush()是否清除缓存,默认是。
maxEntriesLocalHeap
堆内存中最大缓存对象数,0没有限制
然后java代码
Cache articleCache = cacheManager.getCache("articleCache")
这样就拿到了cache对象 就可以操作Element了
第三层 就是我们开始说的Element
主要就是cache中的一些方法 我只说一些比较常用的
//存储一个Element 这样就存储了一个key为1 value为2的Element
articleCache.put(new Element(1, 2));
//有存就有取 看一下 这里是通过Element 中的key 拿到Element自己
Element element = articleCache.get(1);
//通过element拿到value值 这里可以直接强制类型转换 转换成你要的 注意不要转错了就行
Object objectValue = element.getObjectValue();
Integer i = (Integer)element.getObjectValue();
//拿到所有的key
List keys = articleCache.getKeys();
//通过key集合拿对象 这里返回的map 是不是很奇怪,其实方便了你的使用map的key 就是 Element中的key 直接get就可以拿出来了
Map<Object, Element> all = articleCache.getAll(keys);
下面我说一下注意事项:
基于Java 传址不传值 的特性 从ehcache中取出来的对象 如果直接更改它的属性 ehcache中的对象会一样被改。因为取出来和里面的是同一个对象。包括放进List 再怎么怎么样 。除非自己再拷贝一个。注意浅拷贝和深拷贝 这里就不多说了
Springboot 有和ehcache 集成一套注解使用 个人觉得 本来也没那么麻烦。那个还要写表达式,使用也没这么灵活。所以我自己不喜欢用。
ehcache 集群的知识
某度一搜 差不多都是基于官网的翻译。那张官方图片已经不知道在几个地方见过了。
ehcache 集群有好几种实现方式,详情baidu 我这里直说基于RMI的,所以先说RMI是个啥。
RMI(Remote Method Invocation)中文名:远程方法调用,能够让在客户端Java虚拟机上的对象像调用本地对象一样调用服务端java 虚拟机中的对象上的方法。
基于RMI的配置有自动发现和手动发现 我这里直说手动的 自动的还没有尝试过。
因为这东西是JVM提供的,所以不需要任何其他 jar 可以直接使用
主要也是依赖于配置 几乎不需要任何代码侵入。 还是ehcache.xml
<!-- 成员发现对象 cacheManagerPeerProviderFactory配置 -->
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="
peerDiscovery=manual, //手动发现
remoteObjectPort=40003, //监听端口,这个东西如果你开了防火墙,就得手动配置,并且这个端口加入白名单,如果没防火墙就不用了
rmiUrls=//127.0.0.1:40002/articleCache //监听谁的cache 配置对方的如果有多个 中间用 | 隔开
"
/>
<!--监听对象 配置自己的 其中有自己的名字 自己的端口 连接超时时间等-->
<cacheManagerPeerListenerFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
properties="hostName=127.0.0.1, port=40001,
socketTimeoutMillis=2000"/>
这两个是放在 ehcache标签里面的
然后是放在cache标签里面的
<cache name="updateCache"
eternal="true"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
memoryStoreEvictionPolicy="LRU" >
<cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicateAsynchronously=true,
replicatePuts=true,
replicateUpdates=true,
replicateUpdatesViaCopy=true,
replicateRemovals=true"/>
<!-- 用于在初始化缓存,以及自动设置 -->
<!--<bootstrapCacheLoaderFactory class="net.sf.ehcache.bootstrap.BootstrapCacheLoaderFactory"/>-->
<bootstrapCacheLoaderFactory
class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"
properties="bootstrapAsynchronously=true, maximumChunkSizeBytes=5000000"/>
</cache>
<!--
RMI缓存分布同步查找 class使用net.sf.ehcache.distribution.RMICacheReplicatorFactory
这个工厂支持以下属性:
replicatePuts=true | false – 当一个新元素增加到缓存中的时候是否要复制到其他的peers。默认是true。
replicateUpdates=true | false – 当一个已经在缓存中存在的元素被覆盖时是否要进行复制。默认是true。
replicateRemovals= true | false – 当元素移除的时候是否进行复制。默认是true。
replicateAsynchronously=true | false – 复制方式是异步的还是同步的 指定为true时是异步的额,默认是true。
replicatePutsViaCopy=true | false – 当一个新增元素被拷贝到其他的cache中时是否进行复制 指定为true时为复制,默认是true。
replicateUpdatesViaCopy=true | false – 当一个元素被拷贝到其他的cache中时是否进行复制 指定为true时为复制,默认是true。
asynchronousReplicationIntervalMillis=1000
-->
这个里面配置很简单 有几个我使用中总结需要注意的
1、由于RMI需要通知,所以你改了对象后 需要调用put方法 告诉ehcache 你改了它,什么不做不通知
2、同步和异步的问题,这里我本机实验,同步基于你对象的大小 所有需要同步的对象必须实现序列化,
序列化快自然就快。但是but 然而 如果 你配置的地址找不到人,rmiUrls=//127.0.0.1:40002/articleCache
这个东西,他会耗时很多 我本地是一个1s 注意是秒
这个底层实现 大约是这样的
启动时 会读取你这个配置放进一个map 每次 监听到有改动时遍历map 每个去通知 所以也就是不支持动态增减机器的
谁挂了仍然可以正常使用,注意失败log 是DUBUG级别的。
放一下所有两个配置文件 仅供参考
server1:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="java.io.tmpdir/Tmp_EhCache" />
<defaultCache eternal="false"
maxElementsInMemory="1000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LRU" />
<!-- server1 的cacheManagerPeerProviderFactory配置 -->
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="
peerDiscovery=manual,
remoteObjectPort=40003,
rmiUrls=//127.0.0.1:40002/articleCache|//127.0.0.1:40002/commentCache
"
/>
<cacheManagerPeerListenerFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
properties="hostName=127.0.0.1, port=40001,
socketTimeoutMillis=2000"/>
<cache name="articleCache"
eternal="true"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
memoryStoreEvictionPolicy="LRU" >
<cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicateAsynchronously=true,
replicatePuts=true,
replicateUpdates=true,
replicateUpdatesViaCopy=true,
replicateRemovals=true,
replicateAsynchronously=false"/>
<!-- 用于在初始化缓存,以及自动设置 -->
<!--<bootstrapCacheLoaderFactory class="net.sf.ehcache.bootstrap.BootstrapCacheLoaderFactory"/>-->
<bootstrapCacheLoaderFactory
class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"
properties="bootstrapAsynchronously=true, maximumChunkSizeBytes=5000000"/>
</cache>
<cache name="commentCache"
eternal="false"
maxElementsInMemory="50000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
memoryStoreEvictionPolicy="LRU" >
<cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicateAsynchronously=true,
replicatePuts=true,
replicateUpdates=true,
replicateUpdatesViaCopy=true,
replicateRemovals=true "/>
<!-- 用于在初始化缓存,以及自动设置 -->
<!--<bootstrapCacheLoaderFactory class="net.sf.ehcache.bootstrap.BootstrapCacheLoaderFactory"/>-->
<bootstrapCacheLoaderFactory
class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"
properties="bootstrapAsynchronously=true, maximumChunkSizeBytes=5000000"/>
</cache>
</ehcache>
server2:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="java.io.tmpdir/Tmp_EhCache" />
<defaultCache eternal="false"
maxElementsInMemory="1000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LRU" />
<!-- server1 的cacheManagerPeerProviderFactory配置 -->
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="
peerDiscovery=manual,
remoteObjectPort=40004,
rmiUrls=//127.0.0.1:40001/articleCache|//127.0.0.1:40001/commentCache"
/>
<cacheManagerPeerListenerFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
properties="hostName=127.0.0.1, port=40002,
socketTimeoutMillis=2000"/>
<cache name="articleCache"
eternal="true"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
memoryStoreEvictionPolicy="LRU" >
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicateAsynchronously=true,
replicatePuts=true,
replicateUpdates=true,
replicateUpdatesViaCopy=true,
replicateAsynchronously=true,
replicateRemovals=true "/>
<!-- 用于在初始化缓存,以及自动设置 -->
<!--<bootstrapCacheLoaderFactory class="net.sf.ehcache.bootstrap.BootstrapCacheLoaderFactory"/>-->
<bootstrapCacheLoaderFactory
class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"
properties="bootstrapAsynchronously=true">
</bootstrapCacheLoaderFactory>
</cache>
<cache name="commentCache"
eternal="false"
maxElementsInMemory="50000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
memoryStoreEvictionPolicy="LRU" >
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicateAsynchronously=true,
replicatePuts=true,
replicateUpdates=true,
replicateUpdatesViaCopy=true,
replicateAsynchronously=true,
replicateRemovals=true "/>
<!-- 用于在初始化缓存,以及自动设置 -->
<!--<bootstrapCacheLoaderFactory class="net.sf.ehcache.bootstrap.BootstrapCacheLoaderFactory"/>-->
<bootstrapCacheLoaderFactory
class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"
>
</bootstrapCacheLoaderFactory>
</cache>
</ehcache>