CloudFoundry源码分析:NATS

简介

NATS是一个轻量的消息发布-订阅系统。NATS的核心是Event machine。

Server

名为server.rb的文件有两个。

一个是lib/nats/server.rb,这个是server包装类,也就是server的入口类:Server包装类通过EventMachine启动了两个服务。

(1) Nats Server,核心消息服务器

EventMachine::start_server(NATSD::Server.host, NATSD::Server.port, NATSD::Connection)

(2) HTTP Monitor Server,监控服务器

NATSD::Server.start_http_server

一个是lib/nats/server/server.rb类,这个是server实现类实,实现了主要server的主要功能:

(1)为某个订阅者订阅某个topic

      def subscribe(sub)
        @sublist.insert(sub.subject, sub)
      end

(2)为某个订阅者取消订阅一个topic

      def unsubscribe(sub)
        @sublist.remove(sub.subject, sub)
      end


(3)将一个消息推送到某个订阅者

 def deliver_to_subscriber(sub, subject, reply, msg)

(4)为某个消息路由所有的订阅者

 def route_to_subscribers(subject, reply, msg)

(5)HTTP接口的监控服务器,通过Thin实现

 http_server = Thin::Server.new(@options[:http_net], port, :signals => false)

Nats Server是通过EM启动的,数据的接收和分析由Connection类实现,文件位于lib/nats/server/connection.rb

Connection

Connection有两种模式:等待控制信息(AWAITING_CONTROL_LINE)模式和等待数据消息(AWAITING_MSG_PAYLOAD)模式。

Connection初始处于AWAITING_CONTROL_LINE状态,等待着控制信息的到来。

控制信息包括:PUB_OP、SUB_OP、UNSUB_OP、PING、PONG、CONNECT、INFO。当然还有一个UNKONWN。当且仅当控制信息是PUB_OP时,也就是客户端发布了一个消息时,CONNECTION会转入到AWAITING_MSG_PAYLOAD模式,接受发布的消息内容,然后路由到注册的订阅者进行处理,处理之后仍然恢复为AWAITING_CONTROL_LINE模式,并等待着新的控制信息的到来。

下面接着看看几种控制信息格式:

(1)PUB_OP

信息格式为

 /\APUB\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\r\n/i

例如:PUB MSG_SUB MSG_REPLY 128\r\n

其中$1:MSG_SUB消息的topic

$3:MSG_REPLY消息的响应

$4:128消息的长度

收到消息发布命令之后,Server转入AWAITING_MSG_PAYLOAD模式:

@parse_state = AWAITING_MSG_PAYLOAD

在AWAITING_MSG_PAYLOAD模式下,server截取消息:

msg = @buf.slice(0, @msg_size)

然后调用server中的方法,将该消息广播到所有的订阅者:

Server.route_to_subscribers(@msg_sub, @msg_reply, msg)

最后Server重新转入AWAITING_CONTROL_LINE模式:

@parse_state = AWAITING_CONTROL_LINE

(2)SUB_OP

信息格式为

/\ASUB\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?([^\s]+)\r\n/i

例如:SUB MSG_SUB QGROUP SID

$1:MSG_SUB消息的topic

$3:QGROUP 消息

$4:SID 连接的SID

随后,根据MSG_SUB、QGROUP以及SID生成新的Subscriber

subscriber = Subscriber.new(self, sub, sid, qgroup, 0)

subscriber对象包括其要注册的topic->sub并将subscriber加入到hash中@subscriptions[sid] = sub

然后通Server将该subscriber注册到指定的topic上。

(3)UNSUB_OP

信息格式为

/\AUNSUB\s+([^\s]+)\s*(\s+(\d+))?\r\n/i

例如:UNSUB SID

$1:取消订阅的SID

随后,找到要取消定于的subscriber和其sid:sid, sub = $1, @subscriptions[$1]

如果该订阅者没有未处理的响应,则取消订阅

delete_subscriber(sub) unless (sub.max_responses && (sub.num_responses < sub.max_responses))

(4)PING

信息格式为

/\APING\s*\r\n/i

收到了一个ping,自然响应一个pong:

 queue_data(PONG_RESPONSE)

(5)PONG

信息格式为

/\APONG\s*\r\n/i

收到了一个pong,则计算器减一:pings_outstanding -= 1

(6)CONNECT

信息格式为

/\ACONNECT\s+([^\r\n]+)\r\n/i

例如:CONNECT connection_info

$1:客户端的连接信息

对JSON格式的连接信息进行解析和处理:

config = JSON.parse($1)
process_connect_config(config)

(7)INFO

信息格式为

/\AINFO\s*\r\n/i

向客户端发送info:send_info

(8)UNKONWN

信息格式为/\A(.*)\r\n/

向客户端发送错误信息:queue_data(UNKNOWN_OP)


下面再来看下其核心数据结构Sublist

核心数据机构:Sublist

Sublist的核心由SublistNode和SublistLevel组成。表示了注册在一个topic上的所有订阅者。文件文件位于lib/nats/server/sublist.rb

其中topic是以逗号分隔的序列,包括两种通配符:全通配符>和部分通配符*

SublistNode  = Struct.new(:leaf_nodes, :next_level)

由于topic的匹配是森林结构的,所有一个SublistNode由两部分组成:leaf_nodes是到该层为止匹配到的subscriber数组。而next_level是可以进一步匹配的节点

  SublistLevel = Struct.new(:nodes, :pwc, :fwc)

任何一层可以匹配的节点分成3组:pwc指向由*匹配的节点,fwc指向由>指向的节点,而nodes是一个hash,指向了由各个具体字符串匹配的节点。

其他文件

const主要定义了一些常量、正则表达式和错误码。

util定义了一个些工具方法。

connz和var处理一些监控信息返回给客户。

Client

Client中的主要方法主要包括在NATS模块的单件类中,包括start,connect,subscribe,unsubscribe,publish以及定义在模块中的receive_data方法,下面分别解释:

Start

Start主要是调用connect方法,返回一个连接handler

EM.epoll; EM.kqueue
EM.run { @client = connect(*args, &blk) }
Connect

Connect方法利用Eventmachine建立到nats server的连接,并且关联连接建立时的block,当连接建立完成执行block.

client = EM.connect(@uri.host, @uri.port, self, opts)
client.on_connect(&blk) if blk

Subscribe

Sub方法在本地保存sub相关信息,并向nats server发送命令。

sub = @subs[sid] = { :subject => subject, :callback => callback, :received => 0 }
#....
send_command("SUB #{subject} #{opts[:queue]} #{sid}#{CR_LF}")
Subscribe方法关联一个callback,当客户端收到相关的topic信息时,会执行关联的callback


Unsubscribe

Unsubscribe方法发往server命令

send_command("UNSUB #{sid}#{opt_max_str}#{CR_LF}")
Publish

Publish方法向nats server发送命令

 send_command("PUB #{subject} #{opt_reply} #{msg.bytesize}#{CR_LF}#{msg}#{CR_LF}")
并关联一个callback,当收到PONG命令时执行
receive_data

客户连接接受信息时的连接有两种状态AWAITING_CONTROL_LINEAWAITING_MSG_PAYLOAD

MSG

当收到MSG时,首先解析注册的话题,然后进入AWAITING_MSG_PAYLOAD状态
@sub, @sid, @reply, @needed = $1, $2.to_i, $4, $5.to_i
@parse_state = AWAITING_MSG_PAYLOAD
进入AWAITING_MSG_PAYLOAD之后,解析收到的信息,然后调用on_msg方法
on_msg(@sub, @sid, @reply, @buf.slice(0, @needed))
在on_msg方法中,执行注册时的block
    if cb = sub[:callback]
      case cb.arity
        when 0 then cb.call
        when 1 then cb.call(msg)
        when 2 then cb.call(msg, reply)
        else cb.call(msg, reply, subject)
      end
    end








评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值