Apollo 原理总结

概念

  • App: App信息
  • AppNamespace: App下Namespace的元信息
  • Cluster: 集群信息
  • Namespace: 集群下的namespace
  • Item: Namespace的配置,每个Item是一个key, value组合
  • Release: Namespace发布的配置,每个发布包含发布时该Namespace的所有配置
  • Commit: Namespace下的配置更改记录
  • Audit: 审计信息,记录用户在何时使用何种方式操作了哪个实体。

Apollo架构

Apollo架构——图片来自Apollo-github

  • Apollo Client:为应用提供配置查询功能;
  • Apollo Config Service:提供配置的读取、推送等功能,服务对象是 Apollo Client;
  • Apollo Portal:Apollo管理界面,为开发者提供配置修改功能;
  • Apollo Admin Service:提供配置修改、发布等功能,服务对象是Apollo Portal。

服务发现和负载均衡

在Apollo中,Config Service和Admin Service都是多实例无状态部署的,需要将自己注册到Eureka
Eureka负责服务发现,在Eureka之上Apollo又架了一层Meta Server用于封装Eureka的服务发现接口:

  • Client通过域名访问Meta Server获取Config Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Client侧会做load balance错误重试
  • Portal通过域名访问Meta Server获取Admin Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Portal侧会做load balance错误重试

为什么Apollo使用Eureka而不是别的服务发现组件,比如ZooKeeper?

  1. 提供了完整的服务注册和发现实现,用起来省心;
  2. 项目基础是SpringCloud,已经有应用Eureka的基础;

Meta Server部署在哪里?
Meta Server只是一个逻辑角色,在部署时和Config Service是在一个JVM进程中的,所以IP、端口和Config Service一致

服务端实现原理

服务端的主要任务是维护配置信息,以及将配置信息推送到客户端。

服务端推送大体流程

  1. 用户在Portal操作配置发布
  2. Portal调用Admin Service的接口操作发布
  3. Admin Service发布配置后,发送ReleaseMessage给各个Config Service
  4. Config Service收到ReleaseMessage后,通知对应的客户端

发送ReleaseMessage给Config Service的过程

Apollo配置信息的发布——图片来自Apollo-github
用户操作配置发布后,Admin Service会往ReleaseMessage表插入一条消息记录,然后Config Service定时轮询这张表来消费消息。
ApolloPortol会调Admin服务发出消息,这时,Admin Service作为Producer发出消息,各个ConfigService作为Consumer消费消息,为了减少对外部的依赖,Apollo发送消息的功能是通过数据库自己实现的一个简单的消息队列

  1. Admin操作Release:com.ctrip.framework.apollo.portal.controller.ReleaseController#createRelease
    在Admin Service的后台操作界面上可以看到Release操作入口。
  2. Admin向ReleaseMessage表插入一条消息记录:com.ctrip.framework.apollo.adminservice.controller.ReleaseController#publish
    该消息的内容就是配置发布的AppId+Cluster+Namespace。
    注意在“发消息”前,先把发布信息存到了Release表中。
  3. 定时扫描消息,
    扫描消息:com.ctrip.framework.apollo.biz.message.ReleaseMessageScanner
    定时任务逻辑:批量处理,每次扫描500条,每条消息分别触发所有消息监听器(ReleaseMessageListener)。
    定时任务线程池配置:每100毫秒执行一次,core线程数只有1,但是总线程数为Integer.MAX_INT。
  4. Config Service通知客户端
    代码入口:NotificationControllerV2#handleMessage
    注意NotificationControllerV2这个Controller本身也是个消息监听器。
    Config Service会从消息中获取配置发布的AppId+Cluster+Namespace,然后通知客户端(通知客户端的过程见下面)。

Config Service通知客户端的过程

  1. 客户端发起一个HTTP请求到Config Service
    入口:/notifications/v2,对应NotificationControllerV2(注意和上面的消息监听器是同一个类)。
  2. Config Service将请求挂起
    通过Spring 的 DeferredResult将请求挂起,默认等待60秒。
    如果在等待期间有该客户端关注的配置(Namespace)发布,则NotificationControllerV2会调用DeferredResult#setResult传入变化的Namespace信息,同时该请求也会立刻返回。
    反之,如果60秒内都没有该客户端关注的配置发布,则返回HTTP状态码304给客户端。
  3. 客户端请求最新的Namespace配置
    如果Config Service返回了配置信息、客户端获取到变化的Namespace信息后,客户端就会立即请求Config Service获取该Namespace的最新配置。

客户端实现原理

客户端主要任务是从Config Service获取配置信息(Push和Pull都有),并在本地维护一个配置文件缓存。
Apollo配置在客户端的维护——图片来自Apollo-github

客户端long-polling

客户端会和服务端维持一个长连接,以及时接收到配置的变更信息。
入口:com.ctrip.framework.apollo.internals.RemoteConfigLongPollService#startLongPolling
客户端长轮询的是Config Service的配置变更通知接口。当有新通知时就会触发RemoteConfigRepository,立即轮询Config Service的配置读取/configs/{appId}/{clusterName}/{namespace:.+}接口。

客户端定时Pull

客户端定时从Config Service拉取应用的配置信息,使用的接口和上面的long-polling一样:/configs/{appId}/{clusterName}/{namespace:.+}
入口:RemoteConfigRepository#scheduleLongPollingRefresh
这是一个fallback机制,主要目的是防止推送机制失效导致配置不更新。
客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified。
定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property: apollo.refreshInterval来覆盖,单位为分钟。

客户端本地对配置的维护

  1. 客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中
  2. 客户端会把从服务端获取到的配置在本地文件系统缓存一份
    在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置
  3. 应用程序可以从Apollo客户端获取最新的配置、订阅配置更新通知

参考

  1. Java客户端使用指南
  2. Apollo配置中心设计