今天主要讲的是 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