你的位置:首页 > Java教程

[Java教程]第四章 配置集中管理 + 配置动态管理


问题:之前的配置文件都是散在各个项目中的,导致配置文件的管理比较困难,而且配置的值一旦改变,我们就需要重新编译部署整个项目,非常麻烦!!!

解决方案

  • 配置的集中管理:采用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