简介
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
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_LINE和AWAITING_MSG_PAYLOAD
MSG
@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