假设生产环境中有3台redis服务器,3台redis服务器不是以cluster的方式部署,而是以单节点的方式部署在各自的服务器中,3台服务器无任何交互。现在要求第一台服务器发生网路闪断或者服务器宕机,转移到第二台服务器以此类推,如果其他服务器重新提供服务,需要能够重新加入redis服务器列表中。为了实现如上要求,现在将代码和配置文件贴在下面:
2、RedisConfiguration用于将上面配置文件中的key-value注入到配置类中
4、RedisHeartbeatListener类用于监听redis心跳检测事件(RedisHeartbeatEvent),如果有事件发生,动态连接redis服务器,完成redis服务列表的更新(服务续约和剔除)
1、redis.properties配置文件,用于记录三台redis服务器的ip地址端口号,以及连接数等等,可自行添加
# redis host0
redis.host.0=192.126.4.143
redis.port.0=6379
# redis host1
redis.host.1=192.126.4.144
redis.port.1=6379
# redis host2
redis.host.2=192.126.4.145
redis.port.2=6379
redis.hosts.num=3
2、RedisConfiguration用于将上面配置文件中的key-value注入到配置类中
package com.happygo.redis.failover;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
/**
* ClassName: RedisConfig <br/>
* Description: RedisConfig <br/>
* Date: 2017/9/26 14:37 <br/>
*
* @author (1378127237@qq.com)<br/>
* @version 1.0 <br/>
*/
@Configuration
@PropertySource(value = "file:${app.config.path}/redis.properties")
public class RedisConfiguration {
/**
* The Host 0.
*/
@Value("${redis.host.0}")
private String host0;
/**
* The Host 1.
*/
@Value("${redis.host.1}")
private String host1;
/**
* The Host 2.
*/
@Value("${redis.host.2}")
private String host2;
/**
* The Port 0.
*/
@Value("${redis.port.0}")
private int port0;
/**
* The Port 1.
*/
@Value("${redis.port.1}")
private int port1;
/**
* The Port 2.
*/
@Value("${redis.port.2}")
private int port2;
/**
* The Num of redis hosts.
*/
@Value("${redis.hosts.num}")
private int numOfRedisHosts;
/**
* Getter for property 'numOfRedisHosts'.
*
* @return Value for property 'numOfRedisHosts'.
*/
public int getNumOfRedisHosts() {
return numOfRedisHosts;
}
/**
* Setter for property 'numOfRedisHosts'.
*
* @param numOfRedisHosts Value to set for property 'numOfRedisHosts'.
*/
public void setNumOfRedisHosts(int numOfRedisHosts) {
this.numOfRedisHosts = numOfRedisHosts;
}
/**
* Redis 0 connection factory jedis connection factory.
*
* @return the jedis connection factory
*/
@Primary
@Bean
public JedisConnectionFactory redis0ConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(host0);
factory.setPort(port0);
return factory;
}
/**
* Redis 0 template redis template.
*
* @return the redis template
*/
@Primary
@Bean("redis0Template")
public RedisTemplate<String, String> redis0Template() {
return new StringRedisTemplate(redis0ConnectionFactory());
}
/**
* Redis 1 connection factory jedis connection factory.
*
* @return the jedis connection factory
*/
@Bean
public JedisConnectionFactory redis1ConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(host1);
factory.setPort(port1);
return factory;
}
/**
* Redis 1 template redis template.
*
* @return the redis template
*/
@Bean("redis1Template")
public RedisTemplate<String, String> redis1Template() {
return new StringRedisTemplate(redis1ConnectionFactory());
}
/**
* Redis 2 connection factory jedis connection factory.
*
* @return the jedis connection factory
*/
@Bean
public JedisConnectionFactory redis2ConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(host2);
factory.setPort(port2);
return factory;
}
/**
* Redis 2 template redis template.
*
* @return the redis template
*/
@Bean("redis2Template")
public RedisTemplate<String, String> redis2Template() {
return new StringRedisTemplate(redis2ConnectionFactory());
}
}
3、RedisHeartbeatEvent类直接继承Spring的ApplicationEvent类,这是Spring中的应用事件类
package com.happygo.redis.failover;
import org.springframework.context.ApplicationEvent;
/**
* ClassName: RedisHeartbeatEvent <br/>
* Description: RedisHeartbeatEvent <br/>
* Date: 2017/9/29 16:43 <br/>
*
* @author (1378127237@qq.com)<br/>
* @version 1.0 <br/>
*/
public class RedisHeartbeatEvent extends ApplicationEvent {
/**
* Instantiates a new Redis heartbeat event.
*
* @param source the source
*/
public RedisHeartbeatEvent(Object source) {
super(source);
}
}
4、RedisHeartbeatListener类用于监听redis心跳检测事件(RedisHeartbeatEvent),如果有事件发生,动态连接redis服务器,完成redis服务列表的更新(服务续约和剔除)
package com.happygo.redis.failover;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.event.EventListener;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
/**
* ClassName: RedisHeartbeatListener <br/>
* Description: RedisHeartbeatListener <br/>
* Date: 2017/9/29 16:45 <br/>
*
* @author (1378127237@qq.com)<br >
* @version 1.0 <br/>
*/
@Component
@AutoConfigureAfter(RedisTemplateHolder.class)
public class RedisHeartbeatListener {
/**
* The constant targetRedisTemplateMap.
*/
private static final ConcurrentMap<String, RedisTemplate> targetRedisTemplateMap = RedisTemplateHolder
.getTargetRedisTemplateMap();
/**
* On application event.
*
* @param redisHeartbeatEvent the redis heartbeat event
*/
@EventListener
public void onApplicationEvent(RedisHeartbeatEvent redisHeartbeatEvent) {
doHeartbeatCheck();
}
/**
* Do heartbeat check.
*/
private void doHeartbeatCheck() {
RedisTemplate redisTemplate = null;
RedisConnectionFactory redisConnectionFactory = null;
RedisConnection connection = null;
boolean isRelease = true;
for (Map.Entry<String, RedisTemplate> entry : targetRedisTemplateMap.entrySet()) {
String lookupKey = entry.getKey();
redisTemplate = entry.getValue();
try {
redisConnectionFactory = redisTemplate.getConnectionFactory();
connection = RedisConnectionUtils
.getConnection(redisConnectionFactory, false);
isRelease = true;
if (lookupKey.contains("die")) {
changeRedisTemplateMap(lookupKey, redisTemplate, "die", "live");
}
} catch (Exception e) {
changeRedisTemplateMap(lookupKey, redisTemplate, "live", "die");
isRelease = false;
} finally {
if (isRelease) {
RedisConnectionUtils.releaseConnection(connection, redisConnectionFactory);
}
}
}
}
/**
* Change redis template map.
*
* @param lookupKey the lookup key
* @param redisTemplate the redis template
* @param target the target
* @param replacement the replacement
*/
private void changeRedisTemplateMap(String lookupKey, RedisTemplate redisTemplate,
String target, String replacement) {
targetRedisTemplateMap.remove(lookupKey);
String newLookupKey = lookupKey.replace(target, replacement);
targetRedisTemplateMap.putIfAbsent(newLookupKey, redisTemplate);
}
}
5、RedisHeartbeatScheduler类定时发送心跳检测事件
package com.happygo.redis.failover;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* ClassName: RedisHeartbeatScheduler <br/>
* Description: RedisHeartbeatScheduler <br/>
* Date: 2017/9/28 10:04 <br/>
*
* @version 1.0 <br/>
*/
@Component
public class RedisHeartbeatScheduler {
/**
* The constant LOGGER.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(RedisHeartbeatScheduler.class);
/**
* The Publisher.
*/
@Autowired
private ApplicationEventPublisher publisher;
/**
* Schedule.
*/
@Scheduled(cron = "*/3 * * * * ?")
public void schedule() {
LOGGER.info(">>>>>>>执行redis心跳检测开始...");
long startTime = System.currentTimeMillis();
RedisHeartbeatEvent redisHeartbeatEvent = new RedisHeartbeatEvent("redis heartbeat");
publisher.publishEvent(redisHeartbeatEvent);
LOGGER.info(">>>>>>>执行redis心跳检测完毕, waste time: " + (System.currentTimeMillis() - startTime)
+ " millisecond");
}
}
6、RedisTemplate动态获取持有类
package com.happygo.redis.failover;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* ClassName: RedisTemplateHolder <br/>
* Description: RedisTemplateHolder <br/>
* Date: 2017/9/26 17:15 <br/>
*
* @author (1378127237@qq.com)<br >
* @version 1.0 <br/>
*/
@Component
public class RedisTemplateHolder implements ApplicationContextAware {
/**
* The constant targetRedisTemplateMap.
*/
private static final ConcurrentMap<String, RedisTemplate> targetRedisTemplateMap = new ConcurrentHashMap<>();
/**
* The Redis conf.
*/
@Autowired
private RedisConfiguration redisConf;
/**
* Gets target redis template map.
*
* @return the target redis template map
*/
public static ConcurrentMap<String, RedisTemplate> getTargetRedisTemplateMap() {
return targetRedisTemplateMap;
}
/**
* Sets application context.
*
* @param applicationContext the application context
* @throws BeansException the beans exception
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
int numOfRedisHosts = redisConf.getNumOfRedisHosts();
String defaultNameOfRedisTemplate = "redis Template";
for (int i = 0; i < numOfRedisHosts; i++) {
String beanNameOfRedisTemplate = defaultNameOfRedisTemplate.replace(" ", Integer.toString(i));
RedisTemplate redisTemplate = (RedisTemplate) applicationContext.getBean(beanNameOfRedisTemplate);
beanNameOfRedisTemplate = beanNameOfRedisTemplate + ":live";
targetRedisTemplateMap.putIfAbsent(beanNameOfRedisTemplate, redisTemplate);
}
if (CollectionUtils.isEmpty(targetRedisTemplateMap)) {
throw new IllegalArgumentException("Please check your 'redis.properties' is set redis configuration");
}
}
/**
* Determine target redis template redis template.
*
* @return the redis template
*/
public RedisTemplate determineTargetRedisTemplate() {
int numOfRedisHosts = redisConf.getNumOfRedisHosts();
String defaultNameOfRedisTemplate = "redis Template:live";
RedisTemplate redisTemplate = null;
for (int i = 0; i < numOfRedisHosts; i++) {
String beanNameOfRedisTemplate = defaultNameOfRedisTemplate.replace(" ", Integer.toString(i));
redisTemplate = targetRedisTemplateMap.get(beanNameOfRedisTemplate);
if (!ObjectUtils.isEmpty(redisTemplate)) {
break;
}
}
if (ObjectUtils.isEmpty(redisTemplate)) {
throw new IllegalArgumentException("Cannot find any available redis hosts connect");
}
return redisTemplate;
}