消息发布流程
- 消息发布时,消息发往指定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()中处理