NSQ 源码分析之NSQD--Exec相关函数

14 篇文章 2 订阅

在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 的具体实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值