问题:之前的配置文件都是散在各个项目中的,导致配置文件的管理比较困难,而且配置的值一旦改变,我们就需要重新编译部署整个项目,非常麻烦!!!
解决方案:
- 配置的集中管理:采用consul的KV,将所有微服务的application.properties中的配置内容存入consul。
- 配置的动态管理:采用archaius,将consul上的配置信息读到spring的PropertySource和archaius的PollResult中,当修改了配置信息后,经常改变的值通过DynamicFactory来获取,不经常改变的值可以通过其他方式获取(例如,environment、@value、@ConfigurationProperties(prefix = "xxx")),这样的话,大部分情况下,修改了consul上的配置信息后,相应的项目不需要重启,也会读到最新的值。(当然不经常改变的值如果发生了修改,还是需要重启整个项目的)--- 这一块儿参考:第二十二章 springboot + archaius + consul(配置管理)
预备知识:理解springboot的启动过程。
一、项目结构
二、基础框架:framework
1、pom.
1 <!-- archaius --> 2 <dependency> 3 <groupId>com.netflix.archaius</groupId> 4 <artifactId>archaius-core</artifactId> 5 <version>0.6.6</version> 6 </dependency> 7 <!-- 动态配置,archaius底层 --> 8 <dependency> 9 <groupId>commons-configuration</groupId>10 <artifactId>commons-configuration</artifactId>11 <version>1.8</version>12 </dependency>
View Code
说明:引入archaius及其底层commons-configuration
2、读取配置信息的数据源:ConsulConfigurationSource
1 package com.microservice.archaius; 2 3 import java.io.StringReader; 4 import java.util.HashMap; 5 import java.util.Map; 6 import java.util.Properties; 7 8 import org.apache.commons.lang3.StringUtils; 9 10 import com.google.common.base.Optional;11 import com.netflix.config.PollResult;12 import com.netflix.config.PolledConfigurationSource;13 import com.orbitz.consul.Consul;14 import com.orbitz.consul.KeyValueClient;15 16 /**17 * 指定archaius读取配置的源头18 */19 public class ConsulConfigurationSource implements PolledConfigurationSource {20 21 private String keyName;22 23 public ConsulConfigurationSource(String keyName) {24 this.keyName = keyName;25 }26 27 /**28 * 默认情况下,每隔60s,该方**执行一次29 */30 @Override31 public PollResult poll(boolean initial, Object checkPoint) throws Exception {32 Consul consul = Consul.builder().build();33 KeyValueClient kvClient = consul.keyValueClient();34 Optional<String> kvOpt = kvClient.getValueAsString(keyName);35 String kvStr = StringUtils.EMPTY;36 if (kvOpt.isPresent()) {37 kvStr = kvOpt.get();38 }39 40 Properties props = new Properties();41 props.load(new StringReader(kvStr));//String->Properties42 43 Map<String, Object> propMap = new HashMap<>();44 for (Object key : props.keySet()) {45 propMap.put((String) key, props.get(key));46 }47 return PollResult.createFull(propMap);48 }49 50 }
View Code
步骤:
- 从consul上读取相应key的value
- 将读下来的String类型的value转成Properties
- 将Properties的KV传入PollResult
注意:
- 上边这个过程默认每隔60s执行一次(也就是说,consul上修改的配置项最多过60s就会被读取到新值),这个值可以通过在system.setproperty中设置读取时间来改变archaius.fixedDelayPollingScheduler.delayMills
3、设置PropertySource:ConsulPropertySource
1 package com.microservice.archaius; 2 3 import java.util.Iterator; 4 import java.util.Map; 5 6 import org.springframework.core.env.MapPropertySource; 7 8 import com.netflix.config.AbstractPollingScheduler; 9 import com.netflix.config.ConfigurationManager;10 import com.netflix.config.DynamicConfiguration;11 import com.netflix.config.FixedDelayPollingScheduler;12 import com.netflix.config.PolledConfigurationSource;13 14 /**15 * 将 consul读取的配置信息存入netflix config和PropertySource16 */17 public class ConsulPropertySource extends MapPropertySource {18 19 /**20 * @param name 属性源名称:这里就是consul KV中的K21 * @param source 属性源:这里就是consul KV中的V22 */23 public ConsulPropertySource(String name, Map<String, Object> source) {24 super(name, source);//初始化25 26 /**27 * 从consul上读取属性并存入netflix config28 */29 PolledConfigurationSource configSource = new ConsulConfigurationSource(name);//定义读取配置的源头30 AbstractPollingScheduler scheduler = new FixedDelayPollingScheduler();//设置读取配置文件的31 DynamicConfiguration configuration = new DynamicConfiguration(configSource, scheduler);32 ConfigurationManager.install(configuration);33 34 /**35 * 将属性存入PropertySource36 */37 @SuppressWarnings("rawtypes")38 Iterator it = configuration.getKeys();39 while (it.hasNext()) {40 String key = (String) it.next();41 this.source.put(key, configuration.getProperty(key));42 }43 }44 45 }
View Code
步骤:
- 继承自MapPropertySource(PropertySource的子类),首先初始化该属性源的name和source
- name:数据源名称。"service/微服务名称/微服务tag/config"(例如,service/jigangservice/dev/config)
- source:数据源值。
- 通过动态数据源与调度器构建DynamicConfiguration,并加入缓存管理器
- 将DynamicConfiguration中的从consul上读下来的Properties的KV设置到source中去
注意:
- 从这里,我们可以看出,一个微服务项目的配置信息会存两份:一份在PollResult,一份存在spring的PropertySource,前者动态改变,后者固定不变
4、初始化器:ConsulPropertySourceBootstrapInitializer
1 package com.microservice.archaius; 2 3 import java.util.HashMap; 4 import java.util.Properties; 5 6 import org.springframework.context.ApplicationContextInitializer; 7 import org.springframework.context.ConfigurableApplicationContext; 8 import org.springframework.core.env.ConfigurableEnvironment; 9 import org.springframework.core.env.MutablePropertySources;10 import org.springframework.core.env.PropertySource;11 12 import com.microservice.util.BaseContants;13 import com.microservice.util.PropertyUtil;14 15 public class ConsulPropertySourceBootstrapInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {16 17 @Override18 public void initialize(ConfigurableApplicationContext applicationContext) {19 ConfigurableEnvironment env = applicationContext.getEnvironment();20 PropertySource<?> source = this.locate(env);21 MutablePropertySources propertySources = env.getPropertySources();22 propertySources.addLast(source);23 }24 25 private PropertySource<?> locate(ConfigurableEnvironment env) {26 env.getPropertySources().remove(BaseContants.DEFAULT_PROPERTIES_SOURCE_NAME);//移除application.properties的PropertySource27 Properties props = PropertyUtil.loadProps("bootstrap.properties");28 String servicename = props.getProperty(BaseContants.SERVICE_NAME_KEY);29 String servicetag = props.getProperty(BaseContants.SERVICE_TAG_KEY);30 return new ConsulPropertySource("service/" + servicename + "/" + servicetag + "/config", new HashMap<>());31 }32 }
View Code
步骤:
- 实现ApplicationContextInitializer接口,重写其中的initialize方法
- 首先,删除application.properties的PropertySource,否则可能会读到application.properties中的内容,而我们只是想读consul上的内容
- 加载bootstrap.properties文件,读取其中的servicename(微服务名)和servicetag(微服务tag)
- 与启动相关而与业务不相关的数据写在bootstrap.properties中去,而与业务相关的参数写到consul里去
- 调用ConsulPropertySource的多参构造器就好了
5、两个辅助类:BaseContants + PropertyUtil
1 package com.microservice.util;2 3 public class BaseContants {4 public static final String SERVICE_NAME_KEY = "service.name";5 public static final String SERVICE_TAG_KEY = "service.tag";6 public static final String DEFAULT_PROPERTIES_SOURCE_NAME = "applicationConfigurationProperties";7 }
View Code
1 package com.microservice.util; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.util.Properties; 6 7 /** 8 * 文件操作工具类 9 */10 public class PropertyUtil {11 12 /**13 * 加载属性文件*.properties14 * @param fileName 不是属性全路径名称,而是相对于类路径的名称15 */16 public static Properties loadProps(String fileName) {17 Properties props = null;18 InputStream is = null;19 20 try {21 is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);//获取类路径下的fileName文件,并且转化为输入流22 if (is != null) {23 props = new Properties();24 props.load(is); //加载属性文件25 }26 } catch (Exception e) {27 e.printStackTrace();28 } finally {29 if (is != null) {30 try {31 is.close();32 } catch (IOException e) {33 e.printStackTrace();34 }35 }36 }37 38 return props;39 }40 }
View Code
6、启动类:MySpringAplication
1 package com.microservice; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 6 import com.microservice.archaius.ConsulPropertySourceBootstrapInitializer; 7 import com.microservice.consul.ConsulRegisterListener; 8 9 import springfox.documentation.swagger2.annotations.EnableSwagger2;10 11 /**12 * 注意:@SpringBootApplication该注解必须在SpringApplication.run()所在的类上13 */14 @SpringBootApplication15 @EnableSwagger216 public class MySpringAplication {17 18 public void run(String[] args) {19 SpringApplication sa = new SpringApplication(MySpringAplication.class);20 sa.addInitializers(new ConsulPropertySourceBootstrapInitializer());//读取配置文件21 sa.addListeners(new ConsulRegisterListener());//consul服务注册22 sa.run(args);23 }24 25 public static void main(String[] args) {26 }27 }
View Code
说明:添加了initializer
三、微服务A:myserviceA
1、启动参数配置类:bootstrap.properties
1 service.name=myserviceA2 service.tag=dev3 service.port=80804 health.url=http://localhost:8080/health5 health.interval=10
View Code
2、测试controller
1 package com.microservice.myserviceA.controller; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.beans.factory.annotation.Value; 5 import org.springframework.core.env.Environment; 6 import org.springframework.web.bind.annotation.RequestMapping; 7 import org.springframework.web.bind.annotation.RestController; 8 9 import com.netflix.config.DynamicPropertyFactory;10 import com.netflix.config.DynamicStringProperty;11 12 @RestController13 @RequestMapping("/test/archaius")14 public class TestController {15 @Autowired16 private Environment env;17 @Value("${mysql.driverClassName}")18 private String zjgBrother;19 20 @RequestMapping("/xxx")21 public String test(){22 System.out.println("env->"+env.getProperty("xxx"));23 System.out.println("value->"+zjgBrother);24 25 DynamicStringProperty dsp = DynamicPropertyFactory.getInstance().getStringProperty("hystrix.command.HystrixCommandKey.execution.isolation.thread.timeoutInMilliseconds", "xxx");26 System.out.println("Dynamic->"+dsp.get());27 return "hello";28 }29 }
View Code
说明:测试了从environment获取、从@value获取以及dynamicFactory中获取三种方式,前两种是从PropertySource中获取(不能感知consul上配置的变化),最后一种是从PollResult中获取(可以感知consul上的配置的变化)
四、测试
- 在consul的KV上进行手动配置(或者用过consul-git提交,之后会说)
- 启动swagger,运行controller
难点:
- 只有掌握了springboot的启动流程,才可以知道在initializer中添加读取配置、构建propertySource的代码
- 这一块儿也可以参考springcloud的做法,构建两个applicationContext,父context负责读取配置信息,然后传给子类的main函数的context。具体文档:http://projects.spring.io/spring-cloud/docs/1.0.1/spring-cloud.html
原标题:第四章 配置集中管理 + 配置动态管理
关键词: