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 的變動通知。

 

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