nats server端启动订阅推送源码剖析

本文详细介绍了NATS服务器的启动过程,包括监听客户端连接,创建并管理客户端。客户端在连接后进行初始化,启动读取循环处理接收到的数据。订阅部分展示了客户端如何解析订阅消息并存储订阅信息。消息推送环节,NATS通过匹配主题找到订阅者,对队列订阅进行负载均衡分发。整个过程揭示了NATS高效的消息传递机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

源码时取最早版本的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)
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值