本文主要了NATS的工作原理,以及它在CloudFoundry是如何与其它组件协同工作的。
在CloudFoundry系统中,包含许多内部组件,如CloudController、DEA、Router等等,如果某个组件(如DEA)需要去订阅其它组件的消息,那么DEA需要找到其它每一个组件并在其上注册事件,如果需要在每一个组件上注册多个事件,那么每个事件都需要执行一次上述过程(找到该组件并注册事件),这将增大系统的复杂度,并且使系统更加耦合,从而降低了系统的可扩展性。而NATS则解决了CloudFoundry的内部组件的通讯问题。
NATS是一个轻量级的分布式的消息订阅发布系统,CloudFounery使用NATS作为内部组件的通讯系统,从而进行基于主题的消息订阅和发布。NATS是基于EventMachine,由Ruby实现的,它由NATS客户端和服务端组成。客户端负责向服务端发送指令(订阅主题,发布主题等等),而服务端则接收并处理来自客户端的指令,并做出响应。目前,NATS客户端同时也支持node.js、Go、Java以及Java-Spring的实现。
NATS是一个典型的网络应用程序,也就是说NATS客户端和服务器端是通过计算机网络通信的,NATS在此基础之上定义了属于自己的一套应用层协议。如下所示:
以下面的NATS客户端和服务端代码为例,简单介绍一下NATS的整个生命周期。
NATS Server:
图1 NATS Sample Server
NATS Client:
图2 NATS Sample Client
下面是NATS的完整的生命周期时序图。
图3 NATS 生命周期
首先NATS Client需要建立与Server端的TCP连接,之后NATS Server会发送一个“INFO”消息至客户端,其中会包含Server端的详细信息,包括NATS Server的ID,host, port, version,以及是否需要用户验证,是否支持SSL等等;
之后NATS Client会发送一个“CONNECT”消息至Server端,其中包含客户端的详细信息,Server端会根据配置来验证该客户端; 如果通过,会返回一个“+OK”消息;
此时,TCP连接已经成功建立,Client会发送一个“PING”消息给Server,而Server会直接返回“PONG”消息给Client,之后Client可以订阅或是分发事件;
NATS Client订阅一个主题为“A.B.C”的消息,它的Subject Identifier是2(每次订阅时该ID会自增+1);
之后NATS Client会发布一个主题为“A.B.C”的消息,消息长度为8,内容为“I‘m Yuan”;
最后,Server端会根据主题匹配算法,找到相应的订阅方,然后发送一个“MSG”消息给订阅方,并且带上主题“A.B.C”和主题ID“2”;
此时,在NATS Client端,就可以根据主题ID找到相应的订阅者,执行相应的callback。
当一个NATS Client向NATS Server订阅一个消息后,Server会保留该订阅者的信息,包括当前主题,主题ID等等(具体参见图4),之后会根据主题匹配算法找到相应订阅者,并发布消息。
.
分隔,如A.B.C
;A.*.C
;A.>
;例子如下所示:
插入主题 | 匹配主题 | 不匹配主题 |
---|---|---|
A.B.C | A.B.C | A.D.C |
A.*.C | A.B.C/A.D.C/A.*.C | A.B.D |
A.B.> | A.B.C/A.B.C.D.E.F.G | A.C.D.E.F.G |
在NATS服务器端内部,是由sublist.rb
负责主题匹配工作的,它是由SublistLevel和SublistNode两个结构体组成的树形结构,如图4所示。SublistLevel主要维护的是token(包括二个特殊的通配符)至SublistNode节点的映射,而SublistNode主要保存的是订阅者信息,以及与下级SublistLevel的关系。Subscriber保存的是订阅方的信息,包含当前的Connection,主题,主题ID等等。
图4 Sublist、Subscriber 结构
当NATS Server收到基于主题“A.B”的PUB消息后,它会把“A.B”主题拆分成二个token,然后将每个token中的内容从Sublist的Root Level开始从上至下开始进行匹配。如果每个Token都能匹配到节点,那么最后一个Token对应的SublistNode就是这个主题的订阅者。
图5 Sublist Workflow
Router组件主要是对所有的请求进行路由,包括管理请求和App应用请求,其它组件如果想让Router路由请求由需要先向Router注册。过程如下:
router.register
和router.unreigster
这两个channel,等待其它组件向这二个channel发送消息;router.start
channel发送消息,其它需要向Router注册的组件(如DEA)在启动时都需要订阅router.start
这个channel,一旦接收到该消息,都需要将需要注册的信息(DEA会包括host, port, uris, tags, app等等)向router.register
channel发送;router.register
消息后,会立即更新路由消息;同时,如果Router收到router.unregister
消息,那么它会立即删除该路由信息;另外,Router还订阅了router.greet
这个channel,主要用于如果某个组件比NATS启动晚时,从而没有收到router.start
消息时,也可以让该组件向Router注册路由信息,DEA也采用了这种方式来保证路由注册。
Router同时每隔一段时间(默认是0,所以不会发送)也会通过router.active_apps
这个channel向其它组件发送当前active的app id信息。
HealthManager组件主要是从DEA拿到app的各类运行信息,然后进行统计、分析和报警,如果某个instance出现异常,HM可以通过NATS通知CC启用或停止该instance。
HM订阅的DEA状态相关channel:
HM订阅的app状态查询相关的channel:
HM不仅统计应用的运行信息,它也会分析应用的状态。与此相关的channel:
DEA是一个安装在DEA VM node上的ruby的agent,由它来管理App的整个生命周期,包括stage, start, stop等等。 具体的流程可以参见How Application Are Staged。 与DEA相关的最重要的二件事是Staging app和Run app,都是由CloudFoundry通过NATS来触发的。
staging.advertise
消息;dea.advertise
消息;NATS与DEA、CloudController主要交互工作流程如下:
staging.advertise
和dea.advertise
这二个channel,CC会根据该channel所提供的信息时刻更新这二个pool;staging.dea_id.start
或dea.dea_id.start
的channel发送消息对于CloudFoundry来说,NATS的作用是必不可少的。NATS让CF系统中的各组件各司其职,各个组件之间可以不需要知道互相的存在,只是通过简单的消息订阅发布来完成异步消息的通信和协同工作,彻底让系统解耦。同时NATS也大大的提高了系统的可维护性和可扩展性,这让我们增加新的组件也更加简单,更加高效。