说明:本文是基于RMI手动同步的方式,使用程序动态注入配置缓存,抛弃传统的ehcache.xml配置方式
1,注入cacheManager管理所有缓存,添加各个缓存名及相关参数配置:
思路大致是:
在项目的主配置类中,通过@Bean注入cacheManager,统一管理所有cache,传统的用xml方式配置太累赘,其实只需要更该chacheName,其他参数变化不是很大,考虑用程序封装做到最大程度的代码重用所以采取拼接JSON串的形式,配置缓存名,及所有相关需要设置好的参数。然后通过解析JSON,拼接成带主机IP的RmiUrl,以manual手动rmi同步的方式配置peerDiscovery成员发现,再通过配置cacheManagerPeerListenerFactory这个类启动本机监听程序,来发现其他主机发来的同步请求。最后再用@EnableCaching注解开启spring支持对ehcache注解使用
下面是项目配置好的详细代码:
1 package com.cshtong.tower.web.init; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.UncheckedIOException; 6 import java.net.InetAddress; 7 import java.net.UnknownHostException; 8 import java.util.ArrayList; 9 import java.util.Iterator; 10 import java.util.List; 11 import java.util.Properties; 12 13 import javax.annotation.Resource; 14 15 import org.slf4j.Logger; 16 import org.slf4j.LoggerFactory; 17 import org.springframework.cache.annotation.EnableCaching; 18 import org.springframework.cache.ehcache.EhCacheCacheManager; 19 import org.springframework.cache.interceptor.KeyGenerator; 20 import org.springframework.cache.interceptor.SimpleKeyGenerator; 21 import org.springframework.context.annotation.Bean; 22 import org.springframework.context.annotation.Configuration; 23 import org.springframework.transaction.annotation.EnableTransactionManagement; 24 25 import com.alibaba.fastjson.JSONArray; 26 import com.alibaba.fastjson.JSONObject; 27 import com.cshtong.tower.service.UserService; 28 import com.cshtong.tower.util.StringUtil; 29 30 import net.sf.ehcache.Cache; 31 import net.sf.ehcache.CacheException; 32 import net.sf.ehcache.CacheManager; 33 import net.sf.ehcache.config.CacheConfiguration; 34 import net.sf.ehcache.config.DiskStoreConfiguration; 35 import net.sf.ehcache.config.FactoryConfiguration; 36 import net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory; 37 import net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory; 38 import net.sf.ehcache.distribution.RMICacheReplicatorFactory; 39 import net.sf.ehcache.store.MemoryStoreEvictionPolicy; 40 41 @EnableCaching 42 @Configuration 43 @EnableTransactionManagement 44 public class EhcacheConfig extends RMICacheReplicatorFactory{ 45 46 private static Logger logger = LoggerFactory.getLogger(EhcacheConfig.class); 47 private static final String EHCACHE_PROPERTIES = "ehcache.properties"; 48 49 /** 最大缓存数量 0 = no limit. **/ 50 private static final Integer MAX_CACHE = 0; 51 /** 缓存失效时间间隔/秒 **/ 52 private static final Integer TIME_TOLIVE_SECONDS = 24*60*60; 53 /** 缓存闲置多少秒后自动销毁 **/ 54 private static final Integer TIME_TOIDLE_SECONDS = 60*60; 55 /** 磁盘失效线程运行时间间隔/秒。 **/ 56 private static final Integer DISK_EXPIRY_Thread_INTERVAL_SENCONDS = 100; 57 58 @Resource 59 UserService userService; 60 61 /** 注入cacheManager **/ 62 @Bean 63 public org.springframework.cache.CacheManager cacheManager() { 64 org.springframework.cache.CacheManager cm = new EhCacheCacheManager(putCache()); 65 return cm; 66 } 67 68 @Bean 69 public KeyGenerator keyGenerator() { 70 return new SimpleKeyGenerator(); 71 } 72 73 @Bean 74 public CacheManager putCache(){ 75 76 String rmiUrls = initRmiURLs(); 77 78 net.sf.ehcache.config.Configuration cf = new net.sf.ehcache.config.Configuration(); 79 Properties pro = initRmiUrlsProperties(); 80 if (null!=rmiUrls) { 81 /** 临时文件目录 **/ 82 cf.diskStore(new DiskStoreConfiguration().path("java.io.tmpdir")) 83 /**成员发现 peerDiscovery 方式:manual:手动,rmiUrls:主机名+端口号+缓存名,用来接受或者发送信息的接口,不同的缓存或者机器用“|”隔开 **/ 84 .cacheManagerPeerProviderFactory(new FactoryConfiguration>() 85 .className(RMICacheManagerPeerProviderFactory.class.getName()) 86 .properties("peerDiscovery=manual,rmiUrls=" + rmiUrls) 87 ); 88 /** 本机监听程序,来发现其他主机发来的同步请求 hostName=192.168.1.112 这里默认是本机可以不配置 **/ 89 cf.cacheManagerPeerListenerFactory(new FactoryConfiguration >() 90 .className(RMICacheManagerPeerListenerFactory.class.getName()) 91 .properties("port="+ pro.getProperty("rmiPortNumber") +",socketTimeoutMillis=2000") 92 ); 93 }else{ 94 logger.debug("The rmiUrls is null!!"); 95 } 96 97 CacheManager cm = null; 98 99 try {100 cm = CacheManager.create(cf);101 } catch (UncheckedIOException e) {102 logger.debug("Fail to load config. message:{}", e.getMessage());103 }104 105 List array = null;106 try {107 array = cacheCollection();108 } catch (CacheException e) {109 logger.error("collect cache Failed");110 }111 112 for (Cache list:array) {113 cm.addCache(list);114 }115 116 return cm;117 }118 119 public static List cacheCollection(){120 String listParameters = cacheParametersCollection();121 return cacheConf(listParameters);122 }123 124 /**125 * 添加缓存的参数126 * 相关属性: 127 name : "缓存名称,cache的唯一标识(ehcache会把这个cache放到HashMap里) 128 maxElementsInMemory : 缓存最大个数,0没有限制。129 eternal="false" : 对象是否永久有效,一但设置了,timeout将不起作用。 (必须设置)130 maxEntriesLocalHeap="1000" : 堆内存中最大缓存对象数,0没有限制(必须设置)131 maxEntriesLocalDisk= "1000" : 硬盘最大缓存个数。 132 overflowToDisk="false" : 当缓存达到maxElementsInMemory值是,是否允许溢出到磁盘(必须设设置)(内存不足时,是否启用磁盘缓存。)133 diskSpoolBufferSizeMB : 这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 134 diskPersistent="false" : 磁盘缓存在JVM重新启动时是否保持(默认为false)硬盘持久化135 timeToIdleSeconds="0" : 导致元素过期的访问间隔(秒为单位),即当缓存闲置n秒后销毁。 当eternal为false时,这个属性才有效,0表示可以永远空闲,默认为0136 timeToLiveSeconds="0" : 元素在缓存里存在的时间(秒为单位),即当缓存存活n秒后销毁. 0 表示永远存在不过期137 memoryStoreEvictionPolicy="LFU" : 当达到maxElementsInMemory时,如何强制进行驱逐默认使用"最近使用(LRU)"策略,其它还有先入先出FIFO,最少使用LFU,较少使用LRU138 diskExpiryThreadIntervalSeconds :磁盘失效线程运行时间间隔,默认是120秒。139 clearOnFlush : 内存数量最大时是否清除。 140 141 cacheEventListenerFactory : 给缓存添加监听142 replicateAsynchronously=true : 异步的方式143 replicatePuts=true,replicateUpdates=true,replicateUpdatesViaCopy=true,replicateRemovals= true : 在put,update,copy,remove操作是否复制144 cationIntervalMillis=1000 : 同步时间1s145 bootstrapCacheLoaderFactory 启动是指一启动就同步数据146 * @return147 */148 public static String cacheParametersCollection(){149 150 String listParameters = "[" ;151 152 /** 排班:列表详情 **/153 listParameters += "{'cacheName':'schedule_listPatrol','maxEntriesLocalHeap':'0'}";154 155 /** APP:用户当月的排班 **/156 listParameters += "{'cacheName':'owner_schedule','maxEntriesLocalHeap':'0'}";157 158 /**考勤统计 :echarts图表相关数据**/159 listParameters += "{'cacheName':'attendance_findByOrgIds','maxEntriesLocalHeap':'0'}";160 /**考勤统计 :主界面表格相关的数据**/161 listParameters += "{'cacheName':'attendance_findByOrgIdsAndPage','maxEntriesLocalHeap':'0','timeToIdleSeconds':'0'}";162 163 /** 机构 **/164 listParameters += "{'cacheName':'org_findAll','maxEntriesLocalHeap':'0'}";165 166 /** 用户信息 key=userId **/167 listParameters += "{'cacheName':'list_userInfo','maxEntriesLocalHeap':'0'}";168 169 /** APP 勤务圈 **/170 listParameters += "{'cacheName':'app_message','maxElementsInMemory':'0','maxEntriesLocalHeap':'0'}";171 172 /** 报警,最近1km内的用户 **/173 listParameters += "{'cacheName':'rescue_users','maxElementsInMemory':'0','maxEntriesLocalHeap':'0'}";174 175 /** 报警,最近1km内的用户 **/176 listParameters += "{'cacheName':'app_message_typeName','maxElementsInMemory':'0','maxEntriesLocalHeap':'0'}";177 178 listParameters += "]";179 180 return listParameters;181 }182 183 /**184 * 添加成员发现: //主机ip+端口号/185 * @return186 */187 public static List cacheManagerPeerProviderCollection(){188 Properties pro = initRmiUrlsProperties();189 190 List ip = new ArrayList<>();191 try {192 ip.add(pro.get("machine1").toString());193 ip.add(pro.get("machine2").toString());194 } catch (Exception e) {195 logger.error("Fail to provider cacheManagerPeer. config file [{}], message:{}", EHCACHE_PROPERTIES, e.getMessage());196 }197 198 InetAddress ia;199 try {200 ia = InetAddress.getLocalHost();201 String localip = ia.getHostAddress();202 for (int i = 0; i < ip.size(); i++) {203 /** 过滤本机ip **/204 if (localip.equalsIgnoreCase(ip.get(i))) {205 ip.remove(i);206 }207 }208 } catch (UnknownHostException Host) {209 Host.printStackTrace();210 logger.error("Unknown to host Address. config file [{}], message:{}", EHCACHE_PROPERTIES, Host.getMessage());211 }212 213 List peer = new ArrayList<>();214 for (int j = 0; j < ip.size(); j++) {215 peer.add("//" + ip.get(j) + ":" + pro.getProperty("rmiPortNumber").toString());216 }217 218 return peer;219 }220 221 public static String initRmiURLs(){222 String rmiUrls = "";223 String listParameters = cacheParametersCollection();224 JSONArray array = initCacheParameters(listParameters);225 for (Iterator
properties文件:
1 #RMICacheReplicatorFactory properties 2 replicateAsynchronously=true 3 replicatePuts=true 4 replicatePutsViaCopy=false 5 replicateUpdates=true 6 replicateUpdatesViaCopy=false 7 replicateRemovals=true 8 asynchronousReplicationIntervalMillis=1000 9 asynchronousReplicationMaximumBatchSize=100010 11 #RMI URLs 12 machine1=//主机ip+端口号/13 14 #RMI port 15 rmiPortNumber=8010
到这里就注入好了缓存名为listParameters里面cacheName的所有缓存,如果后续添加或修改缓存,只需更改listParameters的相关属性,如果在集群环境,也只需在porperties文件中添加machine..配置即可。
2,基于注解方式的缓存使用:
1,为方便重用所有缓存建议在service层使用,当方法第一次执行时将返回值以key-value对象写进缓存,之后在执行该方法时,如果缓存的condition满足则直接取缓存返回,实际上方法都不会进!
2,在修改或添加方法使用@CachePut,查询方法使用@Cacheable,删除方法使用@CacheEvict,注意:缓存一定要配合使用,例如一个list方法将相应值缓存起来,如果有针对该值CUD操作时,一定要将新的返回值@CachePut,否则会出现数据脏读的情况!如果更新或修改方法的返回值与list不相同即该缓存@CacheEvict,否则会报数据映射错误!
3,注解说明:
@Cacheable (value="缓存值/SpEL/不填默认返回值", key="缓存key/SpEL") : 应用到读取数据的方法上,即可缓存的方法,如查找方法:先从缓存中读取,如果没有再调用方法获取数据,然后把数据添加到缓存中。如图:
@CachePut 应用到写数据的方法上,如新增/修改方法,调用方法时会自动把相应的数据放入缓存: 如图:
@CacheEvict: 移除数据,如图:
@Caching 组合多个Cache注解使用,如图:
注解参数说明:
value:必须指定,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称, listparamentes中的cacheName
key:通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index。我们统一采用方法的参数做唯一key,没有参数不写!
condition:有的时候我们可能并不希望缓存一个方法所有的返回结果,condition属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。如下示例表示只有当user的id为偶数时才会进行缓存
1 @Cacheable(value="users",key="#user.id",condition="#user.id%2==0")2 public User find(User user) {3 System.out.println("find user by user " + user);4 return user;5 }
3,基于程序代码方式的缓存相关使用:
当注解不能完全满足需求,或需要在程序代码中实现动态操作时,就需要对ehcache实现相关封装,从而现对缓存手动进行增删改。可以考虑写成util,我这里是以service的形式现实的封装及相关调用,仅供参考。
1 package com.cshtong.tower.service; 2 3 import java.io.UncheckedIOException; 4 import java.util.ArrayList; 5 import java.util.List; 6 7 import javax.annotation.Resource; 8 9 import org.slf4j.Logger; 10 import org.slf4j.LoggerFactory; 11 import org.springframework.stereotype.Service; 12 13 import com.alibaba.fastjson.JSONObject; 14 import com.cshtong.tower.model.MessageType; 15 import com.cshtong.tower.model.User; 16 import com.cshtong.tower.repository.MessageTypeRepository; 17 18 import net.sf.ehcache.Cache; 19 import net.sf.ehcache.CacheManager; 20 import net.sf.ehcache.Ehcache; 21 import net.sf.ehcache.Element; 22 23 @Service 24 public class EhcacheServiceImpl implements EhcacheService{ 25 26 private static Logger logger = LoggerFactory.getLogger(EhcacheServiceImpl.class); 27 28 @Resource 29 private UserService userSerivce; 30 31 @Resource 32 private MessageTypeRepository messageTypeRepository; 33 34 public static CacheManager cacheManager(){ 35 36 CacheManager cm = null; 37 try { 38 cm = CacheManager.newInstance(); 39 } catch (UncheckedIOException e) { 40 logger.error("Fail to load config, message:{}", e.getMessage()); 41 } 42 return cm; 43 } 44 45 /** 46 * key可以为空 47 */ 48 @SuppressWarnings({"deprecation" }) 49 @Override 50 public com.cshtong.tower.model.Ehcache findCache(String cacheName, String key) { 51 com.cshtong.tower.model.Ehcache eh = null; 52 if (null == key) { 53 Ehcache cache = cacheManager().getEhcache(cacheName); 54 eh = new com.cshtong.tower.model.Ehcache(); 55 eh.setCacheName(cache.getName()); 56 ListlistKey = new ArrayList<>(); 57 for (int j = 0; j < cache.getKeys().size(); j++) { 58 listKey.add(cache.getKeys().get(j).toString()); 59 } 60 eh.setKeys(listKey); 61 eh.setSize(cache.getSize()); 62 /*eh.setHitrate(cache.getStatistics().cacheHitRatio());*/ 63 eh.setDiskStoreSize(cache.getDiskStoreSize()); 64 eh.setMemoryStoreSize(cache.getMemoryStoreSize()); 65 eh.setStatus(cache.getStatus().toString()); 66 }else{ 67 Element el = cacheManager().getEhcache(cacheName).get(key); 68 if (el == null) { 69 el = cacheManager().getEhcache(cacheName).get(Integer.parseInt(key)); 70 } 71 eh = new com.cshtong.tower.model.Ehcache(); 72 eh.setKeyHit(el.getHitCount()); 73 eh.setLastUpdateTime(el.getLastUpdateTime()); 74 eh.setValues(el.getValue()); 75 } 76 return eh; 77 } 78 79 @SuppressWarnings("deprecation") 80 @Override 81 public List listAllEhcahce() { 82 List list = new ArrayList<>(); 83 String[] cache = cacheManager().getCacheNames(); 84 List cachelist = new ArrayList<>(); 85 for (int i = 0; i < cache.length; i++) { 86 Ehcache c = cacheManager().getEhcache(cache[i]); 87 cachelist.add(c); 88 } 89 for (int i = 0; i < cachelist.size(); i++) { 90 com.cshtong.tower.model.Ehcache eh = new com.cshtong.tower.model.Ehcache(); 91 eh.setCacheName(cachelist.get(i).getName()); 92 List listKey = new ArrayList<>(); 93 for (int j = 0; j < cachelist.get(i).getKeys().size(); j++) { 94 listKey.add(cachelist.get(i).getKeys().get(j).toString()); 95 } 96 eh.setKeys(listKey); 97 eh.setSize(cachelist.get(i).getSize()); 98 eh.setStatus(cachelist.get(i).getStatus().toString()); 99 eh.setMemoryStoreSize(cachelist.get(i).getMemoryStoreSize());100 eh.setDiskStoreSize(cachelist.get(i).getDiskStoreSize());101 list.add(eh);102 }103 return list;104 }105 106 /**107 * 获取缓存108 * @param cacheName109 * @param key110 * @return json字符串111 */112 @Override113 public String getCache(String cacheName, Object key) {114 logger.debug("Getting Cache that name is " + cacheName + "and key is" + key);115 Element el = cacheManager().getCache(cacheName).get(key);116 return JSONObject.toJSONString(el.getObjectValue());117 }118 119 @Override120 public Cache getCache(String name) {121 logger.debug("Getting Cache that name is " + name);122 return cacheManager().getCache(name);123 }124 125 /**126 * 获取所有的缓存127 * @param names128 * @return129 */130 @Override131 public String[] getCacheNames() {132 logger.debug("All of the Cache is " + cacheManager().getCacheNames());133 return cacheManager().getCacheNames();134 }135 136 @Override137 public void update(String cacheName, Object key, Object value) {138 try {139 remove(cacheName, key);140 put(cacheName, key, value);141 } catch (Exception e) {142 logger.debug("Fail to update the Cache,which is " + cacheManager().getCacheNames());143 }144 }145 146 @Override147 public void put(String cacheName, Object key, Object value) {148 logger.debug("add Cache is " + cacheManager().getCacheNames() + ",and key is " + key + ",and value is" + value);149 Element el = new Element(key, value);150 cacheManager().getCache(cacheName).put(el);151 }152 153 @Override154 public boolean ishasCache(String cacheName, Object key) {155 boolean rs = false;156 Element value = cacheManager().getCache(cacheName).get(key);157 if (null != value) {158 rs = true;159 }160 return rs;161 }162 163 /**164 * 根据缓存名清除缓存key value165 * @param name166 */167 @Override168 public void evictName(String name) {169 logger.debug("delete Cache that name is " + name);170 cacheManager().getCache(name).removeAll();171 }172 173 /**174 * 根据缓存名对应的key清除缓存175 */176 @Override177 public void remove(String cacheName, Object key) {178 logger.debug("Delete Cache that Name is "+ cacheName +"and key is " + key );179 Cache cache = cacheManager().getCache(cacheName); 180 cache.remove(key); 181 }182 183 /**184 * 清除当前cacheManager的所有缓存185 */186 @Override187 public void clear() {188 logger.debug("clear all cache!!");189 cacheManager().clearAll();190 }191 192 }
以上愚见,只是个人的理解,仅供参考。如有不对的地方,欢迎指正批评....