NSQ 源碼分析之NSQD--ProtocolV2

今天來說說NSQD.TCPServer中的核心函數IOLoop的具體實現,IOLoop主要的工作是接收和響應客戶的命令。同時開啓messagePump goroutine 進行心跳檢查,給訂閱者發生消息等操作。

詳細流程參考 https://blog.csdn.net/H_L_S/article/details/104709619 中的邏輯流程圖。

主要代碼文件:

1.nsqd/protocol_v2.go

IOLoop函數

func (p *protocolV2) IOLoop(conn net.Conn) error {
	....
    //客戶端實例化
	clientID := atomic.AddInt64(&p.ctx.nsqd.clientIDSequence, 1)
	client := newClientV2(clientID, conn, p.ctx)
	p.ctx.nsqd.AddClient(client.ID, client)
    ...
    //messagePump啓動信號,當 messagePump初始化完成,才能開始進行命令處理
	messagePumpStartedChan := make(chan bool)
	go p.messagePump(client, messagePumpStartedChan)
	<-messagePumpStartedChan

	for {
        //net.Conn 讀超時設置
		if client.HeartbeatInterval > 0 {
			client.SetReadDeadline(time.Now().Add(client.HeartbeatInterval * 2))
		} else {
			client.SetReadDeadline(zeroTime)
		}

        //以\n 爲分隔符作爲一條命令的結束(協議)
		line, err = client.Reader.ReadSlice('\n')
	    ...
        //每條命令 以separatorBytes(這裏定義的是空格) 作爲命令參數分解符(協議)
		params := bytes.Split(line, separatorBytes)

		var response []byte
        //執行命令
		response, err = p.Exec(client, params)
		if err != nil {
            //命令無法執行,或執行錯誤,返回對應信息給客戶端
            ...
			continue
		}
        //發送響應給客戶端
		if response != nil {
			err = p.Send(client, frameTypeResponse, response)
		    ...
		}
	}
    ....
}

messagePump函數

func (p *protocolV2) messagePump(client *clientV2, startedChan chan bool) {
	var err error
	var memoryMsgChan chan *Message //內存消費消息
	var backendMsgChan chan []byte //磁盤消費消息(內存無法存儲時,將消息存儲到磁盤)
	var subChannel *Channel //訂閱者消息channel
    //發送給客戶的消息並不是馬上發給客戶端,而是先緩存到client.Writer中,然後在發送給客戶端
    //而flusherChan 是緩存刷出的信號。
	var flusherChan <-chan time.Time 
	var sampleRate int32 //暫時不知用處

	subEventChan := client.SubEventChan //客戶端的訂閱信號
	identifyEventChan := client.IdentifyEventChan //身份認證信號
	outputBufferTicker := time.NewTicker(client.OutputBufferTimeout) //刷出緩存的定時器
	heartbeatTicker := time.NewTicker(client.HeartbeatInterval) //心跳檢查定時器
	heartbeatChan := heartbeatTicker.C
    //消息超時時間,這裏的意思是,如果消費者消費消息,超過這個時間,沒有消費確認,會將消息重新消費。
	msgTimeout := client.MsgTimeout 
    ...
	flushed := true
    ...
	close(startedChan) //初始化完成,開始接受請求命令

	for {
		if subChannel == nil || !client.IsReadyForMessages() { //沒有訂閱請求
			...
		} else if flushed { //正在刷出緩存至客戶端
            ....
		} else {
			memoryMsgChan = subChannel.memoryMsgChan // 訂閱者的內存channel
			backendMsgChan = subChannel.backend.ReadChan() //訂閱者的磁盤channel
			flusherChan = outputBufferTicker.C //定時刷出緩存的定時器。
		}

		select {
		case <-flusherChan: //刷出緩存
		    ...
		case <-client.ReadyStateChan:
		case subChannel = <-subEventChan: //訂閱
			// 不允許再被其他人訂閱
			subEventChan = nil
		case identifyData := <-identifyEventChan: 
		    ...
		case <-heartbeatChan: //心跳檢查
			err = p.Send(client, frameTypeResponse, heartbeatBytes)
			if err != nil {
				goto exit
			}
		case b := <-backendMsgChan: //消費磁盤channel的消息, 
		    ....
		case msg := <-memoryMsgChan: //消費內存channel的消息
			if sampleRate > 0 && rand.Int31n(100) > sampleRate {
				continue
			}
			msg.Attempts++ //嘗試重新消費的次數,如果超過這個次數,不應該再重新消費
            //加入等待消費確認的隊列,如果超過msgTimeout,就把被重新被消費,加入這個隊裏。
			subChannel.StartInFlightTimeout(msg, client.ID, msgTimeout)
            //增加總共發送的消息總數 和 待消費確認的消息總數
			client.SendingMessage()
            //發送消息到客戶緩存
			err = p.SendMessage(client, msg)
			if err != nil {
				goto exit
			}
			flushed = false
		case <-client.ExitChan:
			goto exit
		}
	}
    ...
}

Exec函數

func (p *protocolV2) Exec(client *clientV2, params [][]byte) ([]byte, error) {
	if bytes.Equal(params[0], []byte("IDENTIFY")) { //身份認證
		return p.IDENTIFY(client, params)
	}
	err := enforceTLSPolicy(client, p, params[0]) //TLS 加密
	if err != nil {
		return nil, err
	}    
	switch {
	case bytes.Equal(params[0], []byte("FIN")): //消費確認
		return p.FIN(client, params)
	case bytes.Equal(params[0], []byte("RDY")): //Ready 確認
		return p.RDY(client, params)
	case bytes.Equal(params[0], []byte("REQ")): //消息重新消費
		return p.REQ(client, params)
	case bytes.Equal(params[0], []byte("PUB")):  //單消息發佈
		return p.PUB(client, params)
	case bytes.Equal(params[0], []byte("MPUB")): //多消息發佈
		return p.MPUB(client, params)
	case bytes.Equal(params[0], []byte("DPUB")): //帶延時的發佈
		return p.DPUB(client, params)
	case bytes.Equal(params[0], []byte("NOP")): //心跳檢測
		return p.NOP(client, params)
	case bytes.Equal(params[0], []byte("TOUCH")): //更新消息的msgTimeout 超時時間
		return p.TOUCH(client, params)
	case bytes.Equal(params[0], []byte("SUB")): //訂閱
		return p.SUB(client, params)
	case bytes.Equal(params[0], []byte("CLS")): //關閉
		return p.CLS(client, params)
	case bytes.Equal(params[0], []byte("AUTH")): //認證
		return p.AUTH(client, params)
	}
	return nil, protocol.NewFatalClientErr(nil, "E_INVALID", fmt.Sprintf("invalid command %s", params[0]))
}

總結

1.IOLoop 主要實現兩個循環處理,一個是messagePump,主要是對心跳檢測,訂閱的消費的消息輸出等。另一個是對客戶端請求命令的處理Exec。

下次分享:IOLoop 中的 Exec相關函數

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