会问问题的小明:如何通过监听 Nacos 配置变化实现动态切换 Redis 连接池?
AI大佬: 废话少说,上操作
以下是一个示例代码,可以通过监听 Nacos 配置变化实现动态切换 Redis 连接池:
示例代码
@EnableDiscoveryClient
@SpringBootApplication
public class RedisClusterDemoApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisClusterDemoApplication.class);
private static final String GROUP_ID = "DEFAULT_GROUP";
private static final String REDIS_CONFIG_DATA_ID = "redis-config";
private static final String REDIS_SENTINEL_CONFIG_DATA_ID = "redis-sentinel-config";
private static final String REDIS_CLUSTER_CONFIG_DATA_ID = "redis-cluster-config";
// 通过 NacosConfigListener 监听配置变化并重新初始化连接池
@Value("${spring.cloud.nacos.config.server-addr}")
private String nacosServerAddr;
@Value("${spring.cloud.nacos.config.namespace}")
private String nacosNamespace;
@Value("${spring.cloud.nacos.config.username}")
private String nacosUsername;
@Value("${spring.cloud.nacos.config.password}")
private String nacosPassword;
@Value("${spring.cloud.nacos.config.prefix}")
private String nacosConfigPrefix;
private RedisConnectionFactory redisConnectionFactory;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Autowired
private RedisConnectionFactory redisSentinelConnectionFactory;
@Autowired
private RedisConnectionFactory redisClusterConnectionFactory;
public static void main(String[] args) {
SpringApplication.run(RedisClusterDemoApplication.class, args);
}
/**
* 初始化 Redis 连接池,并监听 Nacos 上的配置变化
*/
@Bean
public RedisConnectionFactory redisConnectionFactory() {
NacosConfigListener nacosConfigListener = null;
try {
nacosConfigListener = new NacosConfigListener(nacosServerAddr, nacosNamespace, nacosUsername, nacosPassword,
nacosConfigPrefix, GROUP_ID, REDIS_CONFIG_DATA_ID, REDIS_SENTINEL_CONFIG_DATA_ID,
REDIS_CLUSTER_CONFIG_DATA_ID);
nacosConfigListener.init();
String config = nacosConfigListener.getConfig(REDIS_CONFIG_DATA_ID);
RedisConfig redisConfig = OBJECT_MAPPER.readValue(config, RedisConfig.class);
RedisSentinelConfig redisSentinelConfig = null;
RedisClusterConfig redisClusterConfig = null;
if (redisConfig.isSentinel()) {
String sentinelConfig = nacosConfigListener.getConfig(REDIS_SENTINEL_CONFIG_DATA_ID);
redisSentinelConfig = OBJECT_MAPPER.readValue(sentinelConfig, RedisSentinelConfig.class);
} else if (redisConfig.isCluster()) {
String clusterConfig = nacosConfigListener.getConfig(REDIS_CLUSTER_CONFIG_DATA_ID);
redisClusterConfig = OBJECT_MAPPER.readValue(clusterConfig, RedisClusterConfig.class);
}
redisConnectionFactory = createRedisConnectionFactory(redisConfig, redisSentinelConfig, redisClusterConfig);
// 注册配置变化监听器
nacosConfigListener.addListener(REDIS_CONFIG_DATA_ID, new ConfigChangeListener() {
@Override
public void receiveConfigInfo(String configInfo) {
LOGGER.info("redis config changed: {}", configInfo);
try {
RedisConfig newRedisConfig = OBJECT_MAPPER.readValue(configInfo, RedisConfig.class);
RedisSentinelConfig newRedisSentinelConfig = null;
RedisClusterConfig newRedisClusterConfig = null;
if (newRedisConfig.isSentinel()) {
String sentinelConfigInfo = nacosConfigListener.getConfig(REDIS_SENTINEL_CONFIG_DATA_ID);
newRedisSentinelConfig = OBJECT_MAPPER.readValue(sentinelConfigInfo, RedisSentinelConfig.class);
} else if (newRedisConfig.isCluster()) {
String clusterConfigInfo = nacosConfigListener.getConfig(REDIS_CLUSTER_CONFIG_DATA_ID);
newRedisClusterConfig = OBJECT_MAPPER.readValue(clusterConfigInfo, RedisClusterConfig.class);
}
RedisConnectionFactory newRedisConnectionFactory = createRedisConnectionFactory(newRedisConfig, newRedisSentinelConfig, newRedisClusterConfig);
redisConnectionFactory = newRedisConnectionFactory;
} catch (JsonProcessingException e) {
LOGGER.error("failed to parse redis config", e);
}
}
});
if (redisConfig.isSentinel()) {
nacosConfigListener.addListener(REDIS_SENTINEL_CONFIG_DATA_ID, new ConfigChangeListener() {
@Override
public void receiveConfigInfo(String configInfo) {
LOGGER.info("redis sentinel config changed: {}", configInfo);
try {
RedisSentinelConfig newRedisSentinelConfig = OBJECT_MAPPER.readValue(configInfo, RedisSentinelConfig.class);
RedisConnectionFactory newRedisConnectionFactory = createRedisConnectionFactory(redisConfig, newRedisSentinelConfig, null);
redisConnectionFactory = newRedisConnectionFactory;
} catch (JsonProcessingException e) {
LOGGER.error("failed to parse redis sentinel config", e);
}
}
});
} else if (redisConfig.isCluster()) {
nacosConfigListener.addListener(REDIS_CLUSTER_CONFIG_DATA_ID, new ConfigChangeListener() {
@Override
public void receiveConfigInfo(String configInfo) {
LOGGER.info("redis cluster config changed: {}", configInfo);
try {
RedisClusterConfig newRedisClusterConfig = OBJECT_MAPPER.readValue(configInfo, RedisClusterConfig.class);
RedisConnectionFactory newRedisConnectionFactory = createRedisConnectionFactory(redisConfig, null, newRedisClusterConfig);
redisConnectionFactory = newRedisConnectionFactory;
} catch (JsonProcessingException e) {
LOGGER.error("failed to parse redis cluster config", e);
}
}
});
}
} catch (Exception e) {
LOGGER.error("failed to init nacos config listener", e);
}
return redisConnectionFactory;
}
// 创建 Redis 连接工厂
private RedisConnectionFactory createRedisConnectionFactory(RedisConfig redisConfig,
RedisSentinelConfig redisSentinelConfig,
RedisClusterConfig redisClusterConfig) {
if (redisConfig.isSentinel()) {
RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration();
sentinelConfiguration.setSentinelPassword(redisSentinelConfig.getSentinelPassword());
sentinelConfiguration.setMaster(redisSentinelConfig.getMaster());
sentinelConfiguration.setSentinel(redisSentinelConfig.getSentinels());
sentinelConfiguration.setDatabase(redisConfig.getDatabase());
sentinelConfiguration.setPassword(redisConfig.getPassword());
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(sentinelConfiguration);
jedisConnectionFactory.getPoolConfig().setMaxIdle(redisConfig.getMaxIdle());
jedisConnectionFactory.getPoolConfig().setMaxTotal(redisConfig.getMaxTotal());
jedisConnectionFactory.getPoolConfig().setMinIdle(redisConfig.getMinIdle());
jedisConnectionFactory.setHostName(redisConfig.getHostname());
jedisConnectionFactory.setPort(redisConfig.getPort());
jedisConnectionFactory.setTimeout(redisConfig.getTimeout());
jedisConnectionFactory.setUsePool(true);
return jedisConnectionFactory;
} else if (redisConfig.isCluster()) {
RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration(redisClusterConfig.getNodes());
clusterConfiguration.setMaxRedirects(redisClusterConfig.getMaxRedirects());
clusterConfiguration.setClusterPassword(redisConfig.getPassword());
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(clusterConfiguration);
jedisConnectionFactory.getPoolConfig().setMaxIdle(redisConfig.getMaxIdle());
jedisConnectionFactory.getPoolConfig().setMaxTotal(redisConfig.getMaxTotal());
jedisConnectionFactory.getPoolConfig().setMinIdle(redisConfig.getMinIdle());
jedisConnectionFactory.setHostName(redisConfig.getHostname());
jedisConnectionFactory.setPort(redisConfig.getPort());
jedisConnectionFactory.setTimeout(redisConfig.getTimeout());
jedisConnectionFactory.setUsePool(true);
return jedisConnectionFactory;
} else {
RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration();
standaloneConfiguration.setHostName(redisConfig.getHostname());
standaloneConfiguration.setPort(redisConfig.getPort());
standaloneConfiguration.setDatabase(redisConfig.getDatabase());
standaloneConfiguration.setPassword(redisConfig.getPassword());
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(standaloneConfiguration);
jedisConnectionFactory.getPoolConfig().setMaxIdle(redisConfig.getMaxIdle());
jedisConnectionFactory.getPoolConfig().setMaxTotal(redisConfig.getMaxTotal());
jedisConnectionFactory.getPoolConfig().setMinIdle(redisConfig.getMinIdle());
jedisConnectionFactory.setTimeout(redisConfig.getTimeout());
jedisConnectionFactory.setUsePool(true);
return jedisConnectionFactory;
}
}
// 配置 RedisTemplate
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
// 配置 RedissonClient
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
if (redisConnectionFactory instanceof JedisConnectionFactory) {
JedisConnectionFactory jedisConnectionFactory = (JedisConnectionFactory) redisConnectionFactory;
String sentinels = jedisConnectionFactory.getSentinelConfiguration().getSentinels().stream()
.map(s -> s.getHost() + ":" + s.getPort())
.collect(Collectors.joining(","));
config.useSentinelServers()
.setMasterName(jedisConnectionFactory.getSentinelConfiguration().getMaster())
.setDatabase(jedisConnectionFactory.getDatabase())
.setPassword(jedisConnectionFactory.getPassword())
.addSentinelAddress(sentinels.split(","));
} else if (redisConnectionFactory instanceof RedisClusterConnection) {
RedisClusterConnection redisClusterConnection = (RedisClusterConnection) redisConnectionFactory;
String nodes = redisClusterConnection
.clusterGetNodes()
.stream()
.map(RedisClusterNode::getUriAsString)
.collect(Collectors.joining(","));
config.useClusterServers()
.setMasterConnectionPoolSize(10)
.setSlaveConnectionPoolSize(10)
.addNodeAddress(nodes.split(","));
} else {
RedisStandaloneConfiguration standaloneConfiguration = (RedisStandaloneConfiguration) redisConnectionFactory
.getConnectionConfig();
config.useSingleServer()
.setAddress("redis://" + standaloneConfiguration.getHostName() + ":" + standaloneConfiguration.getPort())
.setDatabase(standaloneConfiguration.getDatabase())
.setPassword(standaloneConfiguration.getPassword());
}
return Redisson.create(config);
}
// 配置 RedissonReactiveClient
@Bean
public RedissonReactiveClient redissonReactiveClient() {
Config config = new Config();
if (redisConnectionFactory instanceof JedisConnectionFactory) {
JedisConnectionFactory jedisConnectionFactory = (JedisConnectionFactory) redisConnectionFactory;
String sentinels = jedisConnectionFactory.getSentinelConfiguration().getSentinels().stream()
.map(s -> s.getHost() + ":" + s.getPort())
.collect(Collectors.joining(","));
config.useSentinelServers()
.setMasterName(jedisConnectionFactory.getSentinelConfiguration().getMaster())
.setDatabase(jedisConnectionFactory.getDatabase())
.setPassword(jedisConnectionFactory.getPassword())
.addSentinelAddress(sentinels.split(","));
} else if (redisConnectionFactory instanceof RedisClusterConnection) {
RedisClusterConnection redisClusterConnection = (RedisClusterConnection) redisConnectionFactory;
String nodes = redisClusterConnection
.clusterGetNodes()
.stream()
.map(RedisClusterNode::getUriAsString)
.collect(Collectors.joining(","));
config.useClusterServers()
.setMasterConnectionPoolSize(10)
.setSlaveConnectionPoolSize(10)
.addNodeAddress(nodes.split(","));
} else {
RedisStandaloneConfiguration standaloneConfiguration = (RedisStandaloneConfiguration) redisConnectionFactory
.getConnectionConfig();
config.useSingleServer()
.setAddress("redis://" + standaloneConfiguration.getHostName() + ":" + standaloneConfiguration.getPort())
.setDatabase(standaloneConfiguration.getDatabase())
.setPassword(standaloneConfiguration.getPassword());
}
return Redisson.createReactive(config);
}
// 配置 RedissonRxClient
@Bean
public RedissonRxClient redissonRxClient() {
Config config = new Config();
if (redisConnectionFactory instanceof JedisConnectionFactory) {
JedisConnectionFactory jedisConnectionFactory = (JedisConnectionFactory) redisConnectionFactory;
String sentinels = jedisConnectionFactory.getSentinelConfiguration().getSentinels().stream()
.map(s -> s.getHost() + ":" + s.getPort())
.collect(Collectors.joining(","));
config.useSentinelServers()
.setMasterName(jedisConnectionFactory.getSentinelConfiguration().getMaster())
.setDatabase(jedisConnectionFactory.getDatabase())
.setPassword(jedisConnectionFactory.getPassword())
.addSentinelAddress(sentinels.split(","));
} else if (redisConnectionFactory instanceof RedisClusterConnection) {
RedisClusterConnection redisClusterConnection = (RedisClusterConnection) redisConnectionFactory;
String nodes = redisClusterConnection
.clusterGetNodes()
.stream()
.map(RedisClusterNode::getUriAsString)
.collect(Collectors.joining(","));
config.useClusterServers()
.setMasterConnectionPoolSize(10)
.setSlaveConnectionPoolSize(10)
.addNodeAddress(nodes.split(","));
} else {
RedisStandaloneConfiguration standaloneConfiguration = (RedisStandaloneConfiguration) redisConnectionFactory
.getConnectionConfig();
config.useSingleServer()
.setAddress("redis://" + standaloneConfiguration.getHostName() + ":" + standaloneConfiguration.getPort())
.setDatabase(standaloneConfiguration.getDatabase())
.setPassword(standaloneConfiguration.getPassword());
}
return Redisson.createRx(config);
}
}
分析一下
上面的代码中,我们实现了以下功能:
- 通过 Nacos 配置中心获取 Redis 连接池的配置信息,包括 Redis 的连接方式(standalone、sentinel、cluster)、节点地址、密码、数据库等。
- 根据配置信息创建对应的 Redis 连接池,包括 JedisConnectionFactory 和 Redisson 的连接工厂,使用 RedisTemplate、RedissonClient、RedissonReactiveClient 和 RedissonRxClient 进行 Redis 的访问。
- 监听 Nacos 配置变化,当 Redis 配置信息发生变化时重新初始化连接池。
此外,我们还需要实现 NacosConfigListener 来监听 Nacos 上的配置变化,以下是一个简单的实现:
public class NacosConfigListener {
private static final Logger LOGGER = LoggerFactory.getLogger(NacosConfigListener.class);
private ConfigService configService;
private String prefix;
private String groupId;
private String redisConfigDataId;
private String redisSentinelConfigDataId;
private String redisClusterConfigDataId;
private Map<String, ConfigChangeListener> listeners = new ConcurrentHashMap<>();
public NacosConfigListener(String serverAddr, String namespace, String username, String password, String prefix, String groupId, String redisConfigDataId, String redisSentinelConfigDataId, String redisClusterConfigDataId) throws NacosException {
this.prefix = prefix;
this.groupId = groupId;
this.redisConfigDataId = redisConfigDataId;
this.redisSentinelConfigDataId = redisSentinelConfigDataId;
this.redisClusterConfigDataId = redisClusterConfigDataId;
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);
properties.put(PropertyKeyConst.NAMESPACE, namespace);
if (StringUtils.isNotBlank(username)) {
properties.put(PropertyKeyConst.USERNAME, username);
}
if (StringUtils.isNotBlank(password)) {
properties.put(PropertyKeyConst.PASSWORD, password);
}
configService = NacosFactory.createConfigService(properties);
}
public void init() throws NacosException {
configService.addListener(prefix + redisConfigDataId, groupId, new ConfigListener() {
@Override
public void receiveConfigInfo(String configInfo) {
LOGGER.info("redis config received: {}", configInfo);
if (listeners.containsKey(redisConfigDataId)) {
listeners.get(redisConfigDataId).receiveConfigInfo(configInfo);
}
}
@Override
public Executor getExecutor() {
return null;
}
});
configService.addListener(prefix + redisSentinelConfigDataId, groupId, new ConfigListener() {
@Override
public void receiveConfigInfo(String configInfo) {
LOGGER.info("redis sentinel config received: {}", configInfo);
if (listeners.containsKey(redisSentinelConfigDataId)) {
listeners.get(redisSentinelConfigDataId).receiveConfigInfo(configInfo);
}
}
@Override
public Executor getExecutor() {
return null;
}
});
configService.addListener(prefix + redisClusterConfigDataId, groupId, new ConfigListener() {
@Override
public void receiveConfigInfo(String configInfo) {
LOGGER.info("redis cluster config received: {}", configInfo);
if (listeners.containsKey(redisClusterConfigDataId)) {
listeners.get(redisClusterConfigDataId).receiveConfigInfo(configInfo);
}
}
@Override
public Executor getExecutor() {
return null;
}
});
}
public String getConfig(String dataId) throws NacosException {
return configService.getConfig(prefix + dataId, groupId, 5000);
}
public void addListener(String dataId, ConfigChangeListener listener) {
listeners.put(dataId, listener);
}
public void removeListener(String dataId) {
listeners.remove(dataId);
}
}
总结一下
我们可以在应用启动时初始化 NacosConfigListener 并通过 addListener 方法注册配置变化监听器,当配置变化时调用 listener 的 receiveConfigInfo 方法即可完成重新初始化的逻辑。需要注意的是,这里的 ConfigChangeListener 是一个接口,需要在应用中实现 receiveConfigInfo 方法,具体实现视应用而定。