随着程序功能的日益复杂,程序的配置日益增多:各类功能的开关、参数的配置、服务器的地址……java
对程序配置的指望值也愈来愈高:配置修改后实时生效,灰度发布,分环境、分集群管理配置,完善的权限、审核机制……git
在这样的大环境下,传统的经过配置文件、数据库等方式已经愈来愈没法知足开发人员对配置管理的需求。github
Apollo配置中心应运而生!spring
Apollo(阿波罗)是携程框架部门研发的开源配置管理中心,可以集中化管理应用不一样环境、不一样集群的配置,配置修改后可以实时推送到应用端,而且具有规范的权限、流程治理等特性。数据库
Apollo支持4个维度管理Key-Value格式的配置:api
同时,Apollo基于开源模式开发,开源地址:https://github.com/ctripcorp/apollo缓存
既然Apollo定位于配置中心,那么在这里有必要先简单介绍一下什么是配置。服务器
按照咱们的理解,配置有如下几个属性:网络
正是基于配置的特殊性,因此Apollo从设计之初就立志于成为一个有治理能力的配置管理平台,目前提供了如下的特性:并发
以下便是Apollo的基础模型:
上图是Apollo配置中心中一个项目的配置首页
用户能够经过配置中心界面方便的添加/修改配置项:
输入配置信息:
经过配置中心发布配置:
填写发布信息:
配置发布后,就能在客户端获取到了,以Java API方式为例,获取配置的示例代码以下。更多客户端使用说明请参见Java客户端使用指南。
Config config = ConfigService.getAppConfig(); Integer defaultRequestTimeout = 200; Integer requestTimeout = config.getIntProperty("request.timeout",defaultRequestTimeout);
经过上述获取配置代码,应用就能实时获取到最新的配置了。
不过在某些场景下,应用还须要在配置变化时得到通知,好比数据库链接的切换等,因此Apollo还提供了监听配置变化的功能,Java示例以下:
Config config = ConfigService.getAppConfig(); config.addChangeListener(new ConfigChangeListener() { @Override public void onChange(ConfigChangeEvent changeEvent) { for (String key : changeEvent.changedKeys()) { ConfigChange change = changeEvent.getChange(key); System.out.println(String.format( "Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType())); } } });
Apollo和Spring也能够很方便地集成,只须要标注@EnableApolloConfig
后就能够经过@Value
获取配置信息:
@Configuration @EnableApolloConfig public class AppConfig {}
@Component public class SomeBean { @Value("${request.timeout:200}") private int timeout; @ApolloConfigChangeListener private void someChangeHandler(ConfigChangeEvent changeEvent) { if (changeEvent.isChanged("request.timeout")) { refreshTimeout(); } } }
经过上面的介绍,相信你们已经对Apollo有了一个初步的了解,而且相信已经覆盖到了大部分的使用场景。
接下来会主要介绍Apollo的cluster管理(集群)、namespace管理(命名空间)和对应的配置获取规则。
在介绍高级特性前,咱们有必要先来了解一下Apollo中的几个核心概念:
【本节内容仅对应用须要对不一样集群应用不一样配置才须要,如没有相关需求,能够跳过本节】
好比咱们有应用在A数据中心和B数据中心都有部署,那么若是但愿两个数据中心的配置不同的话,咱们能够经过新建cluster来解决。
新建Cluster只有项目的管理员才有权限,管理员能够在页面左侧看到“添加集群”按钮。
点击后就进入到集群添加页面,通常状况下能够按照数据中心来划分集群,如SHAJQ、SHAOY等。
不过也支持自定义集群,好比能够为A机房的某一台机器和B机房的某一台机建立一个集群,使用一套配置。
集群添加成功后,就能够为该集群添加配置了,首选须要按照下图所示切换到SHAJQ集群,以后配置添加流程和3.2添加/修改配置项同样,这里就再也不赘述了。
Apollo会默认使用应用实例所在的数据中心做为cluster,因此若是二者一致的话,不须要额外配置。
若是cluster和数据中心不一致的话,那么就须要经过System Property方式来指定运行时cluster:
apollo.cluster
为全小写【本节仅对公共组件配置或须要多个应用共享配置才须要,如没有相关需求,能够跳过本节】
若是应用有公共组件(如hermes-producer,cat-client等)供其它应用使用,就须要经过自定义namespace来实现公共组件的配置。
以hermes-producer为例,须要先新建一个namespace,新建namespace只有项目的管理员才有权限,管理员能够在页面左侧看到“添加Namespace”按钮。
点击后就进入namespace添加页面,Apollo会把应用所属的部门做为namespace的前缀,如FX。
Namespace建立完,须要选择在哪些环境和集群下使用
接下来在这个新建的namespace下添加配置项
添加完成后就能在FX.Hermes.Producer的namespace中看到配置。
对自定义namespace的配置获取,稍有不一样,须要程序传入namespace的名字。更多客户端使用说明请参见Java客户端使用指南。
Config config = ConfigService.getConfig("FX.Hermes.Producer"); Integer defaultSenderBatchSize = 200; Integer senderBatchSize = config.getIntProperty("sender.batchsize", defaultSenderBatchSize);
Config config = ConfigService.getConfig("FX.Hermes.Producer"); config.addChangeListener(new ConfigChangeListener() { @Override public void onChange(ConfigChangeEvent changeEvent) { System.out.println("Changes for namespace " + changeEvent.getNamespace()); for (String key : changeEvent.changedKeys()) { ConfigChange change = changeEvent.getChange(key); System.out.println(String.format( "Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType())); } } });
@Configuration @EnableApolloConfig("FX.Hermes.Producer") public class AppConfig {}
@Component public class SomeBean { @Value("${request.timeout:200}") private int timeout; @ApolloConfigChangeListener("FX.Hermes.Producer") private void someChangeHandler(ConfigChangeEvent changeEvent) { if (changeEvent.isChanged("request.timeout")) { refreshTimeout(); } } }
【本节仅当应用自定义了集群或namespace才须要,如无相关需求,能够跳过本节】
在有了cluster概念后,配置的规则就显得重要了。
好比应用部署在A机房,可是并无在Apollo新建cluster,这个时候Apollo的行为是怎样的?
或者在运行时指定了cluster=SomeCluster,可是并无在Apollo新建cluster,这个时候Apollo的行为是怎样的?
接下来就来介绍一下配置获取的规则。
当应用使用下面的语句获取配置时,咱们称之为获取应用自身的配置,也就是应用自身的application namespace的配置。
Config config = ConfigService.getAppConfig();
对这种状况的配置获取规则,简而言之以下:
图示以下:
因此若是应用部署在A数据中心,可是用户没有在Apollo建立cluster,那么获取的配置就是默认cluster(default)的。
若是应用部署在A数据中心,同时在运行时指定了SomeCluster,可是没有在Apollo建立cluster,那么获取的配置就是A数据中心cluster的配置,若是A数据中心cluster没有配置的话,那么获取的配置就是默认cluster(default)的。
以FX.Hermes.Producer为例,hermes producer是hermes发布的公共组件。当使用下面的语句获取配置时,咱们称之为获取公共组件的配置。
Config config = ConfigService.getConfig("FX.Hermes.Producer");
对这种状况的配置获取规则,简而言之以下:
图示以下:
经过这种方式,就实现了对框架类组件的配置管理,框架组件提供方提供配置的默认值,应用若是有特殊需求,能够自行覆盖。
上图简要描述了Apollo的整体设计,咱们能够从下往上看:
为何咱们采用Eureka做为服务注册中心,而不是使用传统的zk、etcd呢?我大体总结了一下,有如下几方面的缘由:
上图简要描述了Apollo客户端的实现原理:
apollo.refreshInterval
来覆盖,单位为分钟。前面提到了Apollo客户端和服务端保持了一个长链接,从而能第一时间得到配置更新的推送。
长链接实际上咱们是经过Http Long Polling实现的,具体而言:
考虑到会有数万客户端向服务端发起长连,在服务端咱们使用了async servlet(Spring DeferredResult)来服务Http Long Polling请求。
配置中心做为基础服务,可用性要求很是高,下面的表格描述了不一样场景下Apollo的可用性:
场景 | 影响 | 降级 | 缘由 |
---|---|---|---|
某台config service下线 | 无影响 | Config service无状态,客户端重连其它config service | |
全部config service下线 | 客户端没法读取最新配置,Portal无影响 | 客户端重启时,能够读取本地缓存配置文件 | |
某台admin service下线 | 无影响 | Admin service无状态,Portal重连其它admin service | |
全部admin service下线 | 客户端无影响,portal没法更新配置 | ||
某台portal下线 | 无影响 | Portal域名经过slb绑定多台服务器,重试后指向可用的服务器 | |
所有portal下线 | 客户端无影响,portal没法更新配置 | ||
某个数据中心下线 | 无影响 | 多数据中心部署,数据彻底同步,Meta Server/Portal域名经过slb自动切换到其它存活的数据中心 |
Apollo从开发之初就是以开源模式开发的,因此也很是欢迎有兴趣、有余力的朋友一块儿加入进来。
服务端开发使用的是Java,基于Spring Cloud和Spring Boot框架。客户端目前提供了Java和.Net两种实现。
Github地址:https://github.com/ctripcorp/apollo
原文:http://nobodyiam.com/2016/07/09/introduction-to-apollo/