1.业务场景
当springboot项目启动时,redis中间件可能还没有启动成功,这时候springboot项目会直接挂掉,业务需要等待redis启动成功之后再进行连接redis。
2.项目结构配置
pom文件使用redisson
依赖,配置如下:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.15.0</version>
</dependency>
redis连接配置:
package com.hero.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedisConfig {
@Value("${env.redis.addr}")
private String redisAddr;
@Value("${env.redis.password}")
private String password;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
String[] nodes = redisAddr.split(",");
for (String node : nodes) {
config.useClusterServers().addNodeAddress("redis://" + node);
}
config.useClusterServers().setPassword(password);
return Redisson.create(config);
}
}
3.增加项目启动时redis校验
校验逻辑:redis连接失败则一直重试直到连接成功为止,代码如下:
3.1. 获取 Spring 应用上下文的抽象类
package com.hero.validate;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 获取 Spring 应用上下文的抽象类
*/
public abstract class AbstractValidateItem implements ApplicationContextAware {
private ConfigurableApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (applicationContext instanceof ConfigurableApplicationContext) {
this.context = (ConfigurableApplicationContext) applicationContext;
}
validate();
}
public abstract void validate();
public ConfigurableApplicationContext getContext() {
return context;
}
}
3.2 具体校验实现类
package com.hero.validate;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.ClusterNode;
import org.redisson.api.ClusterNodesGroup;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
@DependsOn(value = "springContextHolder")
public class ValidateRedis extends AbstractValidateItem {
@Value("${env.redis.addr}")
private String redisAddr;
@Value("${env.redis.password}")
private String password;
@Value("${nms.validRedis:1}")
private String isValid;
@Override
public void validate() {
log.info(">>> ValidateRedis ");
redisValid();
}
private void redisValid() {
for (int i = 1; i <= 1000000; i++) {
try {
Thread.sleep(backoff(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Boolean redisValid;
log.info(">>> redisValid , isValid: {} ", isValid);
if ("1".equals(isValid)) {
redisValid = getRedisConnection();
} else {
log.info(">>> redisValid 忽略");
redisValid = true;
}
if (redisValid) {
log.info(" check connect redis ok");
return;
} else {
log.warn(" retry connect redis addr {} fail", i);
}
} catch (Exception e) {
log.error(">>> redisValid error : {}", e);
if (i == 1) {
e.printStackTrace();
}
log.warn(" retry connect redis {} fail", i);
if (i == 1000000) {
log.error(" nms will be restarted for retry connect redis {} fail ,error:{}", i, e);
getContext().close();
}
}
}
}
public Boolean getRedisConnection() {
Config config = new Config();
String[] nodes = redisAddr.split(",");
for (String node : nodes) {
config.useClusterServers().addNodeAddress("redis://" + node);
}
config.useClusterServers().setPassword(password);
RedissonClient redissonClient = Redisson.create(config);
log.info(">>> redissonClient : {} " ,redissonClient);
if (redissonClient != null) {
log.info(">>> redissonClient is not null");
List<Boolean> nodeStates = new ArrayList<>();
ClusterNodesGroup clusterNodesGroup = redissonClient.getClusterNodesGroup();
Collection<ClusterNode> connectNodes = clusterNodesGroup.getNodes();
log.info(">>> connectNodes : {} ", connectNodes);
for (ClusterNode node : connectNodes) {
boolean ping = false;
try {
ping = node.ping(5, TimeUnit.SECONDS);
} catch (Exception e) {
log.error(">>> redisValid getRedisConnection ping error : {} ",e );
}
log.info(">>> connectNodes ,currentNode : {} , ping : {}", node, ping);
nodeStates.add(ping);
}
log.info(">>> nodeStates : {} ", nodeStates);
boolean redisPing = nodeStates.contains(true);
log.info(">>> redisPing : {} ", redisPing);
redissonClient.shutdown();
return redisPing;
}
return false;
}
public static int backoff(int n) {
if (n > 5) {
n = 5;
}
return 2000 * n;
}
}
这样,当srpingboot项目启动时会校验reids是否可以成功连接,如果失败则会一直重试直到连接成功。