接到配合架构部要求配合测试需求,对EhCache 进行测试,在此之前,未接触过ehcache缓存,之前知道一些缓存,但是还真没了解过内存缓存。于是百度,看书,查资料,先恶补一下ehcache的一些知识,然后整理如下:
EhCache 是一个分布式缓存,分布式缓存系统简介:
Ehcache是一个纯java的进程内缓存框架,也就是说Ehcache是一个进程中的缓存系统。
Ehcache的主要特性有:
- 快速、精干,简单
- 多种缓存策略
- 缓存数据有两级:内存和磁盘,因此无需担心容量问题
- 缓存数据会在虚拟机重启的过程中写入磁盘
- 可以通过RMI、可插入API等方式进行分布式缓存
- 具有缓存和缓存管理器的侦听借口
- 支持多缓存管理器实例,以及一个实例的多个缓存区域
- 提供Hibernate 的缓存实现。
注意:由于Ehcache是进程中的缓存系统,一旦将应用部署到集群环境中, 每一个节点维护各自的缓存数据,当某个节点对缓存数据进行更新, 这些更新的数据无法再其他节点中共享, 不仅会降低节点运行的效率, 而且会导致数据不同步的情况发生。 例如某个网站采用A、B 两个节点作为集群部署, 当A节点的缓存更新后, 而B节点缓存尚未更新就可能出现用户在浏览页面的时候,一会是更新后的数据, 一会是尚未更新的数据。
所以就需要用到EhCache的集群解决方案
EhCache 从 1.7 版本开始,支持五种集群方案,分别是:
• Terracotta
• RMI
• JMS
• JGroups
• EhCache Server
其中的三种最为常用集群方式,分别是 RMI、JGroups 以及 EhCache Server 。本文主要介绍RMI的原理及测试方法,
RMI方式缓存集群/配置分布式缓存
RMI是java的一种远程方法调用技术,是一种点对点的基于java对象的通讯方法。Ehcache从1.2版本开始支持RMI方式的缓存集群。在集群环境中Ehcache所有缓存对象的键和值都必须是可序列化的,也就是说必须实现java.io.Serializable 接口,这点在其他集群方式下也是需要遵守的。
下面是RMI集群模式的结构图:
采用RMI集群模式时,集群中的每个节点都是对等关系,并不存在主节点或者从节点的概念,因此节点间必须有一个机制能够互相认识对方,必须知道其他节点的信息,包括主机ip,端口号等。Ehcache提供两种节点的发现方式:手工配置和自动发现。手工配置方式要求在每个节点中配置其他所有的节点连接信息,一旦集群中的节点发生变化是,需要对缓存进行重新配置。
由于RMI 是java中内置支持的技术, 因此使用RMI集群模式时, 无需引入其他的jar包,Ehcache 本身就带有支持RMI集群的功能, 使用RMI集群模式需要在ehcache.xml配置文件中定义cacheManagerPeerProviderFactory 节点。
分布式同步缓存要让这边的cache知道对方的cache,叫做Peer Discovery(成员发现) EHCache实现成员发现的方式有两种:
手动查找配置方法:
A、 在ehcache.xml中配置PeerDiscovery成员发现对象
Server1配置,配置本地hostName、port是400001,分别监听192.168.8.32:400002的mobileCache和192.168.5.231:400003 的mobileCache。注意这里的mobileCache是缓存的名称,分别对应着server2、server3的cache的配置。
<?xml version="1.0" encoding="gbk"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <diskStore path="java.io.tmpdir"/> <!-- 集群多台服务器中的缓存,这里是要同步一些服务器的缓存 server1 hostName:192.168.8.9 port:400001 cacheName:mobileCache server2 hostName:192.168.8.32 port:400002 cacheName:mobileCache server3 hostName:192.168.8.231 port:400003 cacheName:mobileCache 注意:每台要同步缓存的服务器的RMI通信socket端口都不一样,在配置的时候注意设置 --> <!-- server1 的cacheManagerPeerProviderFactory配置 --> <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="hostName=localhost, port=400001, socketTimeoutMillis=2000, peerDiscovery=manual, rmiUrls=//192.168.8.32:400002/mobileCache|//192.168.5.231:400003/mobileCache" /> </ehcache>
同样在你的另外2台服务器上增加配置
Server2,配置本地host,port为400002,分别同步192.168.8.9:400001的mobileCache和192.168.5.231:400003的mobileCache
<!-- server2 的cacheManagerPeerProviderFactory配置 --> <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="hostName=localhost, port=400002, socketTimeoutMillis=2000, peerDiscovery=manual, rmiUrls=//192.168.8.9:400001/mobileCache|//192.168.5.231:400003/mobileCache" />
Server3,配置本地host,port为400003,分别同步192.168.8.9:400001的mobileCache缓存和192.168.8.32:400002的mobileCache缓存
<!-- server3 的cacheManagerPeerProviderFactory配置 --> <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="hostName=localhost, port=400003, socketTimeoutMillis=2000, peerDiscovery=manual, rmiUrls=//192.168.8.9:400001/mobileCache|//192.168.8.32:400002/mobileCache" />
这样就在三台不同的服务器上配置了手动查找cache的PeerProvider成员发现的配置了。 值得注意的是你在配置rmiUrls的时候要特别注意url不能重复出现,并且端口、地址都是对的。
如果指定,hostname将使用InetAddress.getLocalHost().getHostAddress()来得到。
注意:不要将localhost配置为本地地址127.0.0.1,因为它在网络中不可见将会导致不能从远程服务器接收信息从而不能复制。在同一台机器上有多个CacheManager的时候,你应该只用localhost来配置。
B、 下面配置缓存和缓存同步监听,需要在每台服务器中的ehcache.xml文件中增加cache配置和cacheEventListenerFactory、cacheLoaderFactory的配置 <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="30" timeToLiveSeconds="30" overflowToDisk="false"/> <!-- 配置自定义缓存 maxElementsInMemory:缓存中允许创建的最大对象数 eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。 timeToIdleSeconds:缓存数据空闲的最大时间,也就是说如果有一个缓存有多久没有被访问就会被销毁,如果该值是 0 就意味着元素可以停顿无穷长的时间。 timeToLiveSeconds:缓存数据存活的时间,缓存对象最大的的存活时间,超过这个时间就会被销毁,这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。 overflowToDisk:内存不足时,是否启用磁盘缓存。 memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。 每一个小时更新一次缓存(1小时过期) --> <cache name="mobileCache" maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="1800" timeToLiveSeconds="3600" memoryStoreEvictionPolicy="LFU"> <!-- RMI缓存分布同步查找 class使用net.sf.ehcache.distribution.RMICacheReplicatorFactory 这个工厂支持以下属性: 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。 replicateUpdatesViaCopy=true | false – 当一个元素被拷贝到其他的cache中时是否进行复制�指定为true时为复制,默认是true。 asynchronousReplicationIntervalMillis=1000 --> <!-- 监听RMI同步缓存对象配置 注册相应的的缓存监听类,用于处理缓存事件,如put,remove,update,和expire --> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true "/> <!-- 用于在初始化缓存,以及自动设置 --> <bootstrapCacheLoaderFactory class="net.sf.ehcache.bootstrap.BootstrapCacheLoaderFactory"/> </cache>
下面给出server1的完整的ehcache.xml的配置:
<?xml version="1.0" encoding="gbk"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <diskStore path="java.io.tmpdir"/> <!-- 集群多台服务器中的缓存,这里是要同步一些服务器的缓存 server1 hostName:192.168.8.9 port:400001 cacheName:mobileCache server2 hostName:192.168.8.32 port:400002 cacheName:mobileCache server3 hostName:192.168.8.231 port:400003 cacheName:mobileCache 注意每台要同步缓存的服务器的RMI通信socket端口都不一样,在配置的时候注意设置 --> <!-- server1 的cacheManagerPeerProviderFactory配置 --> <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="hostName=localhost, port=400001, socketTimeoutMillis=2000, peerDiscovery=manual, rmiUrls=//192.168.8.32:400002/mobileCache|//192.168.5.231:400003/mobileCache" /> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="30" timeToLiveSeconds="30" overflowToDisk="false"/> <!-- 配置自定义缓存 maxElementsInMemory:缓存中允许创建的最大对象数 eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。 timeToIdleSeconds:缓存数据空闲的最大时间,也就是说如果有一个缓存有多久没有被访问就会被销毁, 如果该值是 0 就意味着元素可以停顿无穷长的时间。 timeToLiveSeconds:缓存数据存活的时间,缓存对象最大的的存活时间,超过这个时间就会被销毁, 这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。 overflowToDisk:内存不足时,是否启用磁盘缓存。 memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。 每一个小时更新一次缓存(1小时过期) --> <cache name="mobileCache" maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="1800" timeToLiveSeconds="3600" memoryStoreEvictionPolicy="LFU"> <!-- RMI缓存分布同步查找 class使用net.sf.ehcache.distribution.RMICacheReplicatorFactory 这个工厂支持以下属性: 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。 replicateUpdatesViaCopy=true | false – 当一个元素被拷贝到其他的cache中时是否进行复制�指定为true时为复制,默认是true。 asynchronousReplicationIntervalMillis=1000 --> <!-- 监听RMI同步缓存对象配置 注册相应的的缓存监听类,用于处理缓存事件,如put,remove,update,和expire --> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true "/> <!-- 用于在初始化缓存,以及自动设置 --> <bootstrapCacheLoaderFactory class="net.sf.ehcache.bootstrap.BootstrapCacheLoaderFactory"/> </cache> </ehcache>
Ehcache测试方法
本次测试服务由架构部提供的两个Ehcacahe服务端。一个做put操作,一个做同步操作。
jdk版本: jdk1.7.0_79
tomcat版本: Tomcat/7.0.57
修改echcahe.xml,参照上面的配置方式配置测试服务器ip地址
Put端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"> <cacheManagerPeerListenerFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory" properties="hostName = 10.27.82.170, port = 50001, socketTimeoutMillis=10000" /> <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="peerDiscovery=manual, rmiUrls=//10.27.82.168:40001/clusterCache"/> <cache name="clusterCache" maxEntriesLocalHeap="999999999" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" overflowToDisk="false"> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" /> </cache> </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" > <cacheManagerPeerListenerFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory" properties="hostName=172.19.136.197, port=40001, socketTimeoutMillis=2000" /> <cache name="clusterCache" maxEntriesLocalHeap="10" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" overflowToDisk="false"> <cacheEventListenerFactory class="com.suning.servlet.MyCacheEventListenerFactory"/> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" /> <bootstrapCacheLoaderFactory class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" properties="bootstrapAsynchronously=false, maximumChunkSizeBytes=5000000"/> </cache> </ehcache>
注:本次测试是采用手动配置同步信息方式,需要配置服务器端的ip+端口
修改成功后,将工程导出成war包,然后分别部署到两台测试服务器上,部署成功后,打开tomcat后台日志。
浏览器访问路径:
put数据url:
同步访问路径:
http://ip:8080/EhCacheTest2/monitor.htm
测试结果如下:
Put端日志打印:
同步端后台日志打印如下:
开启put端后,往put端中塞数据,然后往同步端进行同步,观察耗时,差值,以及系统资源消耗,带宽流程等,后续补充,基本大概的思路流程是这样