灰度实战(一):Apollo配置中心(1)
灰度实战(二):Apollo配置中心(2)
灰度实战(三):Apollo配置中心(3)
灰度实战(四):Apollo配置中心(4)
灰度实战(五):SpringCloud灰度(1)
灰度实战(六):SpringCloud灰度(2)
在上一篇博文《灰度实战(二):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 strRedisTemplate; @ApiOperation(value="测试通过@value注入数据",tags = {"演示Apollo控制器"}) @RequestMapping(value = "/test/value", method = RequestMethod.POST) public Wrapper testValue() { Map 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 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 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;@Componentpublic 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@EnableCachingpublic 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 redisTemplate() { RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(jedisConnectionFactory()); return redisTemplate; } @Bean RedisTemplate strRedisTemplate() { final RedisTemplate 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 longRedisTemplate() { final RedisTemplate 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 booleanRedisTemplate() { final RedisTemplate 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 intRedisTemplate() { final RedisTemplate 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连接池为例)。