一缓存系统简介 EhCache 是一个纯 Java 的进程内缓存框架具有快速精干等特点是 Hibernate 中默认的 CacheProvider EhCache 应用架构图下图是 EhCache 在应用程序中的位置 EhCache 的主要特性有 快速精干 简单 多种缓存策略 缓存数据有两级内存和磁盘因此无需担心容量问题 缓存数据会在虚拟机重启的过程中写入磁盘 可以通过 RMI可插入 API 等方式进行分布式缓存 具有缓存和缓存管理器的侦听接口 支持多缓存管理器实例以及一个实例的多个缓存区域 提供 Hibernate 的缓存实现 由于 EhCache 是进程中的缓存系统一旦将应用部署在集群环境中每一个节点维护各自的缓存数据当某个节点对缓存数据进行更新这些更新的数据无法在其它节点中共享这不仅会降低节点运行的效率而且会导致数据不同步的情况发生例如某个网站采用 AB 两个节点作为集群部署当 A 节点的缓存更新后而 B 节点缓存尚未更新就可能出现用户在浏览页面的时候一会是更新后的数据一会是尚未更新的数据尽管我们也可以通过 Session Sticky 技术来将用户锁定在某个节点上但对于一些交互性比较强或者是非 Web 方式的系统来说Session Sticky 显然不太适合 所以就需要用到 EhCache 的集群解决方案 从版本开始Ehcache可以使用分布式的缓存了EhCache 从 版本开始支持五种集群方案分别是 ? Terracotta ? RMI ? JMS ? JGroups ? EhCache Server 其中的三种最为常用集群方式分别是 RMIJGroups 以及 EhCache Server 本文主要介绍RMI的方式 分布式这个特性是以plugin的方式实现的Ehcache自带了一些默认的分布式缓存插件实现这些插件可以满足大部分应用的需要如果需要使用其他的插件那就需要自己开发了开发者可以通过查看distribution包里的源代码及JavaDoc来实现它尽管不是必须的在使用分布式缓存时理解一些ehcahce的设计思想也是有帮助的这可以参看分布式缓存设计的页面以下的部分将展示如何让分布式插件同ehcache一起工作 下面列出的是一些分布式缓存中比较重要的方面 ? 你如何知道集群环境中的其他缓存? ? 分布式传送的消息是什么形式? ? 什么情况需要进行复制?增加(Puts)更新(Updates)或是失效(Expiries)? ? 采用什么方式进行复制?同步还是异步方式? 为了安装分布式缓存你需要配置一个PeerProvider一个CacheManagerPeerListener 它们对于一个CacheManager来说是全局的每个进行分布式操作的cache都要添加一个cacheEventListener来传送消息 二集群缓存概念及其配置 正确的元素类型 只有可序列化的元素可以进行复制一些操作比如移除只需要元素的键值而不用整个元素在这样的操作中即使元素不是可序列化的但键值是可序列化的也可以被复制 成员发现(Peer Discovery) Ehcache进行集群的时候有一个cache组的概念每个cache都是其他cache的一个peer没有主cache的存在刚才我们问了一个问题你如何知道集群环境中的其他缓存?这个问题可以命名为成员发现(Peer Discovery) Ehcache提供了两种机制用来进行成员发现就像一辆汽车手动档和自动档要使用一个内置的成员发现机制要在ehcache的配置文件中指定cacheManagerPeerProviderFactory元素的class属性为 netsfehcachedistributionRMICacheManagerPeerProviderFactory 自动的成员发现 自动的发现方式用TCP广播机制来确定和维持一个广播组它只需要一个简单的配置可以自动的在组中添加和移除成员在集群中也不需要什么优化服务器的知识这是默认推荐的 成员每秒向群组发送一个心跳如果一个成员 秒种都没有发出信号它将被群组移除如果一个新的成员发送了一个心跳它将被添加进群组 任何一个用这个配置安装了复制功能的cache都将被其他的成员发现并标识为可用状态 要设置自动的成员发现需要指定ehcache配置文件中cacheManagerPeerProviderFactory元素的properties属性就像下面这样 peerDiscovery=automatic multicastGroupAddress=multicast address | multicast host name multicastGroupPort=port timeToLive= (timeToLive属性详见常见问题部分的描述) 示例 假设你在集群中有两台服务器你希望同步sampleCache和sampleCache每台独立的服务器都要有这样的配置 配置server和server <cacheManagerPeerProviderFactoryclass=netsfehcachedistributionRMICacheManagerPeerProviderFactoryproperties=peerDiscovery=automatic multicastGroupAddress=/>multicastGroupPort= timeToLive=手动进行成员发现 进行手动成员配置要知道每个监听器的IP地址和端口成员不能在运行时动态地添加和移除在技术上很难使用广播的情况下就可以手动成员发现例如在集群的服务器之间有一个不能传送广播报文的路由器你也可以用手动成员发现进行单向的数据复制只让server知道server而server不知道server 配置手动成员发现需要指定ehcache配置文件中cacheManagerPeerProviderFactory的properties属性像下面这样 peerDiscovery=manual rmiUrls=//server:port/cacheName //server:port/cacheName … rmiUrls配置的是服务器cache peers的列表注意不要重复配置 示例 假设你在集群中有两台服务器你要同步sampleCache和sampleCache下面是每个服务器需要的配置 配置server <cacheManagerPeerProviderFactoryclass=netsfehcachedistributionRMICacheManagerPeerProviderFactoryproperties=peerDiscovery=manual/>rmiUrls=//server:/sampleCache|//server:/sampleCache 配置server <cacheManagerPeerProviderFactoryclass=netsfehcachedistributionRMICacheManagerPeerProviderFactoryproperties=peerDiscovery=manual/>rmiUrls=//server:/sampleCache|//server:/sampleCache配置CacheManagerPeerListener 每个CacheManagerPeerListener监听从成员们发向当前CacheManager的消息配置CacheManagerPeerListener需要指定一个CacheManagerPeerListenerFactory它以插件的机制实现用来创建CacheManagerPeerListener cacheManagerPeerListenerFactory的属性有 class – 一个完整的工厂类名 properties – 只对这个工厂有意义的属性使用逗号分隔 Ehcache有一个内置的基于RMI的分布系统它的监听器是RMICacheManagerPeerListener这个监听器可以用 RMICacheManagerPeerListenerFactory来配置 <cacheManagerPeerListenerFactoryclass=netsfehcachedistributionRMICacheManagerPeerListenerFactoryproperties=hostName=localhost port=/>socketTimeoutMillis=有效的属性是 hostname (可选) – 运行监听器的服务器名称标明了做为集群群组的成员的地址同时也是你想要控制的从集群中接收消息的接口 在CacheManager初始化的时候会检查hostname是否可用 如果hostName不可用CacheManager将拒绝启动并抛出一个连接被拒绝的异常 如果指定hostname将使用InetAddressgetLocalHost()getHostAddress()来得到 警告不要将localhost配置为本地地址因为它在网络中不可见将会导致不能从远程服务器接收信息从而不能复制在同一台机器上有多个CacheManager的时候你应该只用localhost来配置 port – 监听器监听的端口 socketTimeoutMillis (可选) – Socket超时的时间默认是ms当你socket同步缓存请求地址比较远不是本地局域网你可能需要把这个时间配置大些不然很可能延时导致同步缓存失败 配置CacheReplicators 每个要进行同步的cache都需要设置一个用来向CacheManagerr的成员复制消息的缓存事件监听器这个工作要通过为每个cache的配置增加一个cacheEventListenerFactory元素来完成 <! Sample cache named sampleCache ><cache name=sampleCachemaxElementsInMemory=eternal=falsetimeToIdleSeconds=timeToLiveSeconds=overflowToDisk=false><cacheEventListenerFactory class=netsfehcachedistributionRMICacheReplicatorFactoryproperties=replicateAsynchronously=truereplicatePuts=true replicateUpdates=true replicateUpdatesViaCopy=false replicateRemovals=true /></cache>class – 使用netsfehcachedistributionRMICacheReplicatorFactory 这个工厂支持以下属性 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 你可以使用ehcache的默认行为从而减少配置的工作量默认的行为是以异步的方式复制每件事你可以像下面的例子一样减少RMICacheReplicatorFactory的属性配置 <! Sample cache named sampleCache All missing RMICacheReplicatorFactory properties default to true ><cache name=sampleCachemaxElementsInMemory=eternal=trueoverflowToDisk=falsememoryStoreEvictionPolicy=LFU><cacheEventListenerFactory class=netsfehcachedistributionRMICacheReplicatorFactory/></cache> 常见的问题 Windows上的Tomcat 有一个Tomcat或者是JDK的bug在tomcat启动时如果tomcat的安装路径中有空格的话在启动时RMI监听器会失败参见bin/wa?A=ind&L=rmiusers&P=和doc/faqhowtobugs/l 由于在Windows上安装Tomcat默认是装在Program Files文件夹里的所以这个问题经常发生 广播阻断 自动的peer discovery与广播息息相关广播可能被路由阻拦像Xen和VMWare这种虚拟化的技术也可以阻拦广播如果这些都打开了你可能还在要将你的网卡的相关配置打开一个简单的办法可以告诉广播是否有效 那就是使用ehcache remote debugger来看心跳是否可用 广播传播的不够远或是传得太远 你可以通过设置badly misnamed time to live来控制广播传播的距离用广播IP协议时timeToLive的值指的是数据包可以传递的域或是范围约定如下 是限制在同一个服务器 是限制在同一个子网 是限制在同一个网站 是限制在同一个region 是限制在同一个大洲 是不限制 译者按上面这些资料翻译的不够准确请读者自行寻找原文理解吧 在Java实现中默认值是也就是在同一个子网中传播改变timeToLive属性可以限制或是扩展传播的范围 三 RMI方式缓存集群/配置分布式缓存 RMI 是 Java 的一种远程方法调用技术是一种点对点的基于 Java 对象的通讯方式EhCache 从 版本开始就支持 RMI 方式的缓存集群在集群环境中 EhCache 所有缓存对象的键和值都必须是可序列化的也就是必须实现 javaioSerializable 接口这点在其它集群方式下也是需要遵守的 下图是 RMI 集群模式的结构图 采用 RMI 集群模式时集群中的每个节点都是对等关系并不存在主节点或者从节点的概念因此节点间必须有一个机制能够互相认识对方必须知道其它节点的信息包括主机地址端口号等EhCache 提供两种节点的发现方式手工配置和自动发现手工配置方式要求在每个节点中配置其它所有节点的连接信息一旦集群中的节点发生变化时需要对缓存进行重新配置 由于 RMI 是 Java 中内置支持的技术因此使用 RMI 集群模式时无需引入其它的 Jar 包EhCache 本身就带有支持 RMI 集群的功能使用 RMI 集群模式需要在 ehcachexml 配置文件中定义 cacheManagerPeerProviderFactory 节点 分布式同步缓存要让这边的cache知道对方的cache叫做Peer Discovery(成员发现) EHCache实现成员发现的方式有两种 手动查找 A 在ehcachexml中配置PeerDiscovery成员发现对象 Server配置配置本地hostNameport是分别监听:的mobileCache和: 的mobileCache注意这里的mobileCache是缓存的名称分别对应着serverserver的cache的配置 <?xml version= encoding=gbk?><ehcache xmlns:xsi=instance xsi:noNamespaceSchemaLocation=ehcachexsd> <diskStore path=javaiotmpdir/> <! 集群多台服务器中的缓存这里是要同步一些服务器的缓存 server hostName: port: cacheName:mobileCache server hostName: port: cacheName:mobileCache server hostName: port: cacheName:mobileCache 注意每台要同步缓存的服务器的RMI通信socket端口都不一样在配置的时候注意设置 > <! server 的cacheManagerPeerProviderFactory配置 > <cacheManagerPeerProviderFactory class=netsfehcachedistributionRMICacheManagerPeerProviderFactory properties=hostName=localhost port= socketTimeoutMillis= peerDiscovery=manual rmiUrls=//:/mobileCache|//:/mobileCache /></ehcache>以上注意cacheManagerPeerProviderFactory元素出现的位置在diskStore下 同样在你的另外台服务器上增加配置 Server配置本地hostport为分别同步:的mobileCache和:的mobileCache <! server 的cacheManagerPeerProviderFactory配置 ><cacheManagerPeerProviderFactory class=netsfehcachedistributionRMICacheManagerPeerProviderFactory properties=hostName=localhost port= socketTimeoutMillis= peerDiscovery=manual rmiUrls=//:/mobileCache|//:/mobileCache/>Server配置本地hostport为分别同步:的mobileCache缓存和:的mobileCache缓存 <! server 的cacheManagerPeerProviderFactory配置 ><cacheManagerPeerProviderFactory class=netsfehcachedistributionRMICacheManagerPeerProviderFactory properties=hostName=localhost port= socketTimeoutMillis= peerDiscovery=manual rmiUrls=//:/mobileCache|//:/mobileCache/>这样就在三台不同的服务器上配置了手动查找cache的PeerProvider成员发现的配置了 值得注意的是你在配置rmiUrls的时候要特别注意url不能重复出现并且端口地址都是对的 如果指定hostname将使用InetAddressgetLocalHost()getHostAddress()来得到 警告不要将localhost配置为本地地址因为它在网络中不可见将会导致不能从远程服务器接收信息从而不能复制在同一台机器上有多个CacheManager的时候你应该只用localhost来配置 B 下面配置缓存和缓存同步监听需要在每台服务器中的ehcachexml文件中增加cache配置和cacheEventListenerFactorycacheLoaderFactory的配置 <defaultCache maxElementsInMemory= eternal=false timeToIdleSeconds= timeToLiveSeconds= overflowToDisk=false/><! 配置自定义缓存 maxElementsInMemory:缓存中允许创建的最大对象数 eternal:缓存中对象是否为永久的如果是超时设置将被忽略对象从不过期 timeToIdleSeconds:缓存数据空闲的最大时间也就是说如果有一个缓存有多久没有被访问就会被销毁如果该值是 就意味着元素可以停顿无穷长的时间 timeToLiveSeconds:缓存数据存活的时间缓存对象最大的的存活时间超过这个时间就会被销毁这只能在元素不是永久驻留时有效如果该值是就意味着元素可以停顿无穷长的时间 overflowToDisk:内存不足时是否启用磁盘缓存 memoryStoreEvictionPolicy:缓存满了之后的淘汰算法 每一个小时更新一次缓存(小时过期) ><cache name=mobileCache maxElementsInMemory= eternal=false overflowToDisk=true timeToIdleSeconds= timeToLiveSeconds= memoryStoreEvictionPolicy=LFU> <! RMI缓存分布同步查找 class使用netsfehcachedistributionRMICacheReplicatorFactory 这个工厂支持以下属性 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= > <! 监听RMI同步缓存对象配置 注册相应的的缓存监听类用于处理缓存事件如putremoveupdate和expire > <cacheEventListenerFactory class=netsfehcachedistributionRMICacheReplicatorFactory properties=replicateAsynchronously=true /> replicatePuts=true replicateUpdates=true replicateUpdatesViaCopy=false replicateRemovals=true <! 用于在初始化缓存以及自动设置 > <bootstrapCacheLoaderFactory class=netsfehcachebootstrapBootstrapCacheLoaderFactory/></cache> C 这样就完成了台服务器的配置下面给出server的完整的ehcachexml的配置 <?xml version= encoding=gbk?><ehcache xmlns:xsi=instance xsi:noNamespaceSchemaLocation=ehcachexsd> <diskStore path=javaiotmpdir/> <! 集群多台服务器中的缓存这里是要同步一些服务器的缓存 server hostName: port: cacheName:mobileCache server hostName: port: cacheName:mobileCache server hostName: port: cacheName:mobileCache 注意每台要同步缓存的服务器的RMI通信socket端口都不一样在配置的时候注意设置 > <! server 的cacheManagerPeerProviderFactory配置 > <cacheManagerPeerProviderFactory class=netsfehcachedistributionRMICacheManagerPeerProviderFactory properties=hostName=localhost port= socketTimeoutMillis= peerDiscovery=manual rmiUrls=//:/mobileCache|//:/mobileCache /> <defaultCache maxElementsInMemory= eternal=false timeToIdleSeconds= timeToLiveSeconds= overflowToDisk=false/> <! 配置自定义缓存 maxElementsInMemory:缓存中允许创建的最大对象数 eternal:缓存中对象是否为永久的如果是超时设置将被忽略对象从不过期 timeToIdleSeconds:缓存数据空闲的最大时间也就是说如果有一个缓存有多久没有被访问就会被销毁 如果该值是 就意味着元素可以停顿无穷长的时间 timeToLiveSeconds:缓存数据存活的时间缓存对象最大的的存活时间超过这个时间就会被销毁 这只能在元素不是永久驻留时有效如果该值是就意味着元素可以停顿无穷长的时间 overflowToDisk:内存不足时是否启用磁盘缓存 memoryStoreEvictionPolicy:缓存满了之后的淘汰算法 每一个小时更新一次缓存(小时过期) > <cache name=mobileCache maxElementsInMemory= eternal=false overflowToDisk=true timeToIdleSeconds= timeToLiveSeconds= memoryStoreEvictionPolicy=LFU> <! RMI缓存分布同步查找 class使用netsfehcachedistributionRMICacheReplicatorFactory 这个工厂支持以下属性 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= > <! 监听RMI同步缓存对象配置 注册相应的的缓存监听类用于处理缓存事件如putremoveupdate和expire > <cacheEventListenerFactory class=netsfehcachedistributionRMICacheReplicatorFactory properties=replicateAsynchronously=true /> replicatePuts=true replicateUpdates=true replicateUpdatesViaCopy=false replicateRemovals=true <! 用于在初始化缓存以及自动设置 > <bootstrapCacheLoaderFactory class=netsfehcachebootstrapBootstrapCacheLoaderFactory/> </cache></ehcache>自动发现 自动发现配置和手动查找的方式有一点不同其他的地方都基本是一样的同样在ehcachexml中增加配置配置如下 <!搜索某个网段上的缓存timeToLive 是限制在同一个服务器 是限制在同一个子网 是限制在同一个网站 是限制在同一个region 是限制在同一个大洲 是不限制><cacheManagerPeerProviderFactory class=netsfehcachedistributionRMICacheManagerPeerProviderFactory properties=peerDiscovery=automatic multicastGroupAddress= multicastGroupPort= timeToLive=/> |