cloud foundry v2版本之nats源码解读

前言

众所周知,cloud foundry是vmware主导的主要用ruby开发的第一款开源paas云平台,对于想要学习ruby的朋友,它的源码就是我们最好的学习工具。 如果说你在进行cloud foundry的相关开发工作,为了了解其内部的工作机制,源码是不得不透彻地分析一下的。 源码的github地址:https://github.com/cloudfoundry 为了循循渐进,由简入繁地深入cloud foundry,我们从nats的源码开始入手。


nats源码特点

在cloud foundry的各个组件中nats的源代码是最清晰,也是最具有可读性的,很适合对cloud foundry一知半解的朋友入手,它的主线基本上是一个 eventmachine和一个基于thin的http monitor。在读源码之前你需要有必要的ruby功底,还需要一些gem包(如eventmachine)的基本了解。下面我们就从简单介绍eventmachin入手。


eventmachine简介

首先,网上有一篇不错的博文http://blog.csdn.net/resouer/article/details/7975550,可以了解到一些eventmachine的基础知识,在这里我对 nats中常用的再强调一下。

  1. EM::Connection 是EM下控制连接的类,在nats中以模块的形式出现,如果想要定义自己的连接名称,只需要继承这个类即可。Connection中有两个 最基本的方法:recive_data(data)和send_data(data),从方法定义的名称就可以看到他们的作用,recive_data用于接收客户端发来的数据,send_data 用于向客户端发送数据。在nats中阅读Connection模块的时候,只要抓住这两个方法,其他的都是辅助控制连接的。
  2. EM.start_server(ip,port,connection) 是EM服务器启动代码,它在主机(ip)的port端口上监听对EM的访问,连接的属性动作由connetcion控制。
  3. EM::Timer与EM::PeriodicTimer 是两个定时器,前者是一次性的(触发一次事件后消失),后者是周期性的。两者的参数是定时器的时间间隔,可 以为它们定义句柄,以便在程序运行时控制他们,比如调用cancel或stop函数停止定时器。
  4. EM.next_tick(do_work) 是EM在网络编程中协调并发的一种策略,由于ruby在执行多线程的时候并不是系统级的并发,而是通过ruby解释器调度的一 种伪并发,所以ruby在多任务一起处理时有它语言的先天不足。为了弥补这个不足,EM采用了一个让人拍案叫绝的解决办法,即:EM运用一个主进程,在它 运行起来的时候并不去阻塞它,如果碰到需要诸如IO等需要等待的工作时,就把它交给下一个循环(next_tick),在任务需要执行的条件已经成熟的时候 会轮到一个循环来执行这个任务。
  5. EM.run do ... end 是基于EM编程的程序入口,服务器端在这里启动服务器监听,客户端可以在这里启动指向服务器的连接。

以上是对nats中常见的关于EM编程的归纳,如果想深入了解EM的工作方式,还有待更深入的研究。


nats源码文件组织方式

用ruby编写的软件程序都有一个不约而同地习惯,就是bin文件夹下都是一些很简单的命令,在nats中bin文件夹下文件的内容基本只包含一些require, 真正的代码部分都在lib里面,平行的其它文件夹是一些测试和教你如何使用的代码。我们重点看lib/nats下的各个文件。

  1. server.rb nats服务器启动的入口文件,在里面进行了服务器的初始化建立,server的EM启动,和一个基于thin的monitor的启动。
  2. client.rb nats的客户端代码,这cloud foundry的各个组件中都会有这个gem包,其它的各个组件都必须通过它和nats_server建立连接。
  3. server 服务器的主要代码部分
  4. options.rb nats服务器的参数控制文件
  5. connection.rb nats中eventmachine的连接控制模块
  6. server.rb 控制各个连接的server主程序
  7. connz.rb 通过monitor的conn监控时,返回的连接信息
  8. sublist.rb 基于发布与订阅模式的消息处理核心数据结构
  9. const.rb nats中需要的一些全局常量
  10. varz.rb 通过monitor的varz监控时,返回的服务器信息
  11. util.rb 一些关于log,trace,debug等的基本函数封装
  12. ext 导入一些必须的gem包,解析一些数据格式信息

nats的参数控制

首先来看nats的参数,通过对参数的了解,可以窥得nats的全貌,nats的参数是靠options.rb这个文件控制的,它定义了NATSD::Server下的一个内建类, 参数的解析分为三层:

  1. 一些默认的参数,他们通过const.rb中的常量定义好,如:DEFAULT_PORT等
  2. 用户通过配置文件传入的参数,它比默认的参数的优先级高,能够覆写掉默认的参数
  3. 用户通过命令行传入的参数,它们具有最高的优先级,能够overwrite前两种参数

nats中有一些我们比较陌生的参数,列几个解释一下

  1. --daemonize 守护进程标志,设定它可以在系统启动nats的守护进程,一般建议启动它
  2. --http_port 为monitor指定一个端口,如何需要通过http去对nats进行数据采集,需要设定它
  3. --ssl 启动https/ssl,一个针对http数据传输的加密协议
  4. --no_epoll linux下,一种针对EM中更好的IO资源调度策略
  5. --user --paas nats的用户认证策略,在搭建私有云的时候,在安全方面有较高要求时可以设定用户认证
  6. 一些负载能力方面的参数如max_control_line,max_payload等,分别对应于一条控制命令和一条消息的最大长度
  7. 一些超时方面的参数如auth_timeout,ssl_timeout,分别对应于用户认证超时和ssl认证超时

nats消息发布与订阅的核心数据结构

这一块是整个nats运转机制的核心类,是它控制着消息的控制与转发,发布与订阅模式的大致概括为,一条带有主题的消息发布到nats服务器上后, 会被转发给所有关心这个主题的订阅者(客户端)。通过阅读README文件我们得知nats的消息主题有两个通配符:*,>。主题通过<.>被分成各级token, 比如:

  • 订阅主题'foo.*.baz'将匹配到'foo.bar.baz','foo.22.baz'等主题
  • 订阅主题'foo.>'将匹配到'foo.bar', 'foo.bar.baz', 'foo.foo.bar.bax.22'等主题,但是>必须是相关订阅主题的最后token,而主题'foo.*'将不 会匹配到'foo.bar.baz'上面。

下面我们可以进入sublist.rb的阅读了,进入这个类就定义了两个符号:pwc,:fwc风别指向通配符:*与>。还定义了两种结构类:

  • SublistNode = Struct.new(:leaf_nodes, :next_level)
  • SublistLevel = Struct.new(:nodes, :pwc, :fwc)

如果对树这种数据结构比较了解,对这两种结构也应该不在话下,SublistNode中:leaf_nodes指向一个数组,数组里面是一个一个的订阅者(后面我 们将会讲到订阅者Subscriber类,里面是EM连接信息),:next_level指向下一级的SublistLevel,订阅主题有多少级的token,就有多少级的SublistLevel 。level里面的:nodes指向一个hash表,hash的key就是这一级的token,hash的value就是这一级的SublistNode,如果token是,这个SublistNode将会 由:pwc指向,如果token是>,将会由:fwc指向,所以只有token非也非>时,存储订阅者节点的node才会出现在:nodes中hash指向的value中。

如果有众多的主题和订阅者,那么上述结构将会形成一颗庞大的树。sublist类中提供的方法:

1. insert(subject, subscriber) 将一个订阅者按subject插入:leaf_nodes指向的数组中
2. remove(subject, subscriber) 将一个订阅者从它所在的:SublistNode中删除,如果node中订阅者为空,这个node将被清除
3. match(subject) 一条消息中的主题会匹配到若干订阅它的订阅者,这个函数将演绎匹配的动作。

这一部分算法性比较强,remove与match中都有递归,由于不好表述,建议拿出一张纸一步一步画出整个过程,你会对整个设计拍案叫绝。其中有对 主题匹配结果的cache缓存,当cache溢出或对订阅树操作时,缓存将被清空。


server主进程

  • 阅读到这里,感叹到写代码过程中清晰度的重要性,cloud foundry中各个类和方法的定义都可以让你一目了然,这不得不感谢开发者们的精心设计 和对ruby语言的良好把握。server里面的变量和方法定义,我们只需要看名字就知道它的功能了。
  • 一开始定义了一个结构Subscriber就是我们在上文中提到的订阅者定义,它保存了这个订阅者的EM连接(:conn),主题(:subject),订阅id(:sid ,一个连接对应的客户端可能订阅多个主题),组(:qgroup,标识订阅者的组别,消息会被随机发送到组里面的任一个订阅者)等等。
  • 上面提到一颗庞大的订阅树,它到底存在哪?就是server类中的@sublist。它实际上是sublist类的一个实例对象,这个server控制的所有订阅和方 法如subscribe(订阅),unsubscribe(取消订阅),deliver_to_subscriber(将消息发给一个订阅者),route_to_subscribers(将消息路由给匹配 的所有订阅者)都是对这个实例对象的操作。
  • 在server初始化建立的时候,它会根据daemonize_flag的参数设置,选择启动一个daemon进程,然后入口EM处的monitor的启动方法 NATSD::Server.start_http_server也在这里给出了定义,如果想要简单了解thin的使用,这段代码会是一个不错的选择。
  • 做一个测试,我们将nats_server在一台主机上启动起来,在浏览器中输入http://ip_addresss:http_port/(healrhz|varz|connz),就会依次得到 ok,nats_server,nats_connections等返回信息,后两个respones是json格式的数据。

Connection模块

上面提到sublist类是消息发布订阅模式的核心数据结构算法,那么Connection模块就是整个nats的EM核心。 按照前面说的方法,先找recive_data与send_data方法,然后再去看其它的实现方法,这里我们来梳理一下:

  1. flush_data 将@writev中的数据发送出去
  2. queue_data(data) 一个消息缓存队列,将消息存入@writev里,@writev存满后调用flush_data,这里面就会用到EM.next_tick,解决@writev中数 据发送和写入的并行矛盾。
  3. post_init 连接的初始化工作,如果初始化成功,就会建立一条client连接,其中的类变量@subscriptions保存这个客户端的所有订阅者
  4. receive_data(data) Connection接收数据和分析数据的主要代码,它采用自动机的设计思想,分为两个状态:AWAITING_CONTROL_LINE 和 AWAITING_MSG_PAYLOAD,由变量名就可以看出,连接始终处于等待操作码(control_line),分析操作码并做相应的动作,等待消息,处理并发送消息的 循环中,操作码主要有PUB_OP(发布),SUB_OP(订阅),UNSUB_OP(取消订阅),CONNECT(连接请求,用于身份的鉴定,里面会涉及到EM的定时器, 如果定时器超时,则身份认定失败,连接断开),unbind(解除绑定,删除此客户端的订阅信息)。

后记

nats的源码有很多我们值得关注的地方,如EM的设计,sublist数据结构的算法设计等等,更多细节还需去仔细推敲源码,特别是其中发布与订阅模式 的设计与实现绝对值得我们去学习和借鉴。更多cloud foundry的组件源码分析我会陆续更新。



Previous     Next
zhing /
Published under (CC) BY-NC-SA in categories cloudfoundry  tagged with cloudfoundry