nsq源码(3) nsqd 消息发布

消息发布流程

  • 消息发布时,消息发往指定topic的所有channel
  • 一个客户端只能指定topic的一个channel
流程图.png

protocolV2处理对象

protocolV2.PUB()发布消息
  • 每当client连接进来时,都会开启Protocol的Handle协程,执行IOLoop()来找到或新建client对象
  • 发布消息时同时IOLoop()里protocolV2.Exec()会执行PUB(client, params)执行发布指令,并且为当前nsqd新建或绑定topic对象,同时 开启topic.messagePump()协程处理channel.memoryMsgChan消息
func (p *protocolV2) PUB(client *clientV2, params [][]byte) ([]byte, error) {
    var err error

    topicName := string(params[1])
    if !protocol.IsValidTopicName(topicName) {
        return nil, protocol.NewFatalClientErr(nil, "E_BAD_TOPIC",
            fmt.Sprintf("PUB topic name %q is not valid", topicName))
    }

    bodyLen, err := readLen(client.Reader, client.lenSlice)
    if err != nil {
        return nil, protocol.NewFatalClientErr(err, "E_BAD_MESSAGE", "PUB failed to read message body size")
    }

    messageBody := make([]byte, bodyLen)
    _, err = io.ReadFull(client.Reader, messageBody)
    if err != nil {
        return nil, protocol.NewFatalClientErr(err, "E_BAD_MESSAGE", "PUB failed to read message body")
    }

    // 如果nsqd没有此topic则创建
    // topic.messagePump会开启topic层的消息处理协程(topic的消息封装和数据发布由两个协程分开处理)
    topic := p.ctx.nsqd.GetTopic(topicName)
    // 创建新消息对象
    msg := NewMessage(topic.GenerateID(), messageBody)

    // 消息发布,使用ID来标识topic,topic.PutMessage()把消息发往topic.memoryMsgChan
    err = topic.PutMessage(msg)
    if err != nil {
        return nil, protocol.NewFatalClientErr(err, "E_PUB_FAILED", "PUB failed "+err.Error())
    }

    client.PublishedMessage(topicName, 1)

    return okBytes, nil
}
protocolV2.messagePump()接收channel.memoryMsgChan
  • 在一个消息从client发布->topic分发给所有channel->channel发布之后,此时消息存在于topic的每个channel.memoryMsgChan中
  • 每个client都会有protocolV2.messagePump()协程
  • 一个client只能订阅一个channel

func (p *protocolV2) messagePump(client *clientV2, startedChan chan bool) {
    // client.channel.PutMessage()把消息发往channel.memoryMsgChan
    // subEventChan会获取订阅的channel,进而能够获取channel.memoryMsgChan里的消息
    subEventChan := client.SubEventChan
    identifyEventChan := client.IdentifyEventChan
    outputBufferTicker := time.NewTicker(client.OutputBufferTimeout)
    heartbeatTicker := time.NewTicker(client.HeartbeatInterval)
    heartbeatChan := heartbeatTicker.C
    msgTimeout := client.MsgTimeout

    // 初始化已完成,通知外部IOLoop()继续执行难
    close(startedChan)

    for {
        //如果此client的subChannel未被订阅,一直刷新处理
        if subChannel == nil || !client.IsReadyForMessages() {
            // the client is not ready to receive messages...
            memoryMsgChan = nil
            backendMsgChan = nil
            flusherChan = nil
            // force flush
            client.writeLock.Lock()
            err = client.Flush()
            client.writeLock.Unlock()
            if err != nil {
                goto exit
            }
            flushed = true
        } else if flushed {
            // last iteration we flushed...
            // do not select on the flusher ticker channel
            memoryMsgChan = subChannel.memoryMsgChan
            backendMsgChan = subChannel.backend.ReadChan()
            flusherChan = nil
        } else {
            // we're buffered (if there isn't any more data we should flush)...
            // select on the flusher ticker channel, too
            memoryMsgChan = subChannel.memoryMsgChan
            backendMsgChan = subChannel.backend.ReadChan()
            flusherChan = outputBufferTicker.C
        }

        select {
        case <-flusherChan:
            // if this case wins, we're either starved
            // or we won the race between other channels...
            // in either case, force flush
            client.writeLock.Lock()
            err = client.Flush()
            client.writeLock.Unlock()
            if err != nil {
                goto exit
            }
            flushed = true
        case <-client.ReadyStateChan:
        // 防止重复订阅,一个client只能订阅一个channel
        case subChannel = <-subEventChan:
            // you can't SUB anymore
            subEventChan = nil
        case identifyData := <-identifyEventChan:
            ...
            msgTimeout = identifyData.MsgTimeout
        case <-heartbeatChan:
            err = p.Send(client, frameTypeResponse, heartbeatBytes)
            if err != nil {
                goto exit
            }
        case b := <-backendMsgChan:
            msg, err := decodeMessage(b)
            if err != nil {
                p.ctx.nsqd.logf(LOG_ERROR, "failed to decode message - %s", err)
                continue
            }
            msg.Attempts++

            subChannel.StartInFlightTimeout(msg, client.ID, msgTimeout)
            client.SendingMessage()
            err = p.SendMessage(client, msg)
            if err != nil {
                goto exit
            }
            flushed = false
        case msg := <-memoryMsgChan:
            if sampleRate > 0 && rand.Int31n(100) > sampleRate {
                continue
            }
            msg.Attempts++

            subChannel.StartInFlightTimeout(msg, client.ID, msgTimeout)
            
            // 如果此client已订阅,subChannel则为订阅指定的channel
            // 向client发送消息
            client.SendingMessage()
            err = p.SendMessage(client, msg)
            if err != nil {
                goto exit
            }
            
            flushed = false
        case <-client.ExitChan:
            goto exit
        }
    }

exit:
    p.ctx.nsqd.logf(LOG_INFO, "PROTOCOL(V2): [%s] exiting messagePump", client)
    heartbeatTicker.Stop()
    outputBufferTicker.Stop()
}

Topic

  • 接收在Protocol层的topic.PutMessage(msg),把消息分发至此topic的每个channel
  • topic.messagePump()会遍历topic的所有channel
  • 同时读取topic.memoryMsgChan并把消息对象复制给这个topic的所有channel
  • 接着把执行每个channel.PutMessage(chanMsg)发向channel.memoryMsgChan,从而把消息发布至所有client
func (t *Topic) messagePump() {

    // t.Start()才会触发startChan退出循环
    for {
        select {
        case <-t.channelUpdateChan:
            continue
        case <-t.pauseChan:
            continue
        case <-t.exitChan:
            goto exit
        case <-t.startChan:
        }
        break
    }
    t.RLock()
    // 遍历这个topic的所有channel
    for _, c := range t.channelMap {
        chans = append(chans, c)
    }
    // 消息对象会被topic.PutMessage()发送到topic.memoryMsgChan
    t.RUnlock()
    if len(chans) > 0 && !t.IsPaused() {
        memoryMsgChan = t.memoryMsgChan
        backendChan = t.backend.ReadChan()
    }

    // main message loop
    for {
        select {
        case msg = <-memoryMsgChan:
        case buf = <-backendChan:
            msg, err = decodeMessage(buf)
            ...
        // 更新channel操作
        case <-t.channelUpdateChan:
            chans = chans[:0]
            t.RLock()
            for _, c := range t.channelMap {
                chans = append(chans, c)
            }
            t.RUnlock()
            if len(chans) == 0 || t.IsPaused() {
                memoryMsgChan = nil
                backendChan = nil
            } else {
                memoryMsgChan = t.memoryMsgChan
                backendChan = t.backend.ReadChan()
            }
            continue
        case <-t.pauseChan:
            ...
            continue
        case <-t.exitChan:
            goto exit
        }

        for i, channel := range chans {
            chanMsg := msg
            // 把消息对象复制给这个topic的所有channel
            if i > 0 {
                chanMsg = NewMessage(msg.ID, msg.Body)
                chanMsg.Timestamp = msg.Timestamp
                chanMsg.deferred = msg.deferred
            }
            if chanMsg.deferred != 0 {
                channel.PutMessageDeferred(chanMsg, chanMsg.deferred)
                continue
            }
            err := channel.PutMessage(chanMsg)
            if err != nil {
                t.ctx.nsqd.logf(LOG_ERROR,
                    "TOPIC(%s) ERROR: failed to put msg(%s) to channel(%s) - %s",
                    t.name, msg.ID, channel.name, err)
            }
        }
    }

exit:
    t.ctx.nsqd.logf(LOG_INFO, "TOPIC(%s): closing ... messagePump", t.name)
}

channel

  • channel对象本身没有messagePump()来处理channel.PutMessage(),channel.memoryMsgChan将在protocolV2.messagePump()中处理

你可能感兴趣的:(nsq源码(3) nsqd 消息发布)