强烈推荐一个大神的人工智能的教程:http://www.captainbed.net/zhanghan
【前言】
在上一篇博文《灰度实战(二):Apollo配置中心(2)》中讲解了Apollo如何动态更改程序中通过@value配置值,在本篇博文中为大家带来如何通过Apollo动态更新程序和中间件的连接。
【实时推送演示】
一、程序和第三方组件连接动态更改(在此以连接redis为例)---失败版
1、演示代码(增加redis操作)
package com.zhanghan.grayapollo.controller;
import com.zhanghan.grayapollo.util.wrapper.WrapMapper;
import com.zhanghan.grayapollo.util.wrapper.Wrapper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@Api(value = "演示Apollo控制器",tags = {"演示Apollo控制器"})
public class DynamicController {
@Value("${zh.int}")
private Integer zhInt;
@Autowired
private RedisTemplate<String, String> strRedisTemplate;
@ApiOperation(value="测试通过@value注入数据",tags = {"演示Apollo控制器"})
@RequestMapping(value = "/test/value", method = RequestMethod.POST)
public Wrapper testValue() {
Map<String, Object> map = new HashMap();
map.put("zhTest", zhInt);
return WrapMapper.ok(map);
}
@ApiOperation(value="演示向redis中放数据",tags = {"演示Apollo控制器"})
@RequestMapping(value = "/post/rdb", method = RequestMethod.POST)
public Wrapper postDB() {
strRedisTemplate.opsForValue().set("zh-test",zhInt.toString());
Map<String, Object> map = new HashMap();
map.put("putZhTest", zhInt.toString());
return WrapMapper.ok(map);
}
@ApiOperation(value="演示从redis中获取数据",tags = {"演示Apollo控制器"})
@RequestMapping(value = "/get/rdb", method = RequestMethod.POST)
public Wrapper getDB() {
String getZhInt = strRedisTemplate.opsForValue().get("zh-test");
Map<String, Object> map = new HashMap();
map.put("getZhTest", getZhInt);
return WrapMapper.ok(map);
}
}
2、访问swagger给redis设置值
3、访问swagger读取redis设置的值
4、用redis可视化工具查看,此时存储在redis的1库中(此时2库中没有值)
5、在Apollo上修改redis的库(由1改为2),并发布
6、查看系统的日志,发现将变动已经推送到程序
7、此时再次访问swagger的从redis中读取值接口
8、小结:
竟然还可以读取到值,说明redis数据库并未切换;说明刚刚的配置变更只是推送到程序,而程序在第一次启动时已经和redis建立连接(其中含redis的database),推送并没有改变连接;
二、程序和第三方组件连接动态更改(在此以连接redis为例)---成功版
1、如何动态的更改那?
(1)增加Apollo的监听类并刷新程序配置
package com.zhanghan.grayapollo.refresh;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class RedisPropertiesRefresher implements ApplicationContextAware {
private static final Logger logger = LoggerFactory.getLogger(RedisPropertiesRefresher.class);
private ApplicationContext applicationContext;
@Autowired
private RefreshScope refreshScope;
//此处配置的value值为Apollo的namespace名称
@ApolloConfigChangeListener(value = "grayapollo")
public void onChange(ConfigChangeEvent changeEvent) {
boolean propertiesChanged = false;
for (String changedKey : changeEvent.changedKeys()) {
logger.info("===============================================================");
logger.info("changedKey:{} value:{}", changedKey, changeEvent.getChange(changedKey));
ConfigChange configChange = changeEvent.getChange(changedKey);
configChange.getOldValue();
propertiesChanged = true;
break;
}
refreshProperties(changeEvent);
if (propertiesChanged) {
refreshProperties(changeEvent);
}
}
private void refreshProperties(ConfigChangeEvent changeEvent) {
this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
refreshScope.refreshAll();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
(2)redis工场增加@RefreshScope
package com.zhanghan.grayapollo.config;
import com.zhanghan.grayapollo.properties.RedisProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisPoolConfig;
@Component
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Autowired
private RedisProperties redisProperties;
@Bean
@RefreshScope //此注解用于动态刷新
public JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration rf = new RedisStandaloneConfiguration();
rf.setDatabase(redisProperties.getDatabase());
rf.setHostName(redisProperties.getHost());
rf.setPort(redisProperties.getPort());
rf.setPassword(RedisPassword.of(redisProperties.getPassword()));
JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpb =
(JedisClientConfiguration.JedisPoolingClientConfigurationBuilder) JedisClientConfiguration.builder();
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(redisProperties.getMaxIdle());
jedisPoolConfig.setMinIdle(redisProperties.getMinIdle());
jedisPoolConfig.setMaxTotal(redisProperties.getMaxActive());
jedisPoolConfig.setMaxWaitMillis(redisProperties.getMaxWait());
jedisPoolConfig.setEvictorShutdownTimeoutMillis(redisProperties.getTimeout());
jpb.poolConfig(jedisPoolConfig);
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(rf, jpb.build());
return jedisConnectionFactory;
}
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
RedisTemplate<Object, Object> redisTemplate() {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(jedisConnectionFactory());
return redisTemplate;
}
@Bean
RedisTemplate<String, String> strRedisTemplate() {
final RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericToStringSerializer<>(String.class));
template.setValueSerializer(new GenericToStringSerializer<>(String.class));
return template;
}
@Bean
RedisTemplate<String, Long> longRedisTemplate() {
final RedisTemplate<String, Long> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericToStringSerializer<>(Long.class));
template.setValueSerializer(new GenericToStringSerializer<>(Long.class));
return template;
}
@Bean
RedisTemplate<String, Boolean> booleanRedisTemplate() {
final RedisTemplate<String, Boolean> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
return template;
}
@Bean
RedisTemplate<String, Integer> intRedisTemplate() {
final RedisTemplate<String, Integer> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
return template;
}
}
2、此时清空本地redis,再重新做实验,进行验证;(程序启动时Apollo配置的redis的数据库为1,程序启动后动态改成连库2)
3、访问swagger给redis设置值
4、访问swagger读取redis设置的值
5、用redis可视化工具查看,此时存储在redis的1库中(此时2库中没有值)
6、在Apollo上修改redis的库(由1改为2),并发布
7、此时再次访问swagger的从redis中读取值接口(惊喜出现了),说明换库了
8、究竟是换的是不是1库那?我们此时再在swagger上调用放入redis值接口
9、此时通过redis客户端查看库2中惊奇发现已经有刚才设置的值;说明已经成功的切库。
10、至此实验成功;
三、项目地址
灰度实战:https://github.com/dangnianchuntian/gray
【总结】
1、本节第一部分为大家证实,如果不配置Apollo监听以及redis连接工程的动态更新,不能动态的更改程序与中间件连接。
2、本节第二部分为大家演示如何接收Apollo动态更新连第三方中间件的连接池(以redis连接池为例)。