分布式配置中心--Apollo
分布式配置中心--Apollo
Apollo(阿波罗)是携程开源的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,支持配置热发布并实时推送到应用端,并且具备规范的权限及流程治理等特性,适用于分布式微服务配置管理场景
Apollo配置中心介绍
程序功能日益复杂,程序配置日益增多:各种功能开关、参数配置、服务器地址...
对程序配置的期望也越来越高:热部署并实时生效、灰度发布、分环境分集群管理配置、完善的权限审核机制...
在这样的背景下,Apollo配置中心应运而生。Apollo支持四个维度Key-Value格式的配置
- Application(应用) 实际使用配置的应用,Apollo客户端在运行时需要知道当前应用是谁,从而可以去获取对应的配置。每个应用都有对应的身份标识--appId,需要在代码中配置
- Environment(环境) 配置对应的环境,Apollo客户端需要知道当前应用出于哪个环境,,从而可以去获取应用的配置;环境和代码无关,同一份代码部署在不同的环境就应该获取不同环境的配置;环境默认是通过读取机器上的配置(server.properties的env属性)指定的
- Cluster(集群) 一个应用下不同实例的分组,例如按照不同数据中心划分,把上海机房的实例分为一个集群、把深圳机房的实例分为一个集群;对于不同的Cluster,同一个配置可以有不一样的值;集群默认是通过读取机器上的配置指定的(server.properties的idc属性)
- Namespace(命名空间) 一个应用下不同配置的分组,是配置项的集合,可以简单地把Namespace类别为(配置)文件,不同类型的配置存放在不同的文件中,例如数据库配置文件、RPC配置文件、应用自身的配置文件等;应用可以直接读取到公共组件的配置namespace,例如DAL、RPC等;应用也可以通过继承公共组件的配置namespace来对公共组件的配置做调整,如DAL的初始数据库连接数
Apollo在创建项目的时候,都会默认创建一个"application"的Namespace,"application"是个应用自身使用的。例如Spring Boot中项目的默认配置文件application.yaml,这里application.yaml就等同于"application"的Namespace。对于大多数应用来说,"application"Namespace已经能满足日常配置使用场景客户端获取"application"Namespace的代码如下
Config config = ConfigService.getAppConfig()
客户端获取非"application"Namespace的代码如下
Config config = ConfigService.getConfig(namespaceName)
Namespace的格式 配置文件有多种格式,properties、xml、yml、yaml、json等,同样Namespace也具有这些格式
tips: 非properties格式的namespace,在客户端使用时需要调用`ConfigService.getConfigFile(String namespace, ConfigFileFormat configFileFormat)`来获取,如果使用Htpp接口直接调用时,对应的namespace参数需要传入namespace的名字加上后缀名,如datasource.json
Namespace的获取权限分类 此处权限相是对于Apollo客户端来说的
- private(私有的)权限 private权限的Namespace,只能被所属的应用获取到。一个应用尝试获取其他应用private的Namespace,Apollo客户端会报"404"异常
- public(公共的)权限 具有public权限的Namespace,能被任何应用获取
Namespace的类型
- 私有类型 具有private权限,例如上文中提到的"application"Namespace就是私有类型
- 公共类型 具有public权限,公共类型的Namespace相当于游离于应用之外的配置,且通过Namespace的名称去标识公共Namespace,所以公共Namespace的名称必须全局唯一
使用场景 部门级别共享的配置、小组级别共享的配置、几个项目之间共享的配置、中间件客户端的配置
- 关联类型(继承类型) 具有private权限,关联类型的Namespace继承于公共类型的Namespace,用于覆盖公共Namespace的某些配置。例如公共Namespace有两个配置项
k1 = v1
k2 = v2
然后应用A有一个关联类型的Namespace关联此公共Namespace,且以新值v3覆盖配置项k1。那么在应用A实际运行时,获取到的公共Namespace的配置为
k1 = v3
k2 = v2
使用场景 假设RPC框架的配置(如:timeout)有以下要求
- 提供一份全公司默认的配置,且可动态调整
- RPC客户端项目可以自定义某些配置项且可动态调整
结合Apollo的公共类型的Namespace和关联类型的Namespace。RPC团队在Apollo上维护一个叫“rpc-client”的公共Namespace,在"rpc-client"Namespace上配置默认的参数值。rpc-client.jar里的代码读取"rpc-client"Namespace的配置即可;如需要调整默认的配置,只需要修改公共类型"rpc-client"Namespace的配置;如果客户端项目想要自定义或动态修改某些配置项,只需要在Apollo自己项目下关联"rpc-client",就能创建关联类型"rpc-client"的Namespace,然后在关联类型下修改配置项即可。这里rpc-client.jar是在应用容器里运行的,所以rpc-client获取到"rpc-client"Namespace的配置是应用的关联类型的Namespace加上公共类型的Namespace
例子 如下图,有三个应用:应用A、应用B、应用C
- 应用A有两个私有类型的Namespace:application和NS-Private,以及一个关联类型的Namespace:NS-Public
- 应用B有一个私有类型的Namespace:application,以及一个公共类型的Namespace:NS-Public
- 应用C只有一个私有类型的Namespace:application
![](https://pic1.zhimg.com/80/v2-64ebb06ea6af434acaca7249c3ab0a94_720w.jpg)
应用A获取Apollo配置
//application
Config appConfig = ConfigService.getAppConfig();
appConfig.getProperty("k1", null); // k1 = v11
appConfig.getProperty("k2", null); // k2 = v21
//NS-Private
Config privateConfig = ConfigService.getConfig("NS-Private");
privateConfig.getProperty("k1", null); // k1 = v3
privateConfig.getProperty("k3", null); // k3 = v4
//NS-Public,覆盖公共类型配置的情况,k4被覆盖
Config publicConfig = ConfigService.getConfig("NS-Public");
publicConfig.getProperty("k4", null); // k4 = v6 cover
publicConfig.getProperty("k6", null); // k6 = v6
publicConfig.getProperty("k7", null); // k7 = v7
应用B获取Apollo配置
//application
Config appConfig = ConfigService.getAppConfig();
appConfig.getProperty("k1", null); // k1 = v12
appConfig.getProperty("k2", null); // k2 = null
appConfig.getProperty("k3", null); // k3 = v32
//NS-Private,由于没有NS-Private Namespace 所以获取到default value
Config privateConfig = ConfigService.getConfig("NS-Private");
privateConfig.getProperty("k1", "default value");
//NS-Public
Config publicConfig = ConfigService.getConfig("NS-Public");
publicConfig.getProperty("k4", null); // k4 = v5
publicConfig.getProperty("k6", null); // k6 = v6
publicConfig.getProperty("k7", null); // k7 = v7
应用C获取Apollo配置
//application
Config appConfig = ConfigService.getAppConfig();
appConfig.getProperty("k1", null); // k1 = v12
appConfig.getProperty("k2", null); // k2 = null
appConfig.getProperty("k3", null); // k3 = v33
//NS-Private,由于没有NS-Private Namespace 所以获取到default value
Config privateConfig = ConfigService.getConfig("NS-Private");
privateConfig.getProperty("k1", "default value");
//NS-Public,公共类型的Namespace,任何项目都可以获取到
Config publicConfig = ConfigService.getConfig("NS-Public");
publicConfig.getProperty("k4", null); // k4 = v5
publicConfig.getProperty("k6", null); // k6 = v6
publicConfig.getProperty("k7", null); // k7 = v7
ChangeListener 以上代码可以看出,在客户端Namespace映射成一个Config对象,Namespace配置变更的监听器是注册在Config对象上
## 在应用A中监听application的Namespace代码如下
Config appConfig = ConfigService.getAppConfig();
appConfig.addChangeListener(new ConfigChangeListener() {
public void onChange(ConfigChangeEvent changeEvent) {
//do something
}
})
## 在应用A中监听 NS-Private 的 Namespace代码如下
Config privateConfig = ConfigService.getConfig("NS-Private");
privateConfig.addChangeListener(new ConfigChangeListener() {
public void onChange(ConfigChangeEvent changeEvent) {
//do something
}
})
## 在应用A、应用B和应用C中监听NS-Public Namespace代码如下
Config publicConfig = ConfigService.getConfig("NS-Public");
publicConfig.addChangeListener(new ConfigChangeListener() {
public void onChange(ConfigChangeEvent changeEvent) {
//do something
}
})
配置的几大属性
- 配置是独立于程序的只读变量
- 配置首先是独立于程序的,同一份程序在不同的配置下会有不同的行为
- 配置对于程序是只读的,程序通过读取配置来改变自己的行为,程序不应该去改变配置
- 配置伴随应用的整个生命周期
- 配置贯穿于应用的整个生命周期,应用在启动时通过读取配置来初始化,在运行时根据配置来调整行为
- 配置可以有多种加载方式
- 常见的配置加载方式有程序内部hard code、配置文件、环境变量、启动参数、基于数据库等
- 配置需要治理
- 权限控制 由于配置能改变程序行为,不正确的配置甚至能引起灾难,所以对配置的修改必须有比较完善的权限控制
- 不同环境、集群配置管理 同一份程序在不同的环境(开发、测试、生产)、不同的集群(如不同的数据中心)可能有不同的配置,所以需要有完善的环境、集群配置管理
- 框架类组件配置管理 一类比较特殊的配置,通常是由其他团队开发、维护,但是运行时是在业务实际应用内的,所以本质上可以认为框架类组件也是应用的一部分,也需要比较完善的管理方式
基于配置的特殊性,Apollo从设计之初就立志于成为一个有治理能力的配置发布平台,目前提供了以下的特性
- 统一管理不同环境、不同集群的配置
- Apollo提供了一个统一界面集中式管理不同环境(Environment)、不同集群(Cluster)、不同命名空间(Namespace)的配置
- 同一份代码部署在不同的集群,可以有不同的配置
- 通过命名空间可以很方便地支持多个不同应用共享同一份配置,同时还允许应用对共享的配置进行覆盖
- 热发布--配置修改实时生效 用户在Apollo修改完配置并发布后,客户端能实时(1秒)接收到最新的配置,并通知到应用程序
- 版本发布管理 所有的配置发布都有版本概念,从而可以方便地支持配置的回滚
- 灰度发布 支持配置的灰度发布,比如点了发布后,只对部分应用实例生效,等观察一段时间没问题后再推给所有应用实例
- 权限管理、发布审核、操作审计
- 应用和配置的管理都有完善的权限管理机制,对配置的管理还分为了编辑和发布两个环节,从而减少人为的错误
- 所有的操作都有审计日志,可以方便地追踪问题
- 客户端配置信息监控 可以在界面上方便地看到配置在被哪些实例使用
- 提供Java和.Net原生客户端
- 提供了Java和.Net的原生客户端,方便应用集成
- 支持Spring Placeholder, Annotation和Spring Boot的ConfigurationProperties,方便应用使用(需要Spring 3.1.1+)
- 同时提供了Http接口,非Java和.Net应用也可以方便地使用
- 提供开放平台API
- 部署简单
配置获取规则 仅当应用自定义了集群或namespace才需要。有了cluster概念后,配置的规则就显得重要了,比如应用部署在A机房,但是并没有在Apollo新建cluster或者在运行时指定了cluster=SomeCluster,但是并没有在Apollo新建cluster,这时候Apollo的行为是怎样的?下面介绍配置获取的规则
- 应用自身配置的获取规则
当应用使用下面的语句获取配置时,称之为获取应用自身的配置,也就是应用自身的application namespace的配置
Config config = ConfigService.getAppConfig();
这种情况的配置获取规则简而言之如下
- 首先查找运行时cluster的配置(通过apollo.cluster指定)
- 如果没有找到,则查找数据中心cluster的配置
- 如果还是没有找到,则返回默认cluster的配置
图示如下
![](https://pic1.zhimg.com/80/v2-e189962a9aca49a740889bcb7e639114_720w.jpg)
所以,如果应用部署在A数据中心,但是用户没有在Apollo创建cluster,那么获取的配置就是默认cluster(default)的;如果应用部署在A数据中心,同时在运行时指定了apollo.cluster=SomeCluster,但是没有在Apollo创建cluster,那么获取的配置就是A数据中心cluster的配置,如果A数据中心cluster没有配置的话,那么获取的配置就是默认cluster(default)的
- 公共组件配置的获取规则
以`FX.Hermes.Producer`为例,hermes producer是hermes发布的公共组件。当使用下面的语句获取配置时,称之为获取公共组件的配置
Config config = ConfigService.getConfig("FX.Hermes.Producer")
对于这种情况获取配置规则,简而言之如下
- 首先获取当前应用下的`FX.Hermes.Producer`namespace的配置
- 然后获取hermes应用下`FX.Hermes.Producer`namespace的配置
- 上面两部分配置的并集就是最终使用的配置,如有key一样的部分,应当以应用优先
图示如下
![](https://pic3.zhimg.com/80/v2-bbe91efdcaab4074a97b3f2657c04df6_720w.jpg)
通过这种方式实现对框架组件的配置管理,框架组件提供方提供配置的默认值,应用如果有特殊需求可以自行覆盖
Apollo配置中心的设计
总体设计
基础模型
- 用户在配置中心对配置进行修改并发布
- 配置中心通知Apollo客户端有配置更新
- Apollo客户端从配置中心拉取最新的配置、更新本地配置并通知到应用
![](https://pic1.zhimg.com/80/v2-1ea112b728ad79e6f4b9dad168e0ea90_720w.jpg)
架构模块 Apollo包含七个模块:四个功能相关的核心模块和三个辅助服务发现的模块
四个核心模块及其主要功能
- ConfigService 提供配置获取接口、提供配置推送接口、服务于Apollo客户端
- AdminService 提供配置管理接口、提供配置修改发布接口、服务于管理界面Portal
- Client 为应用获取配置,支持实时更新、通过MetaServer获取ConfigService的服务列表、使用客户端软负载SLB方式调用ConfigService
- Portal 配置管理界面、通过MetaServer获取AdminService的服务列表、使用客户端软负载SLB的方式调用AdminService
三个辅助服务发现模块
- Eureka 用于服务发现和注册、ConfigService和AdminService注册实例并定期上报心跳、和ConfigService部署于同一个进程
- MetaServer Portal通过域名访问MetaServer获取AdminService的地址列表、Client通过域名访问MetaServer获取ConfigService的地址列表、相当于一个EurekaProxy、是一个逻辑角色和ConfigService部署于同一个进程
- NginxLB 和域名系统配合,协助Portal访问MetaServer获取AdminService地址列表、和域名系统配合,协助Client访问MetaServer获取ConfigService地址列表、和域名系统配合,协助用户访问Portal进行配置管理
![](https://pic2.zhimg.com/80/v2-2883bdcab6e3bd374bd9bdc56a45c545_720w.jpg)
架构剖析
Apollo架构V1 如果不考虑分布式微服务架构中的服务发现问题,Apollo的最简架构如下图所示
![](https://pic4.zhimg.com/80/v2-d9e63aebd6b8c9039a7409fc4f00f52f_720w.jpg)
要点
- ConfigService是一个独立的微服务,服务于Client进行配置获取
- Client和ConfigService保持长连接,通过一种推拉结合(push & pull)的模式,在实现配置实时更新的同时,保证配置更新不丢失
- AdminService是一个独立的微服务,服务于Portal进行配置管理。Portal通过调用AdminService进行配置管理和发布
- ConfigService和AdminService共享ConfigDB,ConfigDB中存放项目在某个环境中的配置信息。ConfigService/AdminService/ConfigDB三者在每个环境(DEV/FAT/UAT/PRO)中都要部署一份
- Protal有一个独立的PortalDB,存放用户权限、项目和配置的元数据信息。Protal只需部署一份,它可以管理多套环境
Apollo架构 V2 为了保证高可用,ConfigService和AdminService都是无状态以集群方式部署的,这时候就存在一个服务发现的问题:Client怎么找到ConfigService?Portal怎么找到AdminService?为了解决这个问题,Apollo在其架构中引入Eureka服务注册中心组件,实现微服务间的服务注册和发现,更新后的架构如下图所示
![](https://pic3.zhimg.com/80/v2-eb3a3a89f06968aa50368f1470e7528a_720w.jpg)
要点
- ConfigService和AdminService启动后都会注册到Eureka服务注册中心,并定期发送存活心跳
- Eureka采用集群方式部署,使用分布式一致性协议保证每个实例的状态最终一致
Apollo架构V3 Eureka是自带服务发现的Java客户端的,如果Apollo只支持Java客户端接入,不支持其它语言客户端接入的话,那么Client和Portal只需要引入Eureka的Java客户端,就可以实现服务发现功能。发现目标服务后,通过客户端软负载(SLB,例如Ribbon)就可以路由到目标服务实例。这是一个经典的微服务架构,基于Eureka实现服务注册发现+客户端Ribbon配合实现软路由,如下图所示
![](https://pic3.zhimg.com/80/v2-32a5b2e4980d2dcd5a3f7d15b2cdf03a_720w.jpg)
Apollo架构V4 为支持多语言客户端接入,Apollo引入MetaServer角色,它其实是一个Eureka的Proxy,将Eureka的服务发现接口以更简单明确的HTTP接口的形式暴露出来,方便Client/Protal通过简单的HTTPClient就可以查询到ConfigService/AdminService的地址列表。获取到服务实例地址列表之后,再以简单的客户端软负载(Client SLB)策略路由定位到目标实例,并发起调用
另一个问题,MetaServer本身也是无状态以集群方式部署的,那么Client/Protal该如何发现MetaServer呢?一种传统的做法是借助硬件或者软件负载均衡器,在携程采用的是扩展后的NginxLB(Software Load Balancer),由运维为MetaServer集群配置一个域名,指向NginxLB集群,NginxLB再对MetaServer进行负载均衡和流量转发。Client/Portal通过域名+NginxLB间接访问MetaServer集群
引入MetaServer和NginxLB之后的架构如下图
![](https://pic2.zhimg.com/80/v2-7ee8b57f65d64fbf4611abcc278049dd_720w.jpg)
Apollo架构V5 还剩下最后一个环节,Portal也是无状态的以集群方式部署的,用户如何发现和访问Portal?答案也是简单的传统做法,用户通过域名+NginxLB间接访问Portal集群。所以V5版本是包括用户端的最终的Apollo架构全貌,如下图所示
![](https://pic3.zhimg.com/80/v2-24fda44277da3ea01e16c3f91e9de106_720w.jpg)
服务端设计
配置发布后的实时推送设计 在配置中心中,一个重要的功能就是配置发布后实时推送到客户端。下面我们简要看一下这块是怎么设计实现的
![](https://pic2.zhimg.com/80/v2-9b9d12a8a16dd270febb14ccbb704cd5_720w.jpg)
上图简要描述了配置发布的大致过程
- 用户在Portal操作发布配置
- Portal调用Admin Service的接口操作发布
- Admin Service发布配置后,发送ReleaseMessage给各Config Service
- Config Service收到ReleaseMessage后通知对应的客户端
发送ReleaseMessage的实现方式 Admin Service在配置发布后,需要通知所有的Config Service有配置发布,从而Config Service可以通知对应的客户端来拉取最新的配置。从概念上看,这是一个典型的消息使用场景,Admin Service作为Producer发出消息,各个Config Service作为consumer消费消息。通过一个消息组件(Message Queue)就能很好地实现Admin Service和Config Service的解耦。在实现上,Apollo为尽量减少外部依赖,没有采用外部的消息中间件,而是通过数据库实现了一个简单的消息队列
实现方式如下
- Admin Service在配置发布后会往ReleaseMessage表插入一条消息记录,消息内容就是配置发布的AppId+Cluster+Namespace
- Config Service有一个线程会每秒扫描一次ReleaseMessage表,看是否有新的消息记录
- Config Service如果发现有新的消息记录,那么会通知到所有的消息监听器(ReleaseMessageListener),例如NotificationControllerV2
- 消息监听器得到配置发布的AppId+Cluster+Namespace后,会通知对应的客户端
示意图如下
![](https://pic1.zhimg.com/80/v2-46d7da61ceb6cdc93a34e4ddab2dff6c_720w.jpg)
Config Service通知客户端的实现方式 消息监听器在得知有新的配置发布后是如何通知到客户端的呢?其实现方式如下
- 客户端会发起一个Http请求到Config Service的notifications/v2接口,也就是NotificationControllerV2
- NotificationControllerV2不会立即返回结果,而是通过Spring DeferredResult把请求挂起
- 如果在60秒内没有该客户端关心的配置发布,那么会返回Http状态码304给客户端
- 如果有该客户端关心的配置发布,NotificationControllerV2会调用DeferredResult的setResult方法,传入有配置变化的namespace信息,同时该请求会立即返回。客户端从返回的结果中获取到配置变化的namespace后,会立即请求Config Service获取该namespace的最新配置
客户端设计
![](https://pic4.zhimg.com/80/v2-011769adaa80cab8d33e1a746bc46fd3_720w.jpg)
上图简要描述了Apollo客户端的实现原理
- 客户端和服务端保持了一个长连接(通过Http Long Polling实现),从而能第一时间获得配置更新的推送
- 客户端还会定期从Apollo配置中心服务端拉取应用的最新配置
- 这是一个fallback机制,为了防止推送机制失效导致配置不更新
- 客户端定时拉取会上报本地版本,所有一般情况下,对于定时拉取的操作,服务端都会返回304-Not Modified
- 定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property: apollo.refreshInterval来覆盖,单位为分钟
- 客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中
- 客户端会把从服务端获取到的配置在本地文件系统缓存一份,在遇到服务不可以或网络不通时,依然能从本地恢复配置
- 应用程序可以从Apollo客户端获取最新的配置、订阅配置更新通知
Apollo使用指南
名称解析
- 普通应用 独立运行的程序,如Web应用程序、带有main函数的程序
- 公共组件 指发布的类库、客户端程序,不会自己独立运行,如Java的jar包
普通应用接入指南
创建项目
1.进入apollo-portal主页
2.点击"创建项目"
3.输入项目信息
部门 选择应用所在的部门
应用AppId 用来表示应用身份的唯一id,格式为string,需要和客户端app.properties中配置的app.id对应
应用名称 应用名,仅用于界面展示
应用负责人 选择的人默认会成为该项目的管理员,具备项目权限管理、集群创建、Namespace创建等权限
4.点击提交 创建成功后默认会跳转到,项目首页
项目权限分配
项目管理员权限 项目管理员可以管理项目的权限分配、可以创建集群、可以创建Namespace
配置编辑发布权限
编辑权限允许用户在Apollo界面上创建、修改、删除配置
发布权限允许用户在Apollo界面上发布、回滚配置
添加配置项
编辑配置需要拥有这个Namespace的编辑权限,如果发现没有新增配置按钮,可以找项目管理员授权
发布配置
配置只有在发布后才会真的被应用使用到,所以在编辑完配置后,需要发布配置。发布配置需要拥有这个Namespace的发布权限,如果发现没有发布按钮,可以找项目管理员授权
应用读取配置
配置发布成功后就可以通过Apollo客户端读取到配置了。Apollo目前提供Java客户端,如果使用其他语言,也可以通过直接访问Http接口获取配置
回滚已发布配置
如果发现已发布的配置有问题,可以通过点击"回滚"按钮来将客户端读取到的配置回滚到上一个发布版本。这里的回滚机制类似于发布系统,发布系统中的回滚操作是将部署到机器上的安装包回滚到上一个部署的版本,但代码仓库中的代码是不会回滚的,从而开发可以在修复代码后重新发布。Apollo中的回滚也是类似的机制,点击回滚后是将发布到客户端的配置回滚到上一个已发布版本,也就是说客户端读取到的配置会恢复到上一个版本,但页面上编辑状态的配置是不会回滚的,从而开发可以在修复配置后重新发布
公共组件接入步骤
公共组件接入步骤几乎与普通应用接入一致,唯一的区别是公共组件需要建立自己的唯一Namespace
1.创建项目
2.项目管理员权限
3.创建Namespace
4.添加配置项
5.发布配置
6.应用读取配置
应用覆盖公共组件配置步骤
1.关联公共组件Namespace
2.覆盖公共组件配置
3.发布配置
多个AppId共享同一份配置
在一些情况下,尽管应用本身不是公共组件,但还是需要在多个AppId之间共用同一份配置,这种情况下如果希望实现多个AppId使用同一份配置的话,基本概念和公共组件的配置是一致的。具体来说,就是在其中一个AppId下创建一个namespace,写入公共的配置信息,然后在各个项目中读取该namespace的配置即可;如果某个AppId需要覆盖公共的配置信息,那么在该AppId下关联公共的namespace并写入需要覆盖的配置即可
应用接入策略 这里考虑非Java语言客户端接入--直接通过Http接口获取配置
- 通过带缓存的HTTP接口从Apollo读取配置
该接口会从缓存中获取配置,适合频率较高的配置拉取请求,如简单的30秒轮询一次配置。由于缓存最多会有一秒的延迟,所以如果需要配合配置推送通知实现实时更新配置的话,请参考不带缓存的HTTP接口从Apollo读取配置
HTTP接口说明
URL {config_server_url}/configfiles/json/{appId}/{clusterName}/{namespaceName}?ip={clientIp}
Method GET
参数说明
![](https://pic3.zhimg.com/80/v2-d2909f1e78ce554d941cbd958a1b614a_720w.jpg)
HTTP接口返回格式该HTTP接口返回的是JSON格式、UTF-8编码,包含了对应namespace中所有的配置项。返回内容Sample如下
{
"portal.elastic.document.type":"biz",
"portal.elastic.cluster.name":"hermes-es-fws"
}
TIPS 通过{config_server_url}/configfiles/{appId}/{clusterName}/{namespaceName}?ip={clientIp}可以获取到properties形式的配置
- 不带缓存的HTTP接口从Apollo读取配置
该接口会直接从数据库中获取配置,可以配合配置推送通知实现实时更新配置
URL {config_server_url}/configs/{appId}/{clusterName}/{namespaceName}?releaseKey={releaseKey}&ip={clientIp}
Method GET
参数说明
![](https://pic4.zhimg.com/80/v2-6284bb505251ecc7ac915976b4bc247f_720w.jpg)
该HTTP接口返回的是JSON格式、UTF-8编码。如果配置没有变化(传入的releaseKey和服务端的相等),则返回HttpStatus 304,Response Body为空;如果配置有变化,则会返回HttpStatus 200,Response Body为对应namespace的meta信息以及其中所有的配置项。返回内容Sample如下
{
"appId": "100004458",
"cluster": "default",
"namespaceName": "application",
"configurations": {
"portal.elastic.document.type":"biz",
"portal.elastic.cluster.name":"hermes-es-fws"
},
"releaseKey": "20170430092936-dee2d58e74515ff3"
}
应用感知配置更新 Apollo提供了基于Http long polling的配置更新推送通知,第三方客户端可以看自己实际的需求决定是否需要使用这个功能。如果对配置更新时间不是那么敏感的话,可以通过定时刷新来感知配置更新,刷新频率可以视应用自身情况来定,建议在30秒以上。如果需要做到实时感知配置更新(1秒)的话,可以参考下面的文档实现配置更新推送的功能
配置更新推送实现思路 建议参考Apollo的Java实现RemoteConfigLongPollService.java
- 初始化 首先需要确定哪些namespace需要配置更新推送,Apollo的实现方式是程序第一次获取某个namespace的配置时就会来注册一下,我们就知道有哪些namespace需要配置更新推送了。初始化后的结果就是得到一个notifications的Map,内容是namespaceName -> notificationId(初始值为-1)。运行过程中如果发现有新的namespace需要配置更新推送,直接塞到notifications这个Map里面即可
- 请求服务 有了notifications这个Map之后,就可以请求服务了。这里先描述一下请求服务的逻辑,具体的URL参数和说明请参见后面的接口说明
1.请求远端服务,带上自己的应用信息以及notifications信息
2.服务端针对传过来的每一个namespace和对应的notificationId,检查notificationId是否是最新的
3.如果都是最新的,则保持住请求60秒,如果60秒内没有配置变化,则返回HttpStatus 304。如果60秒内有配置变化,则返回对应namespace的最新notificationId, HttpStatus 200
4.如果传过来的notifications信息中发现有notificationId比服务端老,则直接返回对应namespace的最新notificationId, HttpStatus 200
5.客户端拿到服务端返回后,判断返回的HttpStatus
6.如果返回的HttpStatus是304,说明配置没有变化,重新执行第1步
7.如果返回的HttpStauts是200,说明配置有变化,针对变化的namespace重新去服务端拉取配置,参见1.3 通过不带缓存的Http接口从Apollo读取配置。同时更新notifications map中的notificationId。重新执行第1步
HTTP接口说明
URL {config_server_url}/notifications/v2?appId={appId}&cluster={clusterName}¬ifications={notifications}
Method GET
参数说明
![](https://pic3.zhimg.com/80/v2-845816fb5bab95250fa20c7976915696_720w.jpg)
TIPS 由于服务端会hold住60秒,所以请确保客户端访问服务端的超时时间要大于60秒;记得对参数进行URL Encode
HTTP返回格式 该Http接口返回的是JSON格式、UTF-8编码,包含了有变化的namespace和最新的notificationId。返回内容Sample如下
[
{
"namespaceName": "application",
"notificationId": 101
}
]
分布式部署指南
官方展示的部署策略,生产环境部署一套Apollo-Portal+ApolloPortalDB,其他环境(PRO、UAT、FAT、DEV)单独部署MetaServer+AdminService+ConfigService,使用独立数据库ApolloConfigDB及应用服务;MetaServer和Config Service部署在同一个JVM进程内,Admin Service部署在同一台服务器的另一个JVM进程内。部署示例如下图
![](https://pic4.zhimg.com/80/v2-577138252b651372108974efbba6db53_720w.jpg)
网络策略 分布式部署的时候,apollo-configservice和apollo-adminservice需要把自己的IP和端口注册到Meta Server(apollo-configservice本身)。Apollo客户端和Portal会从Meta Server获取服务的地址(IP+PORT),然后通过服务地址直接访问。apollo-configservice和apollo-adminservice是基于内网可信网络设计的,所以出于安全考虑,请不要将apollo-configservice和apollo-adminservice直接暴露在公网
部署步骤
创建数据库 Apollo服务端依赖于MYSQL数据库,所以需要事先创建并完成初始化
获取安装包 Apollo服务端安装包共3个: Apollo-AdminService、Apollo-ConfigService、Apollo-Portal
部署Apollo服务端 获取安装包后就可以部署到测试和生产环境
小结
文章较为全面介绍开源分布式配置中心Apollo的设计、使用、应用接入及部署方法,目前客户端只有Java和.Net版本,其他语言客户端的接入可以通过HTTP接口的方式定时拉取更新配置或通过Http Long Polling机制实时推送,实现应用感知配置更新