众所周知 ehcache是支持通过rmi的缓存同步机制,ehcache.xml只需如下配置即可:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
monitoring="autodetect" dynamicConfig="true">
<diskStore path="java.io.tmpdir" />
<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>
</ehcache>
所有的这些属性都是配置在ehcache.xml里,包含广播地址,监听地址,缓存监听工厂等。
和集群有关的元素为:
cacheManagerPeerProviderFactory
cacheManagerPeerListenerFactory
cacheEventListenerFactory
生产环境下,一旦地址端口,每台机器都需要调整ehcache.xml配置,增加了运维工作量。
该篇提供了一个简化版的集群配置,简化ehcache.xml,将相关缓存广播监听事件工厂全部自动配置,项目只配置具体的cache元素(因项目环境cache元素基本不会改变,这里简单实现,其实可以连ehcache.xml都不需要,全部采用自动配置)
定义ClusterEhCacheManagerFactoryBean(ehcache集群缓存管理工厂bean)实现FactoryBean<CacheManager>, InitializingBean, DisposableBean 接口
主要内容如下:
注入ehClusterConfig
判断是否集群,如果是,分别配置@Autowired private ClusterEhCacheConfig ehClusterConfig;
缓存管理注册提供者工厂(configCacheManagerPeerProviderFactory)
配置缓存同步管理监听工厂(configCacheManagerPeerListenerFactory)
配置缓存事件监听工厂(configCacheEventListenerFactory)
if(this.ehClusterConfig.getEnableCluster()){ configCacheManagerPeerProviderFactory(configuration); configCacheManagerPeerListenerFactory(configuration); configCacheEventListenerFactory(configuration); }
/** * * @Title: configCacheManagerPeerProviderFactory * @Description: 配置缓存管理注册提供者工厂 * @return void 返回类型 * @param configuration */ public void configCacheManagerPeerProviderFactory(Configuration configuration) { LOG.debug("ehClusterConfig:" + ehClusterConfig.toString()); String peerDiscovery = ehClusterConfig.getPeerDiscovery(); LOG.debug("rmi方式:" + peerDiscovery); if (StringUtils.isEmpty(peerDiscovery)) { return; } if ("automatic".equals(peerDiscovery)) { configAutomaticCacheManagerPeerProviderFactory(configuration); } else if ("manual".equals(peerDiscovery)) { configManualCacheManagerPeerProviderFactory(configuration); } else { LOG.error("invalid peerDiscovery:" + peerDiscovery); return; } }
peerDiscovery有自动和人工两种方式
先看自动
主要从配置中获取广播地址和端口等。/** * * @Title: configAutomaticCacheManagerPeerProviderFactory * @Description: 配置缓存管理自动注册提供者工厂 * @return void 返回类型 * @param configuration */ private void configAutomaticCacheManagerPeerProviderFactory(Configuration configuration) { configuration.cacheManagerPeerProviderFactory(new FactoryConfiguration<FactoryConfiguration<?>>() .className(RMICacheManagerPeerProviderFactory.class.getName()) .properties("peerDiscovery=automatic,multicastGroupAddress=" + ehClusterConfig.getMulticastGroupAddress().trim() + ",multicastGroupPort=" + ehClusterConfig.getMulticastGroupPort() + ",timeToLive=32")); }
再看手工方式
配置需要通知的rmi地址/** * * @Title: configManualCacheManagerPeerProviderFactory * @Description: 配置缓存管理手工注册提供者工厂 * @return void 返回类型 * @param configuration */ private void configManualCacheManagerPeerProviderFactory(Configuration configuration) { String rmiUrls = ehClusterConfig.getRmiUrls().trim(); StringTokenizer stringTokenizer = new StringTokenizer(rmiUrls, "|"); Set<String> cacheConfigurationsKeySet = configuration.getCacheConfigurationsKeySet(); StringBuilder rmiUrlsStr = new StringBuilder(); while (stringTokenizer.hasMoreTokens()) { String rmiUrl = stringTokenizer.nextToken(); rmiUrl = rmiUrl.trim(); for (String key : cacheConfigurationsKeySet) { rmiUrlsStr.append("//").append(rmiUrl).append("/").append(key).append("|"); } } rmiUrlsStr = rmiUrlsStr.deleteCharAt(rmiUrlsStr.length() - 1); LOG.debug("last rmiUrls:" + rmiUrlsStr.toString()); configuration.cacheManagerPeerProviderFactory(new FactoryConfiguration<FactoryConfiguration<?>>() .className(RMICacheManagerPeerProviderFactory.class.getName()) .properties("peerDiscovery=manual,rmiUrls=" + rmiUrlsStr)); }
下面配置缓存同步管理监听工厂:
/** * * @Title: configCacheManagerPeerListenerFactory * @Description:配置缓存同步管理监听工厂 * @return void 返回类型 * @param configuration */ private void configCacheManagerPeerListenerFactory(Configuration configuration) { configuration.cacheManagerPeerListenerFactory(new FactoryConfiguration<FactoryConfiguration<?>>() .className(RMICacheManagerPeerListenerFactory.class.getName()) .properties("hostName=" + ehClusterConfig.getHostName().trim() + ",port=" + ehClusterConfig.getPort() + ",socketTimeoutMillis=2000")); }
接下来配置缓存时间监听工厂/** * * @Title: configCacheEventListenerFactory * @Description: 配置缓存事件监听工厂 * @return void 返回类型 * @param configuration */ private void configCacheEventListenerFactory(Configuration configuration) { Properties defaultRMICacheReplicatorFactoryProperties = PropertyUtil .parseProperties(defaultRMICacheReplicatorFactoryPropertiesName, ","); Map<String, CacheConfiguration> cacheConfigurations = configuration.getCacheConfigurations(); //先过滤掉无需缓存同步的cache Set<String> filterNoSyncCache = filterNoSyncCache(configuration.getCacheConfigurationsKeySet()); for (String key : filterNoSyncCache) { CacheConfiguration cacheConfiguration = cacheConfigurations.get(key); boolean hasRMICacheReplicatorFactory = false; @SuppressWarnings("unchecked") List<CacheEventListenerFactoryConfiguration> cacheEventListenerConfigurations = cacheConfiguration .getCacheEventListenerConfigurations(); //已经配置缓存监听情况 if (cacheEventListenerConfigurations != null && !cacheEventListenerConfigurations.isEmpty()) { for (CacheEventListenerFactoryConfiguration c1 : cacheEventListenerConfigurations) { String className = c1.getFullyQualifiedClassPath(); if (className.equals(RMICacheReplicatorFactory.class.getName())) { hasRMICacheReplicatorFactory = true; Properties parseProperties = PropertyUtil.parseProperties(c1.getProperties(), c1.getPropertySeparator()); //属性为空 设置默认 if (parseProperties == null) { c1.properties(defaultRMICacheReplicatorFactoryPropertiesName); continue; } //属性不为空 ,和默认属性合并 Enumeration<?> propertyNames = parseProperties.propertyNames(); while (propertyNames.hasMoreElements()) { String key1 = (String) propertyNames.nextElement(); String property = parseProperties.getProperty(key1); if (StringUtils.hasText(property)) { defaultRMICacheReplicatorFactoryProperties.put(key1, property); } } //重新设置合并后的属性 Enumeration<?> propertyNames1 = defaultRMICacheReplicatorFactoryProperties.propertyNames(); StringBuilder sb = new StringBuilder(); while (propertyNames1.hasMoreElements()) { String key1 = (String) propertyNames1.nextElement(); String property = defaultRMICacheReplicatorFactoryProperties.getProperty(key1); sb.append(key1).append("=").append(property).append(","); } c1.setProperties(sb.substring(0, sb.length() - 1).toString()); } } } //未配置缓存监听情况 设置默认RMICacheReplicatorFactory即属性 if (!hasRMICacheReplicatorFactory) { CacheEventListenerFactoryConfiguration cacheEventListenerFactoryConfiguration = new CacheEventListenerFactoryConfiguration(); cacheEventListenerFactoryConfiguration.className(RMICacheReplicatorFactory.class.getName()); cacheEventListenerFactoryConfiguration.properties(defaultRMICacheReplicatorFactoryPropertiesName); cacheConfiguration.addCacheEventListenerFactory(cacheEventListenerFactoryConfiguration); } } }
ehcache集群配置参考:
http://blog.csdn.net/tang06211015/article/details/52281551
后续有空会做成一个spring-boot-ehcache-cluster-starter上传
以下4个文件和ehcache.xml分别拷贝到到两个项目中,或者打jar包引入,本地启动服务,因为默认是自动配置方式,无需配置具体广播和监听地址,即达成了ehcache集群自动配置,
代码清单:
ClusterEhCacheManagerFactoryBean
package com.wisedu.zzfw.template.common.cache.cluster; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.ehcache.EhCacheManagerUtils; import org.springframework.core.io.Resource; import org.springframework.util.StringUtils; import net.sf.ehcache.CacheException; import net.sf.ehcache.CacheManager; import net.sf.ehcache.config.CacheConfiguration; import net.sf.ehcache.config.CacheConfiguration.CacheEventListenerFactoryConfiguration; import net.sf.ehcache.config.Configuration; import net.sf.ehcache.config.ConfigurationFactory; import net.sf.ehcache.config.FactoryConfiguration; import net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory; import net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory; import net.sf.ehcache.distribution.RMICacheReplicatorFactory; import net.sf.ehcache.util.PropertyUtil; /** * 扩展了EhCacheManagerFactoryBean 增加通过配置文件rmi 集群配置 * * @author luanhy * */ public class ClusterEhCacheManagerFactoryBean implements FactoryBean<CacheManager>, InitializingBean, DisposableBean { private static final Logger LOG = LoggerFactory.getLogger(ClusterEhCacheManagerFactoryBean.class); private Resource configLocation; private String cacheManagerName; private boolean acceptExisting = false; private boolean shared = false; private CacheManager cacheManager; private boolean locallyManaged = true; @Autowired private ClusterEhCacheConfig ehClusterConfig; private String defaultRMICacheReplicatorFactoryPropertiesName = "replicatePuts=true,replicateUpdates=true,replicateRemovals=true,replicateAsynchronously=true,replicatePutsViaCopy=true,replicateUpdatesViaCopy=true,asynchronousReplicationIntervalMillis=true"; /** * Set the location of the EhCache config file. A typical value is * "/WEB-INF/ehcache.xml". * <p> * Default is "ehcache.xml" in the root of the class path, or if not found, * "ehcache-failsafe.xml" in the EhCache jar (default EhCache * initialization). * * @see net.sf.ehcache.CacheManager#create(java.io.InputStream) * @see net.sf.ehcache.CacheManager#CacheManager(java.io.InputStream) */ public void setConfigLocation(Resource configLocation) { this.configLocation = configLocation; } /** * Set the name of the EhCache CacheManager (if a specific name is desired). * * @see net.sf.ehcache.CacheManager#setName(String) */ public void setCacheManagerName(String cacheManagerName) { this.cacheManagerName = cacheManagerName; } /** * Set whether an existing EhCache CacheManager of the same name will be * accepted for this EhCacheManagerFactoryBean setup. Default is "false". * <p> * Typically used in combination with {@link #setCacheManagerName * "cacheManagerName"} but will simply work with the default CacheManager * name if none specified. All references to the same CacheManager name (or * the same default) in the same ClassLoader space will share the specified * CacheManager then. * * @see #setCacheManagerName #see #setShared * @see net.sf.ehcache.CacheManager#getCacheManager(String) * @see net.sf.ehcache.CacheManager#CacheManager() */ public void setAcceptExisting(boolean acceptExisting) { this.acceptExisting = acceptExisting; } /** * Set whether the EhCache CacheManager should be shared (as a singleton at * the ClassLoader level) or independent (typically local within the * application). Default is "false", creating an independent local instance. * <p> * <b>NOTE:</b> This feature allows for sharing this * EhCacheManagerFactoryBean's CacheManager with any code calling * <code>CacheManager.create()</code> in the same ClassLoader space, with no * need to agree on a specific CacheManager name. However, it only supports * a single EhCacheManagerFactoryBean involved which will control the * lifecycle of the underlying CacheManager (in particular, its shutdown). * <p> * This flag overrides {@link #setAcceptExisting "acceptExisting"} if both * are set, since it indicates the 'stronger' mode of sharing. * * @see #setCacheManagerName * @see #setAcceptExisting * @see net.sf.ehcache.CacheManager#create() * @see net.sf.ehcache.CacheManager#CacheManager() */ public void setShared(boolean shared) { this.shared = shared; } @Override public void afterPropertiesSet() throws CacheException { LOG.info("Initializing EhCache CacheManager"); Configuration configuration = this.configLocation != null ? EhCacheManagerUtils.parseConfiguration(this.configLocation) : ConfigurationFactory.parseConfiguration(); if (this.cacheManagerName != null) { configuration.setName(this.cacheManagerName); } if(this.ehClusterConfig.getEnableCluster()){ configCacheManagerPeerProviderFactory(configuration); configCacheManagerPeerListenerFactory(configuration); configCacheEventListenerFactory(configuration); } if (this.shared) { // Old-school EhCache singleton sharing... // No way to find out whether we actually created a new CacheManager // or just received an existing singleton reference. this.cacheManager = CacheManager.create(configuration); } else if (this.acceptExisting) { // EhCache 2.5+: Reusing an existing CacheManager of the same name. // Basically the same code as in CacheManager.getInstance(String), // just storing whether we're dealing with an existing instance. synchronized (CacheManager.class) { this.cacheManager = CacheManager.getCacheManager(this.cacheManagerName); if (this.cacheManager == null) { this.cacheManager = new CacheManager(configuration); } else { this.locallyManaged = false; } } } else { // Throwing an exception if a CacheManager of the same name exists // already... this.cacheManager = new CacheManager(configuration); } } /** * * @Title: configCacheManagerPeerProviderFactory * @Description: 配置缓存管理注册提供者工厂 * @return void 返回类型 * @param configuration */ public void configCacheManagerPeerProviderFactory(Configuration configuration) { LOG.debug("ehClusterConfig:" + ehClusterConfig.toString()); String peerDiscovery = ehClusterConfig.getPeerDiscovery(); LOG.debug("rmi方式:" + peerDiscovery); if (StringUtils.isEmpty(peerDiscovery)) { return; } if ("automatic".equals(peerDiscovery)) { configAutomaticCacheManagerPeerProviderFactory(configuration); } else if ("manual".equals(peerDiscovery)) { configManualCacheManagerPeerProviderFactory(configuration); } else { LOG.error("invalid peerDiscovery:" + peerDiscovery); return; } } /** * * @Title: configManualCacheManagerPeerProviderFactory * @Description: 配置缓存管理手工注册提供者工厂 * @return void 返回类型 * @param configuration */ private void configManualCacheManagerPeerProviderFactory(Configuration configuration) { String rmiUrls = ehClusterConfig.getRmiUrls().trim(); StringTokenizer stringTokenizer = new StringTokenizer(rmiUrls, "|"); Set<String> cacheConfigurationsKeySet = configuration.getCacheConfigurationsKeySet(); StringBuilder rmiUrlsStr = new StringBuilder(); while (stringTokenizer.hasMoreTokens()) { String rmiUrl = stringTokenizer.nextToken(); rmiUrl = rmiUrl.trim(); for (String key : cacheConfigurationsKeySet) { rmiUrlsStr.append("//").append(rmiUrl).append("/").append(key).append("|"); } } rmiUrlsStr = rmiUrlsStr.deleteCharAt(rmiUrlsStr.length() - 1); LOG.debug("last rmiUrls:" + rmiUrlsStr.toString()); configuration.cacheManagerPeerProviderFactory(new FactoryConfiguration<FactoryConfiguration<?>>() .className(RMICacheManagerPeerProviderFactory.class.getName()) .properties("peerDiscovery=manual,rmiUrls=" + rmiUrlsStr)); } /** * * @Title: configAutomaticCacheManagerPeerProviderFactory * @Description: 配置缓存管理自动注册提供者工厂 * @return void 返回类型 * @param configuration */ private void configAutomaticCacheManagerPeerProviderFactory(Configuration configuration) { configuration.cacheManagerPeerProviderFactory(new FactoryConfiguration<FactoryConfiguration<?>>() .className(RMICacheManagerPeerProviderFactory.class.getName()) .properties("peerDiscovery=automatic,multicastGroupAddress=" + ehClusterConfig.getMulticastGroupAddress().trim() + ",multicastGroupPort=" + ehClusterConfig.getMulticastGroupPort() + ",timeToLive=32")); } /** * * @Title: configCacheManagerPeerListenerFactory * @Description:配置缓存同步管理监听工厂 * @return void 返回类型 * @param configuration */ private void configCacheManagerPeerListenerFactory(Configuration configuration) { configuration.cacheManagerPeerListenerFactory(new FactoryConfiguration<FactoryConfiguration<?>>() .className(RMICacheManagerPeerListenerFactory.class.getName()) .properties("hostName=" + ehClusterConfig.getHostName().trim() + ",port=" + ehClusterConfig.getPort() + ",socketTimeoutMillis=2000")); } /** * * @Title: configCacheEventListenerFactory * @Description: 配置缓存监听工厂 * @return void 返回类型 * @param configuration */ private void configCacheEventListenerFactory(Configuration configuration) { Properties defaultRMICacheReplicatorFactoryProperties = PropertyUtil .parseProperties(defaultRMICacheReplicatorFactoryPropertiesName, ","); Map<String, CacheConfiguration> cacheConfigurations = configuration.getCacheConfigurations(); Set<String> filterNoSyncCache = filterNoSyncCache(configuration.getCacheConfigurationsKeySet()); for (String key : filterNoSyncCache) { CacheConfiguration cacheConfiguration = cacheConfigurations.get(key); boolean hasRMICacheReplicatorFactory = false; @SuppressWarnings("unchecked") List<CacheEventListenerFactoryConfiguration> cacheEventListenerConfigurations = cacheConfiguration .getCacheEventListenerConfigurations(); //已经配置缓存监听情况 if (cacheEventListenerConfigurations != null && !cacheEventListenerConfigurations.isEmpty()) { for (CacheEventListenerFactoryConfiguration c1 : cacheEventListenerConfigurations) { String className = c1.getFullyQualifiedClassPath(); if (className.equals(RMICacheReplicatorFactory.class.getName())) { hasRMICacheReplicatorFactory = true; Properties parseProperties = PropertyUtil.parseProperties(c1.getProperties(), c1.getPropertySeparator()); //属性为空 设置默认 if (parseProperties == null) { c1.properties(defaultRMICacheReplicatorFactoryPropertiesName); continue; } //属性不为空 ,和默认属性合并 Enumeration<?> propertyNames = parseProperties.propertyNames(); while (propertyNames.hasMoreElements()) { String key1 = (String) propertyNames.nextElement(); String property = parseProperties.getProperty(key1); if (StringUtils.hasText(property)) { defaultRMICacheReplicatorFactoryProperties.put(key1, property); } } //重新设置合并后的属性 Enumeration<?> propertyNames1 = defaultRMICacheReplicatorFactoryProperties.propertyNames(); StringBuilder sb = new StringBuilder(); while (propertyNames1.hasMoreElements()) { String key1 = (String) propertyNames1.nextElement(); String property = defaultRMICacheReplicatorFactoryProperties.getProperty(key1); sb.append(key1).append("=").append(property).append(","); } c1.setProperties(sb.substring(0, sb.length() - 1).toString()); } } } //未配置缓存监听情况 设置默认RMICacheReplicatorFactory即属性 if (!hasRMICacheReplicatorFactory) { CacheEventListenerFactoryConfiguration cacheEventListenerFactoryConfiguration = new CacheEventListenerFactoryConfiguration(); cacheEventListenerFactoryConfiguration.className(RMICacheReplicatorFactory.class.getName()); cacheEventListenerFactoryConfiguration.properties(defaultRMICacheReplicatorFactoryPropertiesName); cacheConfiguration.addCacheEventListenerFactory(cacheEventListenerFactoryConfiguration); } } } /** * * @Title: filterNoSyncCache * @Description: 过滤掉不同步的缓存key * @returnSet<String> 返回类型 过滤后的缓存配置key * @param cacheConfigurationsKeySet * @return */ private Set<String> filterNoSyncCache(Set<String> cacheConfigurationsKeySet){ Set<String> filteredCacheConfigurationKeySet = new HashSet<String>(); filteredCacheConfigurationKeySet.addAll(cacheConfigurationsKeySet); String unSyncCacheName = ehClusterConfig.getUnSyncCacheName(); StringTokenizer unSyncCacheNameTokenizer = new StringTokenizer(unSyncCacheName, "|"); while(unSyncCacheNameTokenizer.hasMoreTokens()){ String nextToken = unSyncCacheNameTokenizer.nextToken(); for (String key : cacheConfigurationsKeySet) { if(key.equals(nextToken)){ filteredCacheConfigurationKeySet.remove(nextToken); continue; } } } return filteredCacheConfigurationKeySet; } @Override public CacheManager getObject() { return this.cacheManager; } @Override public Class<? extends CacheManager> getObjectType() { return this.cacheManager != null ? this.cacheManager.getClass() : CacheManager.class; } @Override public boolean isSingleton() { return true; } @Override public void destroy() { if (this.locallyManaged) { LOG.info("Shutting down EhCache CacheManager"); this.cacheManager.shutdown(); } } public ClusterEhCacheConfig getEhClusterConfig() { return ehClusterConfig; } public void setEhClusterConfig(ClusterEhCacheConfig ehClusterConfig) { this.ehClusterConfig = ehClusterConfig; } }
ClusterEhCacheConfig 集群配置,从配置文件获取属性,默认自动方式
package com.wisedu.zzfw.template.common.cache.cluster; import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; /** * @ClassName: ClusterEhCacheConfig * @Description: ehcache集群配置 * @author luanhy * @date 2017年8月19日 下午3:24:55 * @Copyright: Copyright (c) 2017 wisedu */ @Component public class ClusterEhCacheConfig { /** * 是否启用集群 */ @Value("${ehcache.enableCluster:true}") private boolean enableCluster; /** * 无需同步的缓存名称 |分隔 */ @Value("${ehcache.unSyncCacheName:noCache|printOrderCache}") private String unSyncCacheName; /** * 缓存提供者rmi通信方式 自动和手动 */ @Value("${ehcache.provider.peerDiscovery:automatic}") private String peerDiscovery; /** * 缓存提供者广播组地址 */ @Value("${ehcache.provider.automatic.multicastGroupAddress:224.1.1.1}") private String multicastGroupAddress; /** * 缓存提供者广播组端口 */ @Value("${ehcache.provider.automatic.multicastGroupPort:41000}") private int multicastGroupPort; /** * 缓存提供者需要通知的rmi地址 */ @Value("${ehcache.provider.manual.rmiUrls:127.0.0.1:40001}") private String rmiUrls; /** * 缓存监听者端口是否随机 (自动配置时生效) */ @Value("${ehcache.listener.randomPort:true}") private boolean randomPort; /** * 缓存监听者端口地址 */ @Value("${ehcache.listener.hostName:127.0.0.1}") private String hostName; /** * 缓存监听者默认端口 */ @Value("${ehcache.listener.port:40002}") private int port; @PostConstruct public void init(){ if(randomPort){ // 41000 默认广播随机端口号 不能随机 PortUtil p = new PortUtil(); // 40000 -40999监听随机端口号 port = p.getUnAvailableRandomPort(40000, 40999); } } public String getPeerDiscovery() { return peerDiscovery; } public void setPeerDiscovery(String peerDiscovery) { this.peerDiscovery = peerDiscovery; } public String getMulticastGroupAddress() { return multicastGroupAddress; } public void setMulticastGroupAddress(String multicastGroupAddress) { this.multicastGroupAddress = multicastGroupAddress; } public int getMulticastGroupPort() { return multicastGroupPort; } public void setMulticastGroupPort(int multicastGroupPort) { this.multicastGroupPort = multicastGroupPort; } public String getRmiUrls() { return rmiUrls; } public void setRmiUrls(String rmiUrls) { this.rmiUrls = rmiUrls; } public String getHostName() { return hostName; } public void setHostName(String hostName) { this.hostName = hostName; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public boolean getRandomPort() { return randomPort; } public void setRandomPort(boolean randomPort) { this.randomPort = randomPort; } public boolean getEnableCluster() { return enableCluster; } public void setEnableCluster(boolean enableCluster) { this.enableCluster = enableCluster; } public String getUnSyncCacheName() { return unSyncCacheName; } public void setUnSyncCacheName(String unSyncCacheName) { this.unSyncCacheName = unSyncCacheName; } @Override public String toString() { return "EhClusterConfig [enableCluster=" + enableCluster + ", unSyncCacheName=" + unSyncCacheName + ",randomPort=" + randomPort + ", peerDiscovery=" + peerDiscovery + ", multicastGroupAddress=" + multicastGroupAddress + ", multicastGroupPort=" + multicastGroupPort + ", rmiUrls=" + rmiUrls + ", hostName=" + hostName + ", port=" + port + "]"; } }
工具类PortUtil 自动模式下获取空闲端口package com.wisedu.zzfw.template.common.cache.cluster; import java.util.Random; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @ClassName: PortUtil * @Description: 端口工具类 * @author luanhy * @date 2017年8月19日 下午3:30:18 * @Copyright: Copyright (c) 2017 wisedu */ public class PortUtil { private static final Logger LOG = LoggerFactory.getLogger(PortUtil.class); private int maxRandomCount = 1000; private int randomCount = 0; public void resetCount(){ randomCount = 0; } /** * * @Title: isPortAvailable * @Description: 端口是否未被用 * @param port * @return */ public boolean isPortAvailable(int port) { try { String[] commond = new String[2]; commond[0] = "netstat"; String encoding ="gbk"; SystemCommandUtil systemCommandUtil = new SystemCommandUtil(); if (systemCommandUtil.isWindows()) { commond[1] = "-aon"; } else { commond[1] = "-anp"; encoding = "utf-8"; } String ret = systemCommandUtil.excuteCmdMultiThread(commond, encoding); boolean matches = Pattern.compile("(.+)("+port+"\\s+)(.*)").matcher(ret).find(); return !matches; } catch (Exception e) { LOG.error("",e); return false; } } /** * * @Title: getRandomPort * @Description: 获取随机端口号 * @param minPort * @param maxPort * @return */ private int getRandomPort(int minPort, int maxPort) { Random random = new Random(); int s = random.nextInt(maxPort) % (maxPort - minPort + 1) + minPort; return s; } /** * * @Title: getUnAvailablePort * @Description:获取未被占用的随机端口号 * @param minPort * @param maxPort * @return */ public int getUnAvailableRandomPort(int minPort, int maxPort) { if ((++randomCount) > maxRandomCount) { throw new RuntimeException("无法从" + minPort + "到" + maxPort + "绑定ehcache rmi同步端口号,请检查端口占用情况"); } int randomPort = getRandomPort(minPort, maxPort); if (!isPortAvailable(randomPort)) { return getUnAvailableRandomPort(minPort, maxPort); } return randomPort; } }
工具类 用于调用命令行获取可用端口package com.wisedu.zzfw.template.common.cache.cluster; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; /** * @ClassName: SystemCommandUtil * @Description: 命令工具 * @author luanhy * @date 2017年8月19日 下午3:32:27 * @Copyright: Copyright (c) 2017 wisedu */ public class SystemCommandUtil { private static final Logger LOG = LoggerFactory.getLogger(SystemCommandUtil.class); public boolean isWindows() { String osName = System.getProperty("os.name"); return osName.indexOf("Windows") != -1; } /** 执行外部程序,并获取标准输出 */ public String excuteCmdMultiThread(String[] cmd, String encoding) { Process p = null; try { p = Runtime.getRuntime().exec(cmd); /* 为"错误输出流"单独开一个线程读取之,否则会造成标准输出流的阻塞 */ Thread t = new Thread(new InputStreamRunnable(p.getErrorStream(), "ErrorStream")); t.start(); /* "标准输出流"就在当前方法中读取 */ String encodingStr = StringUtils.isEmpty(encoding) ? "gbk" : encoding; String string = IOUtils.toString(p.getInputStream(), encodingStr); return string; } catch (Exception e) { LOG.error("执行外部程序,并获取标准输出异常", e); } finally { p.destroy(); } return null; } /** 读取InputStream的线程 */ class InputStreamRunnable implements Runnable { BufferedReader bReader = null; String type = null; public InputStreamRunnable(InputStream is, String typeStr) { try { bReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(is), "UTF-8")); type = typeStr; } catch (Exception ex) { LOG.error("读取InputStream的线程异常", ex); } } public void run() { String line = null; try { while ((line = bReader.readLine()) != null) { LOG.error(line); } bReader.close(); } catch (Exception ex) { LOG.error("", ex); } } } }