Apollo 原理总结
概念
- App: App信息
- AppNamespace: App下Namespace的元信息
- Cluster: 集群信息
- Namespace: 集群下的namespace
- Item: Namespace的配置,每个Item是一个key, value组合
- Release: Namespace发布的配置,每个发布包含发布时该Namespace的所有配置
- Commit: Namespace下的配置更改记录
- Audit: 审计信息,记录用户在何时使用何种方式操作了哪个实体。
Apollo架构
- 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?
- 提供了完整的服务注册和发现实现,用起来省心;
- 项目基础是SpringCloud,已经有应用Eureka的基础;
Meta Server部署在哪里?
Meta Server只是一个逻辑角色,在部署时和Config Service是在一个JVM进程中的,所以IP、端口和Config Service一致
服务端实现原理
服务端的主要任务是维护配置信息,以及将配置信息推送到客户端。
服务端推送大体流程
- 用户在Portal操作配置发布
- Portal调用Admin Service的接口操作发布
- Admin Service发布配置后,发送ReleaseMessage给各个Config Service
- Config Service收到ReleaseMessage后,通知对应的客户端
发送ReleaseMessage给Config Service的过程
用户操作配置发布后,Admin Service会往ReleaseMessage表插入一条消息记录,然后Config Service定时轮询这张表来消费消息。
ApolloPortol会调Admin服务发出消息,这时,Admin Service作为Producer发出消息,各个ConfigService作为Consumer消费消息,为了减少对外部的依赖,Apollo发送消息的功能是通过数据库自己实现的一个简单的消息队列。
- Admin操作Release:
com.ctrip.framework.apollo.portal.controller.ReleaseController#createRelease
在Admin Service的后台操作界面上可以看到Release操作入口。 - Admin向ReleaseMessage表插入一条消息记录:
com.ctrip.framework.apollo.adminservice.controller.ReleaseController#publish
该消息的内容就是配置发布的AppId+Cluster+Namespace。
注意在“发消息”前,先把发布信息存到了Release表中。 - 定时扫描消息,
扫描消息:com.ctrip.framework.apollo.biz.message.ReleaseMessageScanner
定时任务逻辑:批量处理,每次扫描500条,每条消息分别触发所有消息监听器(ReleaseMessageListener)。
定时任务线程池配置:每100毫秒执行一次,core线程数只有1,但是总线程数为Integer.MAX_INT。 - Config Service通知客户端
代码入口:NotificationControllerV2#handleMessage
注意NotificationControllerV2这个Controller本身也是个消息监听器。
Config Service会从消息中获取配置发布的AppId+Cluster+Namespace,然后通知客户端(通知客户端的过程见下面)。
Config Service通知客户端的过程
- 客户端发起一个HTTP请求到Config Service
入口:/notifications/v2,对应NotificationControllerV2(注意和上面的消息监听器是同一个类)。 - Config Service将请求挂起
通过Spring 的 DeferredResult将请求挂起,默认等待60秒。
如果在等待期间有该客户端关注的配置(Namespace)发布,则NotificationControllerV2会调用DeferredResult#setResult传入变化的Namespace信息,同时该请求也会立刻返回。
反之,如果60秒内都没有该客户端关注的配置发布,则返回HTTP状态码304给客户端。 - 客户端请求最新的Namespace配置
如果Config Service返回了配置信息、客户端获取到变化的Namespace信息后,客户端就会立即请求Config Service获取该Namespace的最新配置。
客户端实现原理
客户端主要任务是从Config Service获取配置信息(Push和Pull都有),并在本地维护一个配置文件缓存。
客户端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来覆盖,单位为分钟。
客户端本地对配置的维护
- 客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中
- 客户端会把从服务端获取到的配置在本地文件系统缓存一份
在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置 - 应用程序可以从Apollo客户端获取最新的配置、订阅配置更新通知