NSQ 源碼分析之NSQD--Topic

今天主要講的是NSQ topic 的代碼實現,topic作爲MQ的重要概念,瞭解它的實現,對我們理解其他MQ的topic,有很多的益處。

主要代碼文件:

1.nsqd/topic.go

topic結構體

type Topic struct {
	// 64bit atomic vars need to be first for proper alignment on 32bit platforms
	messageCount uint64 //消息累計條數  後期查看每個topic的狀態時有用
	messageBytes uint64 //消息累計字節數 後期查看每個topic的狀態時有用

	sync.RWMutex

	name              string  //主題名稱
	channelMap        map[string]*Channel  //主題擁有的channel
	backend           BackendQueue //磁盤隊列
	memoryMsgChan     chan *Message //內存隊列
	startChan         chan int  //topic 開始接收消息的chan
	exitChan          chan int //topic 結束的chann
	channelUpdateChan chan int //channel 更新通知(停止,刪除,新增)
	waitGroup         util.WaitGroupWrapper
	exitFlag          int32
	idFactory         *guidFactory  //id 生成器

	ephemeral      bool
	deleteCallback func(*Topic) //topic 刪除的 回調函數
	deleter        sync.Once

	paused    int32
	pauseChan chan int

	ctx *context //包裝了nsqd的context
}

NewTopic 函數 主要做三件事,一是實例化topic, 二是開啓messagePump goroutine 進行消息處理,三是通知 nsqd 有新的 topic創建,讓 nsqd 上報 lookupd

func NewTopic(topicName string, ctx *context, deleteCallback func(*Topic)) *Topic {
	//實例化topic
    t := &Topic{
        ...
	}
	// 創建內存隊列
	if ctx.nsqd.getOpts().MemQueueSize > 0 {
		t.memoryMsgChan = make(chan *Message, ctx.nsqd.getOpts().MemQueueSize)
	}
    //ephemeral 有特殊的用途,暫時還不知道幹啥?
	if strings.HasSuffix(topicName, "#ephemeral") {
		t.ephemeral = true
		t.backend = newDummyBackendQueue()
	} else {//創建磁盤隊列
    	....
        //go-diskqueue 的實例
        t.backend = diskqueue.New(
            ....
		)
	}
	//messagePump 主要作用是,發送msg給所有訂閱了這個 topic 下的 channel。(channelMap)
	t.waitGroup.Wrap(t.messagePump)
    //通知 nsqd 有新的 topi c創建。
	t.ctx.nsqd.Notify(t)

	return t
}

messagePump 函數,主要處理 topic/channel 的變動及發佈消息給 channel

func (t *Topic) messagePump() {
	var msg *Message //消息
	var buf []byte //緩存
	var err error
	var chans []*Channel 
	var memoryMsgChan chan *Message
	var backendChan chan []byte
	for {
		select {
		case <-t.channelUpdateChan: //channel 變動通知
			continue
		case <-t.pauseChan: //topic 暫停
			continue
		case <-t.exitChan: //topic 退出
			goto exit
		case <-t.startChan: //topic 開始接收消息
		}
		break
	}
	t.RLock()
    //收集訂閱的channel
	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 {
		select {
		case msg = <-memoryMsgChan: //內存隊列
		case buf = <-backendChan: //磁盤隊列
			msg, err = decodeMessage(buf) //解析成Message
	        ...
		case <-t.channelUpdateChan: //有變動通知,重新收集topic訂閱的channel
            ...
			continue
		case <-t.pauseChan: //topic 停止通知 設置 memoryMsgChan、backendChan  
	        ....
			continue
		case <-t.exitChan:
			goto exit
		}
        //將 msg 發送給所有訂閱的 channel
		for i, channel := range chans {
			chanMsg := msg
			
			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
			}
            //發送 msg 到 channel
			err := channel.PutMessage(chanMsg)
		    ...
		}
	}

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

PutMessage/PutMessages 函數都是將消息發送(put)到topic的隊列(內存/磁盤)中,流程基本相同,都是要累計消息條數和累計消息的字節總數。

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
}
func (t *Topic) put(m *Message) error {
	select {
	case t.memoryMsgChan <- m: //內存隊列
	default: //內存不足
		b := bufferPoolGet()
		err := writeMessageToBackend(b, m, t.backend)
		bufferPoolPut(b)
	    ...
	}
	return nil
}

 

其他函數:

Delete/Close: Topic 退出結束

Pause/UnPause:Topic 暫停/重啓

flush:將內存隊列的消息,全部刷新到磁盤進行持久化(exit 操作的時候)

總結:

今天主要分析了Topic的代碼實現,在這裏主要需要關注的是 topic 如何接收消息(pub),又如何將消息發送給 channel(messagePump),最後需要關注的是什麼時候將消息存儲在內存,什麼時候存儲在磁盤。

下次分享:channel的代碼實現

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