今天主要講的是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的代碼實現