NSQ 源碼分析之NSQD--Exec相關函數

在NSQD的TCPServer,當客戶端與服務連接成功之後,會有一個goroutine專門處理這個連接的請求,在上篇中的IOLoop中,可看出服務以\n 爲分隔符作爲一條命令的結束(協議)讀取客戶請求,然後在將命令分解成 “命令 參數 參數1 慘2...”,最後交給Exec 函數執行,並返回響應。

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

主要代碼文件:

1.nsqd/protocol_v2.go

Exce 函數

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]))
}

IDENTITY 函數(身份信息交換)

func (p *protocolV2) IDENTIFY(client *clientV2, params [][]byte) ([]byte, error) {
	var err error
    //讀取消息長度
	bodyLen, err := readLen(client.Reader, client.lenSlice)
    ...
    //讀取消息實體
	body := make([]byte, bodyLen)
	_, err = io.ReadFull(client.Reader, body)
	/*
        client_v2 中
        type identifyDataV2 struct {
	        ClientID            string `json:"client_id"`
	        Hostname            string `json:"hostname"`
	        HeartbeatInterval   int    `json:"heartbeat_interval"`
	        OutputBufferSize    int    `json:"output_buffer_size"`
	        OutputBufferTimeout int    `json:"output_buffer_timeout"`
	        FeatureNegotiation  bool   `json:"feature_negotiation"`
	        TLSv1               bool   `json:"tls_v1"`
	        Deflate             bool   `json:"deflate"`
	        DeflateLevel        int    `json:"deflate_level"`
	        Snappy              bool   `json:"snappy"`
	        SampleRate          int32  `json:"sample_rate"`
	        UserAgent           string `json:"user_agent"`
	        MsgTimeout          int    `json:"msg_timeout"`
        }
    */
	var identifyData identifyDataV2
	err = json.Unmarshal(body, &identifyData)
	...
    /*
        Identify的目的是,對一些參數得出折中方案
        比如MsgTimeout,客戶端希望的消息超時時間和NSQD配置的消息超時時間進行比較,得出一個折中方案。
        主要有:
        HeartbeatInterval   
        OutputBufferSize
        SampleRate
        MsgTimeout   
    */
	err = client.Identify(identifyData)
	if err != nil {
        ...
	}

	/*
        Deflate:是否壓縮
        DeflateLevel:壓縮等級
        TLSv1:加密
        Snappy: 不知道幹啥???????????
    */

	resp, err := json.Marshal(struct {
		MaxRdyCount         int64  `json:"max_rdy_count"`
		Version             string `json:"version"`
		MaxMsgTimeout       int64  `json:"max_msg_timeout"`
		MsgTimeout          int64  `json:"msg_timeout"`
		TLSv1               bool   `json:"tls_v1"`
		Deflate             bool   `json:"deflate"`
		DeflateLevel        int    `json:"deflate_level"`
		MaxDeflateLevel     int    `json:"max_deflate_level"`
		Snappy              bool   `json:"snappy"`
		SampleRate          int32  `json:"sample_rate"`
		AuthRequired        bool   `json:"auth_required"`
		OutputBufferSize    int    `json:"output_buffer_size"`
		OutputBufferTimeout int64  `json:"output_buffer_timeout"`
	}{
		MaxRdyCount:         p.ctx.nsqd.getOpts().MaxRdyCount,
		Version:             version.Binary,
		MaxMsgTimeout:       int64(p.ctx.nsqd.getOpts().MaxMsgTimeout / time.Millisecond),
		MsgTimeout:          int64(client.MsgTimeout / time.Millisecond),
		TLSv1:               tlsv1,
		Deflate:             deflate,
		DeflateLevel:        deflateLevel,
		MaxDeflateLevel:     p.ctx.nsqd.getOpts().MaxDeflateLevel,
		Snappy:              snappy,
		SampleRate:          client.SampleRate,
		AuthRequired:        p.ctx.nsqd.IsAuthEnabled(),
		OutputBufferSize:    client.OutputBufferSize,
		OutputBufferTimeout: int64(client.OutputBufferTimeout / time.Millisecond),
	})
    ...
    //發送最終方案給客戶端。
	err = p.Send(client, frameTypeResponse, resp)
    ...
	if tlsv1 {
        ....
	}

	if snappy {
       ....
	}

	if deflate {
	    ...
	}

	return nil, nil
}

NOP 函數(啥也不幹)

func (p *protocolV2) NOP(client *clientV2, params [][]byte) ([]byte, error) {
	return nil, nil
}

SUB 函數(訂閱消息)

func (p *protocolV2) SUB(client *clientV2, params [][]byte) ([]byte, error) {
    ...
    //參數格式:SUB TopicName ChannelName
	if len(params) < 3 {
		return nil, protocol.NewFatalClientErr(nil, "E_INVALID", "SUB insufficient number of parameters")
	}

	topicName := string(params[1])
    //驗證TopicName 的有效性
	if !protocol.IsValidTopicName(topicName) {
        ...
	}

	channelName := string(params[2])
    //驗證ChannelName 的有效性
	if !protocol.IsValidChannelName(channelName) {
        ...
	}
    ...
	var channel *Channel
	for {
		topic := p.ctx.nsqd.GetTopic(topicName) //獲取topic實例,如果不存在就創建一個
		channel = topic.GetChannel(channelName) //獲取channel實例
        ...
		break
	}
    //更新client實例的訂閱狀態
	atomic.StoreInt32(&client.State, stateSubscribed)
	client.Channel = channel
	//通知messagePump中select 中的 subEventChan,有客戶端訂閱,準備消費
	client.SubEventChan <- channel

	return okBytes, nil
}

PUB 函數(發佈消息)

func (p *protocolV2) PUB(client *clientV2, params [][]byte) ([]byte, error) {
	var err error
    //參數格式: PUB TopicName
	if len(params) < 2 {
        ...
	}

	topicName := string(params[1])
    //驗證topicName 的有效性
	if !protocol.IsValidTopicName(topicName) {
        ...
	}
    //讀取消息長度
	bodyLen, err := readLen(client.Reader, client.lenSlice)
	if err != nil {
        ...
	}
    ....
    //讀取消息實體
	messageBody := make([]byte, bodyLen)
	_, err = io.ReadFull(client.Reader, messageBody)
    ...
    //獲取topic實例
	topic := p.ctx.nsqd.GetTopic(topicName)
	/*
       message.go 中定義了消息的結構體。
        type Message struct {
	        ID        MessageID
	        Body      []byte
	        Timestamp int64
	        Attempts  uint16
	        NSQDAddress string
	        Delegate MessageDelegate
	        autoResponseDisabled int32
	        responded            int32
        }
    */
    msg := NewMessage(topic.GenerateID(), messageBody)
    //通過topic將msg發給所有訂閱了這個topic的channel
	err = topic.PutMessage(msg)
    ...
    //統計發佈消息的數量
	client.PublishedMessage(topicName, 1)

	return okBytes, nil
}

MPUB 函數(批量消息發佈) 與PUB基本流程相同,區別在於MPUB通過readMPUB讀取多條消息,並且通過topic.PutMessages(messages) 發佈多條消息

func readMPUB(r io.Reader, tmp []byte, topic *Topic, maxMessageSize int64, maxBodySize int64) ([]*Message, error) {
    //獲取消息的總條數
	numMessages, err := readLen(r, tmp)
    ...
	// 這個不知道啥意思??
	maxMessages := (maxBodySize - 4) / 5
	....
	messages := make([]*Message, 0, numMessages)
    //讀取每條消息
	for i := int32(0); i < numMessages; i++ {
		//讀取消息長度
        messageSize, err := readLen(r, tmp)
	    ...
        //讀取消息實體
		msgBody := make([]byte, messageSize)
		_, err = io.ReadFull(r, msgBody)
        ...
		messages = append(messages, NewMessage(topic.GenerateID(), msgBody))
	}

	return messages, nil
}

DPUB 函數(發佈延時消息),這種消息並不會馬上發送給channel,而是先放入一個延時隊列中,然後通過nsqd中queueScanLoop這個goroutine 掃描這個延時隊列,將滿足條件的msg拋入channel。

func (p *protocolV2) DPUB(client *clientV2, params [][]byte) ([]byte, error) {
	var err error
    //參數格式:DPUB TopicName Timeout(延時時間)
	if len(params) < 3 {
        ...
	}
    //驗證TopicName 的有效性
	topicName := string(params[1])
	if !protocol.IsValidTopicName(topicName) {
        ...
	}
    /*  將 ASCII 轉換爲10進制
        在ASCII中 0-9 的ASCII碼是 48-57。
        在ByteToBase10中 d-'0' , 用ASCII表示就是 (48-57中任意數) - 48 = (0-9)
        我感覺這個挺好玩的。
    */ 
	timeoutMs, err := protocol.ByteToBase10(params[2])
	timeoutDuration := time.Duration(timeoutMs) * time.Millisecond
    ...
    //讀取消息實體
	bodyLen, err := readLen(client.Reader, client.lenSlice)
    ...
	messageBody := make([]byte, bodyLen)
	_, err = io.ReadFull(client.Reader, messageBody)
	topic := p.ctx.nsqd.GetTopic(topicName)
	msg := NewMessage(topic.GenerateID(), messageBody)
    //標記msg中的延時標記 deffered, topic在發佈消息給channel時,
    //如果遇到這個標記,會將消息發給延時隊列,而不是channel
	msg.deferred = timeoutDuration
	err = topic.PutMessage(msg)
    ...
	client.PublishedMessage(topicName, 1)

	return okBytes, nil
}

FIN 函數(消費確認)

func (p *protocolV2) FIN(client *clientV2, params [][]byte) ([]byte, error) {
    //只有訂閱的客戶端,才能進行消息確認命令
    state := atomic.LoadInt32(&client.State)
	if state != stateSubscribed && state != stateClosing {
        ...
	}
    //參數格式:FIN 消息ID(具有唯一性)
	if len(params) < 2 {
		return nil, protocol.NewFatalClientErr(nil, "E_INVALID", "FIN insufficient number of params")
	}
    
	id, err := getMessageID(params[1])
	/*
        channel每次消費一個消息,都會將消息放入
        inFlightMessages 和 inFlightPQ,當長時間沒有消費確認的時候,會將inFlinghPQ中的消息
        重新放入消費隊列。
        FIN命令的作用就是將 inFlightMessages 和 inFlightPQ 中的消息移除。
    */
	err = client.Channel.FinishMessage(client.ID, *id)
    //
    //消費確認統計
	client.FinishedMessage()

	return nil, nil
}

總結:

今天主要講了幾個主要的Exec的幾個常用命令函數 IDENTITY、SUB、PUB、MPUB、DPUB、FIN、NOP等。這些命令主要是與topic 和 channel 交互,進行消息發佈與消費的一些操作。至於其他未提到的,大家有興趣可以看看。

下次分享:topic 的具體實現

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