如下即是Apollo的基础模型:
下图是Apollo架构模块的概览,详细说明可以参考Apollo配置中心架构剖析。
上图简要描述了Apollo的总体设计,我们可以从下往上看:
Config Service提供配置的读取、推送等功能,服务对象是Apollo客户端
sequenceDiagram
Client ->> Config Service: request
Config Service ->> ConfigDB: request
ConfigDB -->> Config Service: ack
Config Service -->> Client: ack
Admin Service提供配置的修改、发布等功能,服务对象是Apollo Portal(管理界面)
sequenceDiagram
Portal ->> Admin Service: r/w, publish appId/cluster/namespace
Admin Service ->> ConfigDB: r/w, publish appId/cluster/namespace
ConfigDB -->> Admin Service: ack
Admin Service -->> Portal: ack
Config Service和Admin Service都是多实例、无状态部署,所以需要将自己注册到Eureka中并保持心跳
在Eureka之上我们架了一层Meta Server用于封装Eureka的服务发现接口
sequenceDiagram
Client or Portal ->> Meta Server: discovery service's instances
Meta Server ->> Eureka: discovery service's instances
Eureka -->> Meta Server: service's instances
Meta Server -->> Client or Portal: service's instances
Client通过域名访问Meta Server获取Config Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Client侧会做load balance、错误重试
sequenceDiagram
Client ->> Meta Server: discovery Config Service's instances
Meta Server -->> Client: Config Service's instances(Multiple IP+Port)
loop until success
Client ->> Client: load balance choose a Config Service instance
Client ->> Config Service: request
Config Service -->> Client: ack
end
Portal通过域名访问Meta Server获取Admin Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Portal侧会做load balance、错误重试
sequenceDiagram
Portal ->> Meta Server: discovery Admin Service's instances
Meta Server -->> Portal: Admin Service's instances(Multiple IP+Port)
loop until success
Portal ->> Portal: load balance choose a Admin Service instance
Portal ->> Config Service: request
Config Service -->> Portal: ack
end
为了简化部署,我们实际上会把Config Service、Eureka和Meta Server三个逻辑角色部署在同一个JVM进程中
graph
subgraph JVM Process
1[Config Service]
2[Eureka]
3[Meta Server]
end
实际部署的架构可以参考部署架构
为什么我们采用Eureka作为服务注册中心,而不是使用传统的zk、etcd呢?我大致总结了一下,有以下几方面的原因:
提供配置获取接口
sequenceDiagram
Client ->> Config Service: get content of appId/cluster/namespace
opt if namespace is not cached
Config Service ->> ConfigDB: get content of appId/cluster/namespace
ConfigDB -->> Config Service: content of appId/cluster/namespace
end
Config Service -->> Client: content of appId/cluster/namespace
提供配置更新推送接口(基于Http long polling)
接口服务对象为Apollo客户端
在配置中心中,一个重要的功能就是配置发布后实时推送到客户端。下面我们简要看一下这块是怎么设计实现的。
上图简要描述了配置发布的大致过程:
Admin Service在配置发布后,需要通知所有的Config Service有配置发布,从而Config Service可以通知对应的客户端来拉取最新的配置。
从概念上来看,这是一个典型的消息使用场景,Admin Service作为producer发出消息,各个Config Service作为consumer消费消息。通过一个消息组件(Message Queue)就能很好的实现Admin Service和Config Service的解耦。
在实现上,考虑到Apollo的实际使用场景,以及为了尽可能减少外部依赖,我们没有采用外部的消息中间件,而是通过数据库实现了一个简单的消息队列。
实现方式如下:
示意图如下:
上一节中简要描述了NotificationControllerV2是如何得知有配置发布的,那NotificationControllerV2在得知有配置发布后是如何通知到客户端的呢?
实现方式如下:
notifications/v2
接口,也就是NotificationControllerV2,参见RemoteConfigLongPollService上图简要描述了Apollo客户端的实现原理:
apollo.refreshInterval
来覆盖,单位为分钟。Apollo除了支持API方式获取配置,也支持和Spring/Spring Boot集成,集成原理简述如下。
Spring从3.1版本开始增加了ConfigurableEnvironment
和PropertySource
:
需要注意的是,PropertySource之间是有优先级顺序的,如果有一个Key在多个property source中都存在,那么在前面的property source优先。
所以对上图的例子:
在理解了上述原理后,Apollo和Spring/Spring Boot集成的手段就呼之欲出了:在应用启动阶段,Apollo从远端获取配置,然后组装成PropertySource并插入到第一个即可,如下图所示:
相关代码可以参考PropertySourcesProcessor
场景 | 影响 | 降级 | 原因 |
---|---|---|---|
某台Config Service下线 | 无影响 | Config Service无状态,客户端重连其它Config Service | |
所有Config Service下线 | 客户端无法读取最新配置,Portal无影响 | 客户端重启时,可以读取本地缓存配置文件。如果是新扩容的机器,可以从其它机器上获取已缓存的配置文件,具体信息可以参考Java客户端使用指南 - 1.2.3 本地缓存路径 | |
某台Admin Service下线 | 无影响 | Admin Service无状态,Portal重连其它Admin Service | |
所有Admin Service下线 | 客户端无影响,Portal无法更新配置 | ||
某台Portal下线 | 无影响 | Portal域名通过SLB绑定多台服务器,重试后指向可用的服务器 | |
全部Portal下线 | 客户端无影响,Portal无法更新配置 | ||
某个数据中心下线 | 无影响 | 多数据中心部署,数据完全同步,Meta Server/Portal域名通过SLB自动切换到其它存活的数据中心 | |
数据库宕机 | 客户端无影响,Portal无法更新配置 | Config Service开启配置缓存后,对配置的读取不受数据库宕机影响 |
Apollo客户端和服务端目前支持CAT自动打点,所以如果自己公司内部部署了CAT的话,只要引入cat-client后Apollo就会自动启用CAT打点。
如果不使用CAT的话,也不用担心,只要不引入cat-client,Apollo是不会启用CAT打点的。
Apollo也提供了Tracer相关的SPI,可以方便地对接自己公司的监控系统。
更多信息,可以参考v0.4.0 Release Note
可以参考@hepyu贡献的apollo-skywalking-pro样例。
从1.5.0版本开始,Apollo服务端支持通过/prometheus
暴露prometheus格式的metrics,如http://${someIp:somePort}/prometheus