NSQ 源码分析之NSQD--lookup

今天主要讲的是 NSQD 中 lookup, 其主要作用是告知 Lookupd,当前 NSQD 实例的情况,包括心跳检测,Topic 和 Channel 的变化。

主要代码文件:

1.nsqd/lookup_peer.go  主要定义了一些集群的消息的结构体和相关的实例化

type lookupPeer struct {
	logf            lg.AppLogFunc //日志输出函数
	addr            string //Lookupd 地址
	conn            net.Conn
	state           int32 //状态
	connectCallback func(*lookupPeer) //连接回调
	maxBodySize     int64 //最大消息长度
	Info            peerInfo //集群伙伴元数据
}

//lookupd的信息
type peerInfo struct {
	TCPPort          int    `json:"tcp_port"` 
	HTTPPort         int    `json:"http_port"`
	Version          string `json:"version"`
	BroadcastAddress string `json:"broadcast_address"`
}

Command 函数 用于发布请求到Lookupd

func (lp *lookupPeer) Command(cmd *nsq.Command) ([]byte, error) {
	initialState := lp.state
	if lp.state != stateConnected { //如果没有连接Lookupd
		err := lp.Connect() //连接Lookupd
        ...
		lp.state = stateConnected
		_, err = lp.Write(nsq.MagicV1) //协议版本确认
        ....
		if initialState == stateDisconnected {
			lp.connectCallback(lp) //执行连接回调函数
		}
	    ..
	}
    ...
	_, err := cmd.WriteTo(lp) //写入请求
    ...
    //读取响应
	resp, err := readResponseBounded(lp, lp.maxBodySize)
	if err != nil {
		lp.Close()
		return nil, err
	}
	return resp, nil
}

readResponseBounded 函数 读取响应

func readResponseBounded(r io.Reader, limit int64) ([]byte, error) {
	var msgSize int32

	//读取消息总长度
	err := binary.Read(r, binary.BigEndian, &msgSize)
	if err != nil {
		return nil, err
	}
    ....
    //读取消息
	buf := make([]byte, msgSize)
	_, err = io.ReadFull(r, buf)
	if err != nil {
		return nil, err
	}

	return buf, nil
}

其他函数:

Connect、Read、Write 和 Close,都是字面上的意思

2.nsqd/lookup.go

connectCallback 函数,连接回调

func connectCallback(n *NSQD, hostname string) func(*lookupPeer) {
	return func(lp *lookupPeer) {
		ci := make(map[string]interface{})
		ci["version"] = version.Binary //版本号
		ci["tcp_port"] = n.RealTCPAddr().Port //NSQD 的 TCP 端口
		ci["http_port"] = n.RealHTTPAddr().Port //NSQD 的 HTTP 端口
		ci["hostname"] = hostname //NSQD的实例的地址
		ci["broadcast_address"] = n.getOpts().BroadcastAddress //NSQD广播地址 默认配置取的是os.Hostname

		cmd, err := nsq.Identify(ci) //生成Identify命令
        ...
		resp, err := lp.Command(cmd) //发送Identify请求
		if err != nil {
			n.logf(LOG_ERROR, "LOOKUPD(%s): %s - %s", lp, cmd, err)
			return
		} else if bytes.Equal(resp, []byte("E_INVALID")) {
	        ...
		} else {
            //解析 lookupd 的基本信息
			err = json.Unmarshal(resp, &lp.Info) 
		    ...
		}
        //下面的都是 向lookupd 注册 NSQD 拥有的 Topic 和 Channel
		var commands []*nsq.Command
		n.RLock()
		for _, topic := range n.topicMap { 
			topic.RLock()
			if len(topic.channelMap) == 0 {
				commands = append(commands, nsq.Register(topic.name, ""))
			} else {
				for _, channel := range topic.channelMap {
					commands = append(commands, nsq.Register(channel.topicName, channel.name))
				}
			}
			topic.RUnlock()
		}
		n.RUnlock()

		for _, cmd := range commands {
			n.logf(LOG_INFO, "LOOKUPD(%s): %s", lp, cmd)
			_, err := lp.Command(cmd)
			if err != nil {
				n.logf(LOG_ERROR, "LOOKUPD(%s): %s - %s", lp, cmd, err)
				return
			}
		}
	}
}

lookupLoop 函数 主要是用来监听NSQD的状态,并上报给lookupd。 lookupLook 会在 nsqd.Main 函数中调用,NSQD会开启一个 goroutine 进行为维护。

func (n *NSQD) lookupLoop() {
    ...
	//每15秒进行心跳检查
	ticker := time.Tick(15 * time.Second)
	for {
		if connect { //如果当前NSQD需要连接Lookupd
            //lookupd 是一个集群,所有 NSQLookupdTCPAddresses 有多个lookupd地址
			for _, host := range n.getOpts().NSQLookupdTCPAddresses {
				if in(host, lookupAddrs) { //判断是否已连接
					continue
				}
				n.logf(LOG_INFO, "LOOKUP(%s): adding peer", host)
				lookupPeer := newLookupPeer(host, n.getOpts().MaxBodySize, n.logf,
					connectCallback(n, hostname))
				lookupPeer.Command(nil) // start the connection
				lookupPeers = append(lookupPeers, lookupPeer)
				lookupAddrs = append(lookupAddrs, host)
			}
			n.lookupPeers.Store(lookupPeers)
			connect = false
		}

		select {
		case <-ticker: //心跳检查
			for _, lookupPeer := range lookupPeers { 
				cmd := nsq.Ping()
				_, err := lookupPeer.Command(cmd)
			}
		case val := <-n.notifyChan: //Topic Channel 变动通知
			var cmd *nsq.Command
			var branch string

			switch val.(type) {
			case *Channel: //channel 变动
				branch = "channel"
				channel := val.(*Channel)
				if channel.Exiting() == true { //注册新channel
					cmd = nsq.UnRegister(channel.topicName, channel.name)
				} else { //注销channel
					cmd = nsq.Register(channel.topicName, channel.name)
				}
			case *Topic: //topic 变动
				branch = "topic"
				topic := val.(*Topic)
				if topic.Exiting() == true { //注册新topic
					cmd = nsq.UnRegister(topic.name, "")
				} else { //注销topic
					cmd = nsq.Register(topic.name, "")
				}
			}

			for _, lookupPeer := range lookupPeers {
				_, err := lookupPeer.Command(cmd)
			}
		case <-n.optsNotificationChan: //Lookupd 集群地址变动通知
			var tmpPeers []*lookupPeer
			var tmpAddrs []string
			for _, lp := range lookupPeers { //重新生成集群伙伴信息
				if in(lp.addr, n.getOpts().NSQLookupdTCPAddresses) {
					tmpPeers = append(tmpPeers, lp)
					tmpAddrs = append(tmpAddrs, lp.addr)
					continue
				}
				n.logf(LOG_INFO, "LOOKUP(%s): removing peer", lp)
				lp.Close()
			}
			lookupPeers = tmpPeers
			lookupAddrs = tmpAddrs
			connect = true
		case <-n.exitChan:
			goto exit
		}
	}

exit:
	n.logf(LOG_INFO, "LOOKUP: closing")
}

总结:lookup 的作用是 NSQD 集群上报,报告内容包括 新跳检测 和 Topic、Channel 的变动通知。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章