Apollo服务端设计原理剖析
本文摘自于《Spring Cloud微服务 入门 实战与进阶》一书。
1 配置释出后的实时推送设计
配置中心最重要的一个特性就是实时推送了,正因为有这个特性,我们可以依赖配置中心做很多事情。在我自己开发的Smconf这个配置中心,Smconf是依赖于Zookeeper的Watch机制来实现实时推送。
来源于Apollo 文件
上图简要描述了配置释出的大致过程:
使用者在Portal中进行配置的编辑和释出Portal会呼叫Admin Service提供的界面进行释出操作Admin Service收到请求后,传送ReleaseMessage给各个Config Service,通知Config Service配置发生变化Config Service收到ReleaseMessage后,通知对应的客户端,基于Http长连线实现
2 传送ReleaseMessage的实现方式
ReleaseMessage讯息是通过Mysql实现了一个简单的讯息伫列。之所有没有采用讯息中介软件,是为了让Apollo在部署的时候尽量简单,尽可能减少外部依赖。
release-message-design.png
上图简要描述了传送ReleaseMessage的大致过程:
Admin Service在配置释出后会往ReleaseMessage表插入一条讯息记录Config Service会启动一个执行绪定时扫描ReleaseMessage表,去检视是否有新的讯息记录Config Service发现有新的讯息记录,那么就会通知到所有的讯息监听器讯息监听器得到配置释出的资讯后,则会通知对应的客户端
3 Config Service通知客户端的实现方式
通知是采用基于Http长连线实现,主要分为下面几个步骤:客户端会发起一个Http请求到Config Service的notifications/v2界面v2界面通过Spring DeferredResult把请求挂起,不会立即返回如果在60秒内没有该客户端关心的配置释出,那么会返回Http状态码304给客户端如果发现配置有修改,则会呼叫DeferredResult的setResult方法,传入有配置变化的namespace资讯,同时该请求会立即返回客户端从返回的结果中获取到配置变化的namespace后,会立即请求Config Service获取该namespace的最新配置
4 源代码解析实时推送设计
Apollo推送这块程式码比较多,就不在本书中详细分析了,我把推送这块的程式码稍微简化了下,给大家进行讲解,这样理解起来会更容易。当然我这边会比较简单,很多细节就不做考虑了,只是为了能够让大家明白Apollo推送的核心原理。传送ReleaseMessage的逻辑我们就写一个简单的界面,用伫列储存,测试的时候就呼叫这个界面模拟配置有更新,传送ReleaseMessage讯息。

讯息传送之后,前面我们有讲过Config Service会启动一个执行绪定时扫描ReleaseMessage表,去检视是否有新的讯息记录,然后取通知客户端,这边我们也启动一个执行绪去扫描:

循环去读取NotificationControllerV2中的伫列,如果有讯息的话就构造一个ReleaseMessage的物件,然后呼叫NotificationControllerV2中的handleMessage()方法进行讯息的处理。
ReleaseMessage就一个字段,模拟讯息内容:

接下来,我们看handleMessage做了什么样的工作
NotificationControllerV2实现了ReleaseMessageListener界面,ReleaseMessageListener中定义了handleMessage()方法。

handleMessage就是当配置发生变化的时候,通知的讯息监听器,讯息监听器得到配置释出的资讯后,则会通知对应的客户端:

Apollo的实时推送是基于Spring DeferredResult实现的,在handleMessage()方法中可以看到是通过deferredResults获取DeferredResult,deferredResults就是第一行的Multimap,Key其实就是讯息内容,Value就是DeferredResult的业务包装类DeferredResultWrapper,我们来看下DeferredResultWrapper的程式码:

通过setResult()方法设定返回结果给客户端,以上就是当配置发生变化,然后通过讯息监听器通知客户端的原理,那么客户端是在什么时候接入的呢?

NotificationControllerV2中提供了一个/getConfig的界面,客户端在启动的时候会呼叫这个界面,这个时候会执行getApolloConfigNotifications()方法去获取有没有配置的变更资讯,如果有的话证明配置修改过,直接就通过deferredResultWrapper.setResult(newNotifications);返回结果给客户端了,客户端收到结果后重新拉取配置的资讯进行覆盖本地的配置。
如果getApolloConfigNotifications()方法没有返回配置修改的资讯,证明配置没有发生修改,就将DeferredResultWrapper物件新增到deferredResults中,等待后续配置发生变化时讯息监听器进行通知。
同时这个请求就会挂起,不会立即返回,挂起是通过DeferredResultWrapper中的下面的程式码实现的:

在建立DeferredResult物件的时候指定了超时的时间和超时后返回的响应码,如果60秒内没有讯息监听器进行通知,那么这个请求就会超时,超时后客户端就收到的响应码就是304。
整个Config Service的流程就走完了,接下来我们看客户端是怎么实现的,我们简单的写个测试类模拟客户端注册:


首先启动/getConfig界面所在的服务,然后启动客户端,客户端就会发起注册请求,如果有修改直接获取到结果,进行配置的更新操作。如果无修改,请求会挂起,这边客户端设定的读取超时时间是90秒,大于服务端的60秒超时时间。
每次收到结果后,无论是有修改还是没修改,都必须重新进行注册,通过这样的方式就可以达到配置实时推送的效果。
我们可以呼叫之前写的/addMsg界面来模拟配置发生变化,呼叫之后客户端就能马上得到返回结果。
本文摘自于《Spring Cloud微服务 入门 实战与进阶》一书。
去年出版的《Spring Cloud微服务:全栈技术与案例解析》一书,得到了大家的支援以及反馈,基于大家的反馈,重新进行了更正和改进。
基于比较稳定的 Spring Cloud Finchley.SR2 版本和 Spring Boot 2.0.6.RELEASE 版本编写。
同时将示列程式码进行标准的归档,之前的都在一起,不方便读者参考和执行。

同时还增加了像Apollo,Spring Cloud Gateway,生产实践经验等新的内容。
https://github.com/yinjihuan/spring-cloud