今天主要講的是 NSQD 中 queueScanLoop代碼實現,這個函數的作用是用來維護 channel 中的延時隊列和等待消費確認隊列。將超時的消息重新加入消費隊列中。
主要代碼文件:
1.nsqd/nsqd.go
queueScanLoop 函數在 Main 函數中啓動 n.waitGroup.Wrap(n.queueScanLoop)
func (n *NSQD) queueScanLoop() {
//傳遞需要處理的 channel
workCh := make(chan *Channel, n.getOpts().QueueScanSelectionCount)
responseCh := make(chan bool, n.getOpts().QueueScanSelectionCount)
closeCh := make(chan int)
//定時執行 loop 的間隔時間
workTicker := time.NewTicker(n.getOpts().QueueScanInterval)
//goroutine 池維護時間
refreshTicker := time.NewTicker(n.getOpts().QueueScanRefreshInterval)
channels := n.channels() //獲取當前nsqd 中所有的channels
//goroutine 池任務處理和維護
n.resizePool(len(channels), workCh, responseCh, closeCh)
for {
select {
case <-workTicker.C:
if len(channels) == 0 {
continue
}
case <-refreshTicker.C:
channels = n.channels()
n.resizePool(len(channels), workCh, responseCh, closeCh)
continue
case <-n.exitChan:
goto exit
}
num := n.getOpts().QueueScanSelectionCount
if num > len(channels) {
num = len(channels)
}
loop:
//UniqRands 是作用是,從 channels 中隨機選擇 num 個 channel
for _, i := range util.UniqRands(num, len(channels)) {
workCh <- channels[i]
}
//等待處理響應,記錄失敗次數
numDirty := 0
for i := 0; i < num; i++ {
if <-responseCh {
numDirty++
}
}
//失敗次數超過一定比率,重新執行 loop.
if float64(numDirty)/float64(num) > n.getOpts().QueueScanDirtyPercent {
goto loop
}
}
exit:
n.logf(LOG_INFO, "QUEUESCAN: closing")
close(closeCh)
workTicker.Stop()
refreshTicker.Stop()
}
resizePool 函數主要作用是維護 goroutine 池的數量。
func (n *NSQD) resizePool(num int, workCh chan *Channel, responseCh chan bool, closeCh chan int) {
//理想線程池數量
idealPoolSize := int(float64(num) * 0.25)
if idealPoolSize < 1 {
idealPoolSize = 1
} else if idealPoolSize > n.getOpts().QueueScanWorkerPoolMax {
idealPoolSize = n.getOpts().QueueScanWorkerPoolMax
}
for {
if idealPoolSize == n.poolSize {
break
} else if idealPoolSize < n.poolSize {
closeCh <- 1 //某個關閉工作線程
n.poolSize--
} else {
//增加工作線程 處理延遲優先級隊列和消費確認優先級隊列
n.waitGroup.Wrap(func() {
n.queueScanWorker(workCh, responseCh, closeCh)
})
n.poolSize++
}
}
}
queueScanWorker 主要處理 channel 中的 延時優先級隊列和消費確認優先級隊列,具體處理流程參考 https://blog.csdn.net/H_L_S/article/details/105155235。
func (n *NSQD) queueScanWorker(workCh chan *Channel, responseCh chan bool, closeCh chan int) {
for {
select {
case c := <-workCh:
now := time.Now().UnixNano()
dirty := false
if c.processInFlightQueue(now) { //消費確認優先級隊列
dirty = true
}
if c.processDeferredQueue(now) { //延時優先級隊列處理
dirty = true
}
responseCh <- dirty
case <-closeCh: //關閉工作線程
return
}
}
}
總結:
queueScanLoop 函數維護並管理 goroutine 池的數量,這些 goroutine 主要用於處理 channel 中 延時優先級隊列和等待消費確認優先級隊列。同時 queueScanLoop 循環隨機選擇 channel 並交給工作線程池進行處理。
下次分享:NSQD 消息協議 Message