nsq topic创建流程
一、topic结构体:
二、topic的创建流程
topic的入口在哪里:GetTopic(),GetTopic如果存在则直接返回,不存在则NewTopic()
NewTopic 函数 主要做三件事:
一是实例化topic,
二是开启messagePump 协程进行消息分发处理,
三是通知 nsqd 有新的 topic创建,让 nsqd 上报 lookupd,通知lookupd有新的topic产生
messagePump:
messagePump selects over the in-memory and backend queue and // writes messages to every channel for this topic
这是topic的一个“守护”协程,负责分发整个 topic 接收到的消息给该 topic 下的 channel。(在NewTopic中最后通过新建协程创建)
// messagePump selects over the in-memory and backend queue and // writes messages to every channel for this topic func (t *Topic) messagePump() { var msg *Message var buf []byte var err error var chans []*Channel var memoryMsgChan chan *Message var backendChan <-chan []byte // do not pass messages before Start(), but avoid blocking Pause() or GetChannel() // 这里就是要等到startChan完成后才能往下走, for { select { case <-t.channelUpdateChan: //channel 变动通知 continue case <-t.pauseChan: //topic 暂停 continue case <-t.exitChan: //topic 退出 goto exit case <-t.startChan: //topic 开始接收消息 //也就是要等到topic执行完GetChannel()之后才会接着往下走 } break } t.RLock() for _, c := range t.channelMap { chans = append(chans, c) } t.RUnlock() if len(chans) > 0 && !t.IsPaused() { memoryMsgChan = t.memoryMsgChan backendChan = t.backend.ReadChan() } // main message loop //这里是守护协程的主体了,也就是这个for会一直跑 for { select { case msg = <-memoryMsgChan: //内存队列 //如果topic有收到新消息 case buf = <-backendChan: //磁盘队列 //如果消息是从diskqueue里来的,还要解码反序列化成msg msg, err = decodeMessage(buf) if err != nil { t.nsqd.logf(LOG_ERROR, "failed to decode message - %s" , err) continue } case <-t.channelUpdateChan: //如果有新的channel加入获删除后,则把chans置空后,重新遍历channelMap获取最新的channs 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: if len(chans) == 0 || t.IsPaused() { memoryMsgChan = nil backendChan = nil } else { memoryMsgChan = t.memoryMsgChan backendChan = t.backend.ReadChan() } continue case <-t.exitChan: goto exit } //将 msg 发送给所有订阅的 channel for i, channel := range chans { chanMsg := msg // copy the message because each channel // needs a unique instance but... // fastpath to avoid copy if its the first channel // (the topic already created the first copy) if i > 0 { chanMsg = NewMessage(msg.ID, msg.Body) chanMsg.Timestamp = msg.Timestamp chanMsg.deferred = msg.deferred } if chanMsg.deferred != 0 { // 如果是延时消息则将延时消息丢给channel channel.PutMessageDeferred(chanMsg, chanMsg.deferred) continue } err := channel.PutMessage(chanMsg) if err != nil { t.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.nsqd.logf(LOG_INFO, "TOPIC(%s): closing ... messagePump" , t.name) } |
解析一:
主循环中消息的分发:
前面两个case是监控消息的到达:
case msg = ←memoryMsgChan:
case buf = ←backendChan:
后面两个case是用来更新memoryMsgChan和backendChan的(为啥需要更新?因为对应的channel数量变化为0后或者topic暂停后,则不需要再往里面存消息,需要把memoryMsgChan,backendChan置为null)
case ←t.channelUpdateChan:
case ←t.pauseChan:
分析二:
发布消息:
发布单条消息:PutMessage(m *Message) error
发布多条消息:PutMessages(msgs []*Message) error
// PutMessage writes a Message to the queue func (t *Topic) PutMessage(m *Message) error { t.RLock() defer t.RUnlock() if atomic.LoadInt32(&t.exitFlag) == 1 { return errors.New( "exiting" ) } err := t.put(m) if err != nil { return err } atomic.AddUint64(&t.messageCount, 1) atomic.AddUint64(&t.messageBytes, uint64(len(m.Body))) return nil } // PutMessages writes multiple Messages to the queue func (t *Topic) PutMessages(msgs []*Message) error { t.RLock() defer t.RUnlock() if atomic.LoadInt32(&t.exitFlag) == 1 { return errors.New( "exiting" ) } messageTotalBytes := 0 for i, m := range msgs { err := t.put(m) if err != nil { atomic.AddUint64(&t.messageCount, uint64(i)) atomic.AddUint64(&t.messageBytes, uint64(messageTotalBytes)) return err } messageTotalBytes += len(m.Body) } atomic.AddUint64(&t.messageBytes, uint64(messageTotalBytes)) atomic.AddUint64(&t.messageCount, uint64(len(msgs))) return nil } //select 先走case, 也就是内存缓冲区 //如果case没有执行,看看有没有default, 接着执行default //也就是说 消息先投递到内存, 内存缓冲区满了之后会将消息写入到磁盘队列 //这里使用了sync.Pool 减少GC //同时也会看每次写入磁盘是否有错误, 设置其健康状态保存已暴露给api接口/ping使用 func (t *Topic) put(m *Message) error { select { case t.memoryMsgChan <- m: //内存队列 default : //内存不足 err := writeMessageToBackend(m, t.backend) t.nsqd.SetHealth(err) if err != nil { t.nsqd.logf(LOG_ERROR, "TOPIC(%s) ERROR: failed to write message to backend - %s" , t.name, err) return err } } return nil } |
删除消息 && 关闭topic
删除topic:func (t *Topic) Delete() error
关闭topic:func (t *Topic) Close() error
删除的操作主要有:
- 通知nsqd,把topic从lookup中反注册。t.nsqd.Notify(t, !t.ephemeral)
- 关闭topic.exitChan管道让topic.messagePump退出;
- 循环删除其channelMap列表,channel.Delete();(Delete专用)
- 循环channelMap执行channel.Close(),关闭下面的所有channel(close的时候)
- 将内存未消费的消息持久化;(close的时候)
获取一个channel如果不存在则创建 && 删除存在的channel
获取一个channel如果不存在则创建: func (t *Topic) GetChannel(channelName string) *Channel
获取一个存在的channel:func (t *Topic) GetExistingChannel(channelName string) (*Channel, error)
删除一个存在的channel:func (t *Topic) DeleteExistingChannel(channelName string) error
参考: