版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/v2sking/article/details/78995310
众所周知 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"));
}
主要从配置中获取广播地址和端口等。
再看手工方式
/**
*
* @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));
}
配置需要通知的rmi地址
下面配置缓存同步管理监听工厂:
/**
*
* @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上传
代码清单:
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);
}
}
}
}