标签:
本章节将要介绍EhCache及EhCache实现分布式的一些解决方案、并针对于这些解决性方案做一个实现,后续将出一个提供项目模块化、服务化、插件化的VieMall快速开发平台,同时集成Dubbo服务化、Zookeeper(分布式调度/分布式配置管理服务)、Redis分布式缓存技术及Memcache/Ehcache 二级缓存切换、FastDFS分布式文件系统、ActiveMQ异步消息中间件、Solr搜索、Nginx负载均衡等分布式及读写分离、如果有时间可以深入分表分库(mycat /sharding-jdbc),并且将这每一项技术细化分析,一起来学习和分享。后续动态可以关注个人博客 http://www.viemall.cn/;
EhCache 是一个纯 Java 的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认的 CacheProvider。
下图是 EhCache 在应用程序中的位置:
EhCache 的主要特性有:
1.快速;
2.简单;
3.多种缓存策略;
4.缓存数据有两级:内存和磁盘,因此无需担心容量问题;
5.缓存数据会在虚拟机重启的过程中写入磁盘;
6.可以通过 RMI、可插入 API 等方式进行分布式缓存;
7.具有缓存和缓存管理器的侦听接口;
8.支持多缓存管理器实例,以及一个实例的多个缓存区域;
9.提供 Hibernate 的缓存实现;
10.等等 …
由于 EhCache 是进程中的缓存系统,一旦将应用部署在集群环境中,每一个节点维护各自的缓存数据,当某节点对缓存数据进行更新,这些更新的数据无法在其它节点中共享,这不仅会降低节点运行的效率,而且会导致数据不同步的情况发生。例如某个网站采用 A、B 两个节点作为集群部署,当 A 节点的缓存更新后,而 B 节点缓存尚未更新就可能出现用户在浏览页面的时候,一会是更新后的数据,一会是尚未更新的数据,尽管我们也可以通过 Session Sticky 技术来将用户锁定在某个节点上,但对于一些交互性比较强或者是非 Web 方式的系统来说,Session Sticky 显然不太适合。所以就需要用到 EhCache 的集群解决方案。
EhCache 从 1.7 版本开始,支持五种集群方案,分别是:
本文主要介绍其中的三种最为常用集群方式,分别是 RMI、JGroups 以及 EhCache Server 。
RMI 是 Java 的一种远程方法调用技术,是一种点对点的基于 Java 对象的通讯方式。EhCache 从 1.2 版本开始就支持 RMI 方式的缓存集群。在集群环境中 EhCache 所有缓存对象的键和值都必须是可序列化的,也就是必须实现 java.io.Serializable 接口,这点在其它集群方式下也是需要遵守的。
下图是 RMI 集群模式的结构图:
图 2. RMI 集群模式结构图:
采用 RMI 集群模式时,集群中的每个节点都是对等关系,并不存在主节点或者从节点的概念,因此节点间必须有一个机制能够互相认识对方,必须知道其它节点的信息,包括主机地址、端口号等。EhCache 提供两种节点的发现方式:手工配置和自动发现。手工配置方式要求在每个节点中配置其它所有节点的连接信息,一旦集群中的节点发生变化时,需要对缓存进行重新配置。
由于 RMI 是 Java 中内置支持的技术,因此使用 RMI 集群模式时,无需引入其它的 Jar 包,EhCache 本身就带有支持 RMI 集群的功能。使用 RMI 集群模式需要在 ehcache.xml 配置文件中定义cacheManagerPeerProviderFactory 节点。假设集群中有两个节点,分别对应的 RMI 绑定信息是:
自动发现方式使用tcp广播来建立和包含一个广播组,它的特征是最小配置和对成员组的自动添加和管理。没有那个服务器是有优先级的。对等点每一秒中向广播组发送心跳,如果一个对等点在五秒钟内没发送过来,则此对等点将会被删除,如果有新的,则会被加入集群。
<span style="font-family:SimHei;font-size:18px;"> <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="peerDiscovery=automatic, multicastGroupAddress=224.1.1.1, multicastGroupPort=1000, timeToLive=32" /> </span>解析:
peerDiscovery 方式:atutomatic 为自动 ;mulicastGroupAddress 广播组地址:230.0.0.1;mulicastGroupPort 广播组端口:40001;timeToLive 搜索某个网段上的缓存:0是限制在同一个服务器,1是限制在同一个子网,32是限制在同一个网站,64是限制在同一个region,128是同一块大陆,还有个256,我就不说了;hostName:主机名或者ip,用来接受或者发送信息的接口。同时组播地址可以指定 D 类 IP 地址空间,范围从 224.0.1.0 到 238.255.255.255 中的任何一个地址。
如下是详细的配置:
Server1- 1000:
<span style="font-family:SimHei;font-size:18px;"> <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="peerDiscovery=automatic, multicastGroupAddress=224.1.1.1, multicastGroupPort=1000, timeToLive=32" /> <cacheManagerPeerListenerFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory" properties="hostName=127.0.0.1,port=1000,socketTimeoutMillis=120000" /> <!-- 默认缓存 --> <defaultCache maxElementsInMemory="1000" eternal="true" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="true" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> <!-- demo缓存 --> <cache name="user" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" /> <!-- 用于在初始化缓存,以及自动设置 --> <bootstrapCacheLoaderFactory class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" /> </cache> </span>
Server2 -2000:
<span style="font-family:SimHei;font-size:18px;"><cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="peerDiscovery=automatic, multicastGroupAddress=224.1.1.1, multicastGroupPort=1000, timeToLive=32" /> <cacheManagerPeerListenerFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory" properties="hostName=127.0.0.1,port=2000,socketTimeoutMillis=120000" /> <!-- 默认缓存 --> <defaultCache maxElementsInMemory="1000" eternal="true" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="true" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> <!-- demo缓存 --> <cache name="user" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" /> <!-- 用于在初始化缓存,以及自动设置 --> <bootstrapCacheLoaderFactory class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" /> </cache> </span>
手动成员发现需要给监听器配置ip地址和端口。任何成员不能在运行的时候添加和移除。手动的成员发现推荐在组播不能实现的时候使用,比如集群的路由不允许发送组播报文的时候。你也可以单向的缓存数据同步,比如让server2可以同步server1的数据,但sever1不能同步server2的。
配置手动发现成员时需要配置cacheManagerPeerProviderFactory的如下属性
peerDiscovery=manual
rmiUrls=//server:port/cacheName, ...
rmiUrls是缓存成员的列表,但是不包括自己,也就是被配置的这台服务器的地址。
案例:
比如你的集群里有server1和server2两台服务器,你希望同步 user
下面是server1-2000的配置:
<span style="font-family:SimHei;font-size:18px;"><cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="hostName=127.0.0.1, port=2000, socketTimeoutMillis=12000, peerDiscovery=manual, rmiUrls=//127.0.0.1:1000/user" /> <cacheManagerPeerListenerFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory" properties="hostName=127.0.0.1,port=2000,socketTimeoutMillis=120000" /> <!-- demo缓存 --> <cache name="user" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" /> <!-- 用于在初始化缓存,以及自动设置 --> <bootstrapCacheLoaderFactory class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" /> </cache> 如果想配置多个:rmiUrls=//127.0.0.1:1000/test1| rmiUrls=//127.0.0.1:1000/test2 下面是server2-1000的配置: <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="hostName=127.0.0.1,port=1000,socketTimeoutMillis=12000, peerDiscovery=manual, rmiUrls=//127.0.0.1:2000/user" /> <cacheManagerPeerListenerFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory" properties="hostName=127.0.0.1,port=1000,socketTimeoutMillis=120000" /> <!-- demo缓存 --> <cache name="user" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" /> <!-- 用于在初始化缓存,以及自动设置 --> <bootstrapCacheLoaderFactory class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" /> </cache> </span><span style="font-family: SimHei; font-size: 18px; background-color: rgb(255, 255, 255);"> </span>
EhCache 从 1.5. 版本开始增加了 JGroups 的分布式集群模式。与 RMI 方式相比较, JGroups 提供了一个非常灵活的协议栈、可靠的单播和多播消息传输,主要的缺点是配置复杂以及一些协议栈对第三方包的依赖。
JGroups 也提供了基于 TCP 的单播 ( Unicast ) 和基于 UDP 的多播 ( Multicast ) ,对应 RMI 的手工配置和自动发现。使用单播方式需要指定其它节点的主机地址和端口,下面是两个节点.
<span style="font-family:SimHei;font-size:18px;"> <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory" properties="connect=TCP(bind_addr=127.0.0.1;bind_port=1000): TCPPING(initial_hosts=127.0.0.1[1000],127.0.0.1[2000];port_range=1;timeout=5000;num_initial_members=2): MERGE2(min_interval=3000;max_interval=5000): FD_ALL(interval=5000;timeout=20000): FD(timeout=5000;max_tries=48;): VERIFY_SUSPECT(timeout=1500): pbcast.NAKACK(retransmit_timeout=100,200,300,600,1200,2400,4800;discard_delivered_msgs=true): pbcast.STABLE(stability_delay=1000;desired_avg_gossip=20000;max_bytes=0): pbcast.GMS(print_local_addr=true;join_timeout=5000)" propertySeparator="::" /></span>
<span style="font-family:SimHei;font-size:18px;"> EhCache 的 Groups 集群模式还有另外一种节点发现方式, 就是通过多播( multicast )来维护集群中的所有有效节点。 这也是最为简单而且灵活的方式,与手工模式不同的是, 每个节点上的配置信息都相同,大大方便了节点的部署, 集群启动时会自动发现同一局域网的缓存服务器 以jgroups 多播方式配置集群,自动发现集节点 <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory" properties="connect=UDP(mcast_addr=224.1.1.1;mcast_port=45678;ip_ttl=32;mcast_send_buf_size=120000;mcast_recv_buf_size=80000): PING(timeout=2000;num_initial_members=2): MERGE2(min_interval=5000;max_interval=10000): FD_SOCK:VERIFY_SUSPECT(timeout=1500): pbcast.NAKACK(retransmit_timeout=3000): UNICAST(timeout=5000): pbcast.STABLE(desired_avg_gossip=20000): FRAG: pbcast.GMS(join_timeout=5000;print_local_addr=true)" propertySeparator="::" /> </span>从上面的配置来看,JGroups 的配置要比 RMI 复杂得多,但也提供更多的微调参数,有助于提升缓存数据复制的性能。详细的 JGroups 配置参数的具体意义可参考 JGroups 的配置手册。
JGroups 方式对应缓存节点的配置信息如下:
<span style="font-family:SimHei;font-size:18px;"><!-- demo缓存 replicateAsynchronously 对象同步是否异步完成,默认为true。如果比较紧急就设为false。 在一致性时间性要求不强的时候,设为异步可大大提供性能,因为它是异步立即返回的,而且可以批量提交。 replicateUpdatesViaCopy 是否将对象变更复制到所有节点,还是只是发送一个失效信息,让对方该缓存失效,当对方需要该缓存时重新计算载入。 默认为true。鉴于对象复制的消耗挺大的,又有锁的问题,而且对方也未必需要该对象,所以此属性建议设为false。如果业务上真的需要设为true时,就可考虑使用Terracotta了。 replicatePuts、replicateUpdates、replicateRemovals 增删改是否同步,默认都为true。但因为我们前面选择了失效算法,所以replicatePuts 要设为false。 --> <cache name="user" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <cacheEventListenerFactory class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory" properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true" /> <!-- 用于在初始化缓存,以及自动设置 --> <bootstrapCacheLoaderFactory class="net.sf.ehcache.distribution.jgroups.JGroupsBootstrapCacheLoaderFactory" /> </cache> </span>
使用 JGroups 需要引入 JGroups 的 Jar 包以及 EhCache 对 JGroups 的封装包 ehcache-jgroupsreplication-xxx.jar 。
在一些启用了 IPv6 的电脑中,经常启动的时候报如下错误信息:
java.lang.RuntimeException: the type of the stack (IPv6) and the user supplied addresses (IPv4) don‘t match: /231.12.21.132.
解决的办法是增加 JVM 参数:-Djava.net.preferIPv4Stack=true。如果是 Tomcat 服务器,可在 catalina.bat 或者 catalina.sh 中增加如下环境变量即可:
SET CATALINA_OPTS=-Djava.net.preferIPv4Stack=true
与前面介绍的两种集群方案不同的是, EhCache Server 是一个独立的缓存服务器,其内部使用 EhCache 做为缓存系统,可利用前面提到的两种方式进行内部集群。对外提供编程语言无关的基于 HTTP 的 RESTful 或者是 SOAP 的数据缓存操作接口。
下面是 EhCache Server 提供的对缓存数据进行操作的方法:
OPTIONS /{cache}}
获取某个缓存的可用操作的信息。
HEAD /{cache}/{element}
获取缓存中某个元素的 HTTP 头信息,例如:
curl --head http://localhost:8080/ehcache/rest/sampleCache2/2
EhCache Server 返回的信息如下:
HTTP/1.1 200 OK
X-Powered-By: Servlet/2.5
Server: GlassFish/v3
Last-Modified: Sun, 27 Jul 2008 08:08:49 GMT
ETag: "1217146129490"
Content-Type: text/plain; charset=iso-8859-1
Content-Length: 157
Date: Sun, 27 Jul 2008 08:17:09 GMT
GET /{cache}/{element}
读取缓存中某个数据的值。
PUT /{cache}/{element}
写缓存。
由于这些操作都是基于 HTTP 协议的,因此你可以在任何一种编程语言中使用它,例如 Perl、PHP 和 Ruby 等等。
下图是 EhCache Server 在应用中的架构:
图 3. EhCache Server 应用架构图:
EhCache Server 同时也提供强大的安全机制、监控功能。在数据存储方面,最大的 Ehcache 单实例在内存中可以缓存 20GB。最大的磁盘可以缓存 100GB。通过将节点整合在一起,这样缓存数据就可以跨越节点,以此获得更大的容量。将缓存 20GB 的 50 个节点整合在一起就是 1TB 了。
属性解释:
必须属性:
name:设置缓存的名称,用于标志缓存,惟一
maxElementsInMemory:在内存中最大的对象数量
maxElementsOnDisk:在DiskStore中的最大对象数量,如为0,则没有限制
eternal:设置元素是否永久的,如果为永久,则timeout忽略
overflowToDisk:是否当memory中的数量达到限制后,保存到Disk
可选的属性:
timeToIdleSeconds:设置元素过期前的空闲时间
timeToLiveSeconds:设置元素过期前的活动时间
diskPersistent:是否disk store在虚拟机启动时持久化。默认为false
diskExpiryThreadIntervalSeconds:运行disk终结线程的时间,默认为120秒
memoryStoreEvictionPolicy:缓存满了之后的淘汰算法
缓存子元素:
cacheEventListenerFactory:注册相应的的缓存监听类,用于处理缓存事件,如put,remove,update,和expire
bootstrapCacheLoaderFactory:指定相应的BootstrapCacheLoader,用于在初始化缓存,以及自动设置。
RMI缓存分布同步查找 class使用net.sf.ehcache.distribution.RMICacheReplicatorFactory
Jroups缓存同步查找 class使用 net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory
这个工厂支持以下属性:
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
原文地址及个人源码地址: http://www.dczou.com/viemall/209.html
参考资料:http://www.ibm.com/developerworks/cn/java/j-lo-ehcache/
标签:
原文地址:http://blog.csdn.net/tang06211015/article/details/52281551