NSQ 源碼分析之Lookupd--TCPServer

今天主要講的是 Lookupd 中 TCP 服務的實現,TCP服務主要用於響應 NSQD 的心跳檢查,NSQD 註冊等信息。Lookupd 的 TCP 服務與 NSQD 中的 TCP 服務 在代碼結構及實現方面非常相似。都是通過 IOLoop 循環讀取客戶端請求、解析命令、調用命令函數、執行命令、返回響應。

主要代碼文件:

1.nsqlookupd/lookup_protocol_v1.go 

IOLoop 函數

func (p *LookupProtocolV1) IOLoop(conn net.Conn) error {
	var err error
	var line string
    //包裝conn
	client := NewClientV1(conn)
	reader := bufio.NewReader(client)
	for {
        //以回車作爲命令分隔
		line, err = reader.ReadString('\n')
		if err != nil {
			break
		}
        //去除左右空格
		line = strings.TrimSpace(line)
        //以空格分隔,生成命令數組
		params := strings.Split(line, " ")

		var response []byte
        //執行命令
		response, err = p.Exec(client, reader, params)
		if err != nil {
            ....
            //響應錯誤
			_, sendErr := protocol.SendResponse(client, []byte(err.Error()))
			if sendErr != nil {
				p.ctx.nsqlookupd.logf(LOG_ERROR, "[%s] - %s%s", client, sendErr, ctx)
				break
			}
            ...
			continue
		}

		if response != nil {
            //響應請求
			_, err = protocol.SendResponse(client, response)
			if err != nil {
				break
			}
		}
	}

	conn.Close()
	if client.peerInfo != nil {
        //清除客戶信息
		registrations := p.ctx.nsqlookupd.DB.LookupRegistrations(client.peerInfo.id)
		for _, r := range registrations {
			if removed, _ := p.ctx.nsqlookupd.DB.RemoveProducer(r, client.peerInfo.id); removed {
				p.ctx.nsqlookupd.logf(LOG_INFO, "DB: client(%s) UNREGISTER category:%s key:%s subkey:%s",
					client, r.Category, r.Key, r.SubKey)
			}
		}
	}
	return err
}

 Exec 函數

func (p *LookupProtocolV1) Exec(client *ClientV1, reader *bufio.Reader, params []string) ([]byte, error) {
	switch params[0] {
	case "PING": //心跳
		return p.PING(client, params)
	case "IDENTIFY": //NSQD 註冊
		return p.IDENTIFY(client, reader, params[1:])
	case "REGISTER": //註冊Topic or Channel
		return p.REGISTER(client, reader, params[1:])
	case "UNREGISTER": //註銷Topic or Channel
		return p.UNREGISTER(client, reader, params[1:])
	}
	return nil, protocol.NewFatalClientErr(nil, "E_INVALID", fmt.Sprintf("invalid command %s", params[0]))
}

PING 函數 心跳檢測

func (p *LookupProtocolV1) PING(client *ClientV1, params []string) ([]byte, error) {
	if client.peerInfo != nil {
		// we could get a PING before other commands on the same client connection
		cur := time.Unix(0, atomic.LoadInt64(&client.peerInfo.lastUpdate))
		now := time.Now()
        //更新心跳檢測時間
		atomic.StoreInt64(&client.peerInfo.lastUpdate, now.UnixNano())
	}
	return []byte("OK"), nil
}

IDENTIFY 函數 NSQD註冊

func (p *LookupProtocolV1) IDENTIFY(client *ClientV1, reader *bufio.Reader, params []string) ([]byte, error) {
	var err error
    //防止重複註冊
	if client.peerInfo != nil {
		return nil, protocol.NewFatalClientErr(err, "E_INVALID", "cannot IDENTIFY again")
	}

    ... //讀取請求
    ... // 收集客戶端信息,包括IP,TPC 端口,域名,廣播地址等。

	client.peerInfo = &peerInfo
    //向DB註冊客戶端
	if p.ctx.nsqlookupd.DB.AddProducer(Registration{"client", "", ""}, &Producer{peerInfo: client.peerInfo}) {
		p.ctx.nsqlookupd.logf(LOG_INFO, "DB: client(%s) REGISTER category:%s key:%s subkey:%s", client, "client", "", "")
	}

	//建立響應,將服務相關的IP,端口等返回給客戶端。
    
	return response, nil
}

REGISTER 函數註冊 Topic or Channel

func (p *LookupProtocolV1) REGISTER(client *ClientV1, reader *bufio.Reader, params []string) ([]byte, error) {
	...
    //getTopicChan 主要是解析出param 中的 topic 和 channel
	topic, channel, err := getTopicChan("REGISTER", params)
	if err != nil {
		return nil, err
	}
    //註冊channel
	if channel != "" {
		key := Registration{"channel", topic, channel}
		if p.ctx.nsqlookupd.DB.AddProducer(key, &Producer{peerInfo: client.peerInfo}) {
			p.ctx.nsqlookupd.logf(LOG_INFO, "DB: client(%s) REGISTER category:%s key:%s subkey:%s",
				client, "channel", topic, channel)
		}
	}
    //註冊topic
	key := Registration{"topic", topic, ""}
	if p.ctx.nsqlookupd.DB.AddProducer(key, &Producer{peerInfo: client.peerInfo}) {
		p.ctx.nsqlookupd.logf(LOG_INFO, "DB: client(%s) REGISTER category:%s key:%s subkey:%s",
			client, "topic", topic, "")
	}

	return []byte("OK"), nil
}

 UNREGISTER 函數註銷 Topic or Channel

func (p *LookupProtocolV1) UNREGISTER(client *ClientV1, reader *bufio.Reader, params []string) ([]byte, error) {
    ...

	if channel != "" {
        //刪除channel
		key := Registration{"channel", topic, channel}
		removed, left := p.ctx.nsqlookupd.DB.RemoveProducer(key, client.peerInfo.id)
        ...
        //刪除臨時節點
		if left == 0 && strings.HasSuffix(channel, "#ephemeral") {
			p.ctx.nsqlookupd.DB.RemoveRegistration(key)
		}
	} else {
        //查找 topic 下所有的channel
		registrations := p.ctx.nsqlookupd.DB.FindRegistrations("channel", topic, "*")
		for _, r := range registrations {
            //刪除channel
			removed, _ := p.ctx.nsqlookupd.DB.RemoveProducer(r, client.peerInfo.id)
			if removed {
                ...
			}
		}
        //刪除topic
		key := Registration{"topic", topic, ""}
		removed, left := p.ctx.nsqlookupd.DB.RemoveProducer(key, client.peerInfo.id)
        ...
        //刪除臨時節點
		if left == 0 && strings.HasSuffix(topic, "#ephemeral") {
			p.ctx.nsqlookupd.DB.RemoveRegistration(key)
		}
	}

	return []byte("OK"), nil
}

總結:Lookupd 中的 TCPServer 主要提供了 NSQD 註冊,心跳檢測、Topic or Channel 註冊及 Topic or Channel 註銷。

下一篇分享:Lookupd 中的 HttpServer

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