背景

在项目重构时,删除若干个application-{env}.yml文件,仅保留一个application.yml文件,该文件中保留的配置项都是几乎不会变更的配置,至于需要跟随不同环境而变更的配置项都放置在Apollo配置中心。

然后本地application.yml文件里面有一个静态配置,调用外部接口的URL:https://graph.facebook.com/v9.0/aaaaa,有一天没有得到事前通知(至少研发同事都没人注意到),这个v9.0版本的API被废弃(deprecated),进而引发严重的生产问题。
方案:

  1. 修改本地配置,然后重新部署应用;
  2. 添加Apollo配置。

故而引出问题:本地配置和Apollo配置的优先级关系是怎样的?方案2的预设前提是:Apollo的优先级高于本地配置应用,并且可以自动下发推送到客户端,即无需重新部署应用。

调研

Apollo使用的Spring @Value注解为字段注入值,那么Apollo与yml同时存在相同配置时,以谁为准?Apollo官网有此解释,在 3.1 和Spring集成的原理,结论是优先读取Apollo的配置,Apollo中没有的读取其他的。官网解释如下:

Apollo除了支持API方式获取配置,也支持和Spring/Spring Boot集成,集成原理简述如下。

Spring从3.1版本开始增加ConfigurableEnvironment和PropertySource:
ConfigurableEnvironment
Spring的ApplicationContext会包含一个Environment(实现ConfigurableEnvironment接口)
ConfigurableEnvironment自身包含了很多个PropertySource
PropertySource
属性源
可以理解为很多个Key - Value的属性配置
在运行时的结构形如:

Application ContextEnvironmentPropertySourcesPropertySource1PropertySource2

PropertySource之间是有优先级顺序的,如果有一个Key在多个property source中都存在,在前面的property source优先。

基于此原理后,Apollo和Spring/SB集成的方案就是:在应用启动阶段,Apollo从远端获取配置,然后组装成PropertySource并插入到第一个即可,即 Remote Property Source

源码

package com.ctrip.framework.apollo.spring.config;import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener;
import com.ctrip.framework.apollo.spring.util.SpringInjector;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;/*** Apollo Property Sources processor for Spring Annotation Based Application. <br /> <br />** The reason why PropertySourcesProcessor implements {@link BeanFactoryPostProcessor} instead of* {@link org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor} is that lower versions of* Spring (e.g. 3.1.1) doesn't support registering BeanDefinitionRegistryPostProcessor in ImportBeanDefinitionRegistrar* - {@link com.ctrip.framework.apollo.spring.annotation.ApolloConfigRegistrar}** @author Jason Song(song_s@ctrip.com)*/
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {private static final Multimap<Integer, String> NAMESPACE_NAMES = LinkedHashMultimap.create();private static final Set<BeanFactory> AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES = Sets.newConcurrentHashSet();private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector.getInstance(ConfigPropertySourceFactory.class);private ConfigUtil configUtil;private ConfigurableEnvironment environment;public static boolean addNamespaces(Collection<String> namespaces, int order) {return NAMESPACE_NAMES.putAll(order, namespaces);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {this.configUtil = ApolloInjector.getInstance(ConfigUtil.class);initializePropertySources();initializeAutoUpdatePropertiesFeature(beanFactory);}private void initializePropertySources() {if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME)) {//already initializedreturn;}CompositePropertySource composite;if (configUtil.isPropertyNamesCacheEnabled()) {composite = new CachedCompositePropertySource(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME);} else {composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME);}//sort by order ascImmutableSortedSet<Integer> orders = ImmutableSortedSet.copyOf(NAMESPACE_NAMES.keySet());Iterator<Integer> iterator = orders.iterator();while (iterator.hasNext()) {int order = iterator.next();for (String namespace : NAMESPACE_NAMES.get(order)) {Config config = ConfigService.getConfig(namespace);composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));}}// clean upNAMESPACE_NAMES.clear();// add after the bootstrap property source or to the firstif (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {// ensure ApolloBootstrapPropertySources is still the firstensureBootstrapPropertyPrecedence(environment);environment.getPropertySources().addAfter(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, composite);} else {environment.getPropertySources().addFirst(composite);}}private void ensureBootstrapPropertyPrecedence(ConfigurableEnvironment environment) {MutablePropertySources propertySources = environment.getPropertySources();PropertySource<?> bootstrapPropertySource = propertySources.get(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);// not exists or already in the first placeif (bootstrapPropertySource == null || propertySources.precedenceOf(bootstrapPropertySource) == 0) {return;}propertySources.remove(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);propertySources.addFirst(bootstrapPropertySource);}private void initializeAutoUpdatePropertiesFeature(ConfigurableListableBeanFactory beanFactory) {if (!configUtil.isAutoUpdateInjectedSpringPropertiesEnabled() || !AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES.add(beanFactory)) {return;}AutoUpdateConfigChangeListener autoUpdateConfigChangeListener = new AutoUpdateConfigChangeListener(environment, beanFactory);List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources();for (ConfigPropertySource configPropertySource : configPropertySources) {configPropertySource.addChangeListener(autoUpdateConfigChangeListener);}}@Overridepublic void setEnvironment(Environment environment) {//it is safe enough to cast as all known environment is derived from ConfigurableEnvironmentthis.environment = (ConfigurableEnvironment) environment;}@Overridepublic int getOrder() {//make it as early as possiblereturn Ordered.HIGHEST_PRECEDENCE;}// for test onlystatic void reset() {NAMESPACE_NAMES.clear();AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES.clear();}
}

验证

本地application.yml添加一个配置test: aaaaa,Controller打印配置值(不够严谨:没有加时间戳):

@RestController
public class HealthController {@Value("${test}")private String test;@RequestMapping(value = "/hs")public String hs() {System.out.println("test" + test);return "ok";}
}

启动应用程序,postman请求接口http://localhost:8080/hs,控制台输出:testaaaaa

增加Apollo配置项:

发布配置项,配置生效时间有延迟,等一分钟,再次请求接口,控制台打印输出依然是:testaaaaa

至于未打印时间的不够严谨的问题,给出postman截图:

所以,应用需要重启

结论:应用发布后,Apollo新增的配置,无论这个配置是否在本地配置文件存在与否,都不能覆盖。想要Apollo的配置生效,必须要重启(重新部署)应用,因为配置是启动时加载的

注:本文使用的Apollo为公司内部二次开发版,基于Apollo 1.4版本,开源最新版本为1.9。

热更新

public String currentUser() {log.info("levelOne" + levelOne);
}
2022-03-09 15:50:29.413 [INFO][http-nio-8080-exec-4]:c.x.c.common.services.impl.UserServiceImpl [currentUser:274] levelOne10


另外,再给出配置发布历史,发布前是10,发布后是15。

2022-03-09 15:52:58.564 [INFO][http-nio-8080-exec-1]:c.x.c.common.services.impl.UserServiceImpl [currentUser:274] levelOne10

结论:公司内部维护的Apollo版本,真是一个笑话!!!!!!!!

如何实现热更新

答案:使用@ApolloConfig

实例:

@ApolloConfig
private Config config;int one = config.getIntProperty("category.level.one", 11);
log.info("one: " + one);

变更前的日志:
2022-03-10 14:01:38.373 [INFO][http-nio-8080-exec-4]:c.x.c.common.services.impl.UserServiceImpl [currentUser:280] one: 10
配置变更:

变更前的日志:
2022-03-10 14:03:11.463 [INFO][http-nio-8080-exec-8]:c.x.c.common.services.impl.UserServiceImpl [currentUser:280] one: 12

其他

另外还有一个感觉很恶心的使用问题,配置项必须不能为空:

参考

Apollo配置中心设计
apollo配置中心与yml中存在相同配置
https://github.com/apolloconfig/apollo/issues/1800

Apollo配置中心与本地配置优先级相关推荐

  1. SpringCloud config 配置中心集群配置以及整合消息总线BUS实现关联微服务配置自动刷新

    一.SpringCloud Config 基本配置中的问题 在上一章节<SpringCloud config 配置中心介绍与基本配置使用>中我们现实了配置中心的配置集中管理.调用微服务应用 ...

  2. SpringCloud配置中心客户端读取配置

    转载自 SpringCloud配置中心客户端读取配置 微服务连接配置中心来实现外部配置的读取. 引入依赖 <dependencies><dependency><group ...

  3. 分布式配置中心 Disconf 安装配置

    分布式配置中心 Disconf 安装配置 前提准备 CentOS 服务器一台 MySQL 数据库一台 安装依赖软件 安装 JDK 安装 Git 安装 Maven 安装 Zookeeeper 安装 To ...

  4. 携程Apollo(阿波罗)配置中心本地开发模式不接入配置中心进行本地开发

    官方教程:https://github.com/ctripcorp/apollo/wiki/Java%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BD%BF%E7%94%A8%E6%8 ...

  5. Apollo分布式配置中心在本地的安装教程

    一.准备工作 1.1 Java Apollo服务端:1.8+ Apollo客户端:1.7+ 由于Quick Start会在本地同时启动服务端和客户端,所以需要在本地安装Java 1.8+. 在配置好后 ...

  6. apollo 配置中心_分布式配置中心之Apollo

    一.Apollo简介 Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限.流程治理等特性,适用于微 ...

  7. SpringCloud Alibaba - Nacos 作为配置中心 读取Properties配置信息

    SpringCloud Alibaba是阿里巴巴致力于对微服务的管理.配置.注册等一整套的解决方案. 简介 Nacos 提供用于存储配置和其他元数据的 K-V 存储,为分布式系统中的外部化配置提供服务 ...

  8. Nacos配置中心-如何使用Nacos作为配置中心统一管理配置

    1).引入依赖,<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-c ...

  9. 配置中心_Nacos做配置中心

    一.简单使用Nacos 官方快速开始文档:https://nacos.io/zh-cn/docs/quick-start.html 在每个服务中编写bootstrap文件,服务使用该文件启动,并根据该 ...

最新文章

  1. mysql 1146错误
  2. ios 两个 TableView 之间的联动, TableView 与 CollectionView 之间的联动
  3. 区块链BaaS云服务(5)金丘科技 海星链
  4. [云炬创业管理笔记]第二章测试1
  5. 人类视觉系统_对人类视觉系统的对抗攻击
  6. RubyMine 1.0加入Ruby IDE大家庭
  7. postscript打印机什么意思_涨知识|你不知道的关于打印机的打印过程和打印机驱动的那些事...
  8. C/C++程序员如何学习英语
  9. WEB打印分页类(JS)
  10. 04-07递归解法问题
  11. Mac字体管理工具: RightFont
  12. FireFox如何使用扩展
  13. Greasy Fork、GitHub、OpenUserJS
  14. Js(二)SyntaxError Cannot use import statement outside a module
  15. linux服务器网络不稳定,Linux服务器故障排查指南7:网络缓慢状况
  16. Word中的TIF图像保存后再打开就模糊了
  17. 远程服务器和宽带连接711有关系吗,宽带连接错误711是怎么回事 宽带连接错误711的解决方案...
  18. 【mac使用技巧】程序坞上的下载不见了该如何解决呢?
  19. 嵌入式系统编程实现485串口收发数据
  20. 04735数据库系统原理(知识点快速记忆)

热门文章

  1. sony降级xp,超级简单,集成sata,可以u盘pe安装
  2. python preference_Android首选项(二) 隐藏Preference
  3. 计算机毕业设计ssm基于HTML5的流浪动物领养平台yww0b系统+程序+源码+lw+远程部署
  4. 转:每个架构师都应该研究下康威定律
  5. 用JS简单实现Vue的双向绑定
  6. 《ESPnet2-TTS: Extending the Edge of TTS Research》
  7. 7 个最好的 Word 转 PDF 转换器
  8. android studio ui源码,KOK官方合作注册连接:http://kokbet1533.com -官网
  9. MySQL密码重置(Windows本地)
  10. python uiautomator2控制手机点击_Python控制手机03-Uiautomator2配置