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相关函数

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