源码时取最早版本的nats
1.启动
nats的server端根据启动参数指定的ip和端口启动一个tcp的server端,然后持续监听发送过来的连接,并根据连接创建客户端,并把客户端绑定到当前的server上,每个客户端会分配一个客户端id。
func (s *Server) AcceptLoop() {
hp := fmt.Sprintf("%s:%d", s.opts.Host, s.opts.Port)
Logf("Listening for client connections on %s", hp)
l, e := net.Listen("tcp", hp)
if e != nil {
Fatalf("Error listening on port: %d - %v", s.opts.Port, e)
return
}
Logf("gnatsd is ready")
// Setup state that can enable shutdown
s.mu.Lock()
s.listener = l
s.mu.Unlock()
tmpDelay := ACCEPT_MIN_SLEEP
for s.isRunning() {
conn, err := l.Accept()
if err != nil {
if ne, ok := err.(net.Error); ok && ne.Temporary() {
Debug("Temporary Client Accept Error(%v), sleeping %dms",
ne, tmpDelay/time.Millisecond)
time.Sleep(tmpDelay)
tmpDelay *= 2
if tmpDelay > ACCEPT_MAX_SLEEP {
tmpDelay = ACCEPT_MAX_SLEEP
}
} else if s.isRunning() {
Logf("Accept error: %v", err)
}
continue
}
tmpDelay = ACCEPT_MIN_SLEEP
// 生成客户端并绑定
s.createClient(conn)
}
Log("Server Exiting..")
s.done <- true
}
func (s *Server) createClient(conn net.Conn) *client {
c := &client{srv: s, nc: conn, opts: defaultOpts}
// Grab lock
c.mu.Lock()
// 初始化客户端
c.initClient()
Debug("Client connection created", clientConnStr(c.nc), c.cid)
// Send our information.
s.sendInfo(c)
// Check for Auth
if s.info.AuthRequired {
ttl := secondsToDuration(s.opts.AuthTimeout)
c.setAuthTimer(ttl)
}
// Unlock to register
c.mu.Unlock()
// Register with the server.
s.mu.Lock()
// 每个客户端都会有一个id作为唯一表示,并以map{client_id:client}形式存储
s.clients[c.cid] = c
s.mu.Unlock()
return c
}
客户端被创建后会进行初始化操作,初始化最主要的任务就是开启携程对当前clietn对应的connect进行数据读取并处理。
func (c *client) initClient() {
.
.
.
// Spin up the read loop.
go c.readLoop()
}
func (c *client) readLoop() {
// Grab the connection off the client, it will be cleared on a close.
// We check for that after the loop, but want to avoid a nil dereference
.
.
.
for {
n, err := nc.Read(b)
if err != nil {
c.closeConnection()
return
}
if err := c.parse(b[:n]); err != nil {
Log(err, clientConnStr(c.nc), c.cid)
// Auth was handled inline
if err != ErrAuthorization {
c.sendErr("Parser Error")
c.closeConnection()
}
return
}
// Check pending clients for flush.
for cp := range c.pcd {
// Flush those in the set
cp.mu.Lock()
if cp.nc != nil {
cp.nc.SetWriteDeadline(time.Now().Add(DEFAULT_FLUSH_DEADLINE))
err := cp.bw.Flush()
cp.nc.SetWriteDeadline(time.Time{})
if err != nil {
Debugf("Error flushing: %v", err)
cp.mu.Unlock()
cp.closeConnection()
cp.mu.Lock()
}
}
cp.mu.Unlock()
delete(c.pcd, cp)
}
// Check to see if we got closed, e.g. slow consumer
if c.nc == nil {
return
}
}
}
2.订阅
在client的parse方法中包含了对客户端消息的所有处理。
当收到订阅消息后,会把订阅消息打包成一个订阅结构体subscription
// 订阅信息结构体跟client也是绑定的
type subscription struct {
client *client
subject []byte // 订阅的主题消息byte数组
queue []byte // 队列名称的byte数组
sid []byte // 订阅id的byte数组
nm int64
max int64
}
然后把订阅消息结构体缓存到server和client,然后回复消息
func (c *client) processSub(argo []byte) (err error) {
c.traceOp("SUB", argo)
// Copy so we do not reference a potentially large buffer
arg := make([]byte, len(argo))
copy(arg, argo)
args := splitArg(arg)
sub := &subscription{client: c}
switch len(args) {
case 2:
sub.subject = args[0]
sub.queue = nil
sub.sid = args[1]
case 3:
sub.subject = args[0]
sub.queue = args[1]
sub.sid = args[2]
default:
return fmt.Errorf("processSub Parse Error: '%s'", arg)
}
c.mu.Lock()
// 将map{subId:subinfo}缓存到client
c.subs.Set(sub.sid, sub)
if c.srv != nil {
// 将map{subject:subjectinfo}缓存到server
err = c.srv.sl.Insert(sub.subject, sub)
}
shouldForward := c.typ != ROUTER && c.srv != nil
c.mu.Unlock()
// 回复订阅消息
if err != nil {
c.sendErr("Invalid Subject")
} else if c.opts.Verbose {
c.sendOK()
}
if shouldForward {
c.srv.broadcastSubscribe(sub)
}
return nil
}
3.消息推送
推送时会通过一个sublist一个对象里面通过map存储的有subject以及subscription的数据,通过subject匹配找到subscription队列,便利队列中每一个subscription查看是否为队列模式是的话就放到一个集合里面,不是的话就推送消息,最后从集合里面随机挑选出一个client进行推送。
func (c *client) processMsg(msg []byte) {
.
.
.
msgh := c.msgb[:len(msgHeadProto)]
// 根据订阅的subject查询到对应的subjectinfo
r := srv.sl.Match(c.pa.subject)
if len(r) <= 0 {
return
}
// msg header
msgh = append(msgh, c.pa.subject...)
msgh = append(msgh, ' ')
si := len(msgh)
var qmap map[string][]*subscription
var qsubs []*subscription
isRoute := c.typ == ROUTER
var rmap map[string]struct{}
// If we are a route and we have a queue subscription, deliver direct
// since they are sent direct via L2 semantics. If the match is a queue
// subscription, we will return from here regardless if we find a sub.
if isRoute {
if sub, ok := srv.routeSidQueueSubscriber(c.pa.sid); ok {
if sub != nil {
mh := c.msgHeader(msgh[:si], sub)
c.deliverMsg(sub, mh, msg)
}
return
}
}
// 便利与subject匹配的subjectinfo,对每一个subjectinfo进行消息分发
for _, v := range r {
sub := v.(*subscription)
// 如果是队列模式,则转为队列分发模式,并将subject对应的subjectinfo以queuename归类组装成map{queuename:[subject1,subject2]}这样的数据格式
if sub.queue != nil {
// Queue subscriptions handled from routes directly above.
if isRoute {
continue
}
// FIXME(dlc), this can be more efficient
if qmap == nil {
qmap = make(map[string][]*subscription)
}
qname := string(sub.queue)
qsubs = qmap[qname]
if qsubs == nil {
qsubs = make([]*subscription, 0, 4)
}
qsubs = append(qsubs, sub)
qmap[qname] = qsubs
continue
}
.
.
.
// 非队列模式
mh := c.msgHeader(msgh[:si], sub)
c.deliverMsg(sub, mh, msg)
}
// 队列分发模式,队列中随机选出一个subjectinfo进行消息分发(随机负载均衡分发)
if qmap != nil {
for _, qsubs := range qmap {
index := rand.Int() % len(qsubs)
sub := qsubs[index]
mh := c.msgHeader(msgh[:si], sub)
c.deliverMsg(sub, mh, msg)
}
}
}