nsqdnsqd Main() 方法启动了多个 协程
分别是:
TCPServer
httpserver
queueScanLoop
lookupLoop
他们都是先用exitFunc来包装了,exitFunc是一个闭包函数,实现如下:
// 保证任务之启动一次,并且统一对err处理,这些err是会导致进程退出
exitFunc := func(err error) {
once.Do(func() {
if err != nil {
n.logf(LOG_FATAL, "%s", err)
}
exitCh <- err
})
}
用n.waitGroup.Wrap() 来启动,其实现如下
// 这段代码不但封装了 wg的 add ,done 操作,而且全局的wg会等待所有go执行完毕,然后graceful shutdown
func (w *WaitGroupWrapper) Wrap(cb func()) {
w.Add(1)
go func() {
cb()
w.Done()
}()
}
// 这里全局的wg在哪里等待所有goroutine 退出呢,就是在处理上面err的地方
最终是这样调用:
n.waitGroup.Wrap(func() {
exitFunc(protocol.TCPServer(n.tcpListener, n.tcpServer, n.logf))
})
TCPServer
tcp server主要是监听客户端的连接到来,然后开go去处理连接
func TCPServer(listener net.Listener, handler TCPHandler, logf lg.AppLogFunc) error {
logf(lg.INFO, "TCP: listening on %s", listener.Addr())
var wg sync.WaitGroup
for {
clientConn, err := listener.Accept()
if err != nil {
break
}
wg.Add(1)
go func() {
handler.Handle(clientConn)
wg.Done()
}()
}
// wait to return until all handler goroutines complete
wg.Wait()
return nil
}
具体怎么处理呢?看Handle的实现,里面主要是IOLoop(client)在处理消息
func (p *protocolV2) IOLoop(c protocol.Client) error {
var err error
var line []byte
var zeroTime time.Time
client, ok := c.(*clientV2)
messagePumpStartedChan := make(chan bool)
go p.messagePump(client, messagePumpStartedChan)
<-messagePumpStartedChan
for {
if client.HeartbeatInterval > 0 {
client.SetReadDeadline(time.Now().Add(client.HeartbeatInterval * 2))
} else {
client.SetReadDeadline(zeroTime)
}
line, err = client.Reader.ReadSlice('\n')
if err != nil {
if err == io.EOF {
err = nil
} else {
err = fmt.Errorf("failed to read command - %s", err)
}
break
}
// trim the '\n'
line = line[:len(line)-1]
// optionally trim the '\r'
if len(line) > 0 && line[len(line)-1] == '\r' {
line = line[:len(line)-1]
}
params := bytes.Split(line, separatorBytes)
var response []byte
// 这里执行对应的命令
response, err = p.Exec(client, params)
if err != nil {
ctx := ""
if parentErr := err.(protocol.ChildErr).Parent(); parentErr != nil {
ctx = " - " + parentErr.Error()
}
// 执行失败,返回错误消息
sendErr := p.Send(client, frameTypeError, []byte(err.Error()))
if sendErr != nil {
break
}
// errors of type FatalClientErr should forceably close the connection
if _, ok := err.(*protocol.FatalClientErr); ok {
break
}
continue
}
// 返回成功消息
if response != nil {
err = p.Send(client, frameTypeResponse, response)
if err != nil {
err = fmt.Errorf("failed to send response - %s", err)
break
}
}
}
// 当前client因err而终止任务
close(client.ExitChan)
if client.Channel != nil {
client.Channel.RemoveClient(client.ID)
}
return err
}
httpserver
这个没啥好讲的,就是一个http服务,不过有一个值得学习的地方是,nsq对路由使用了装饰着模式
router.Handle("GET", "/ping", http_api.Decorate(s.pingHandler, log, http_api.PlainText))
func Decorate(f APIHandler, ds ...Decorator) httprouter.Handle {
decorated := f
for _, decorate := range ds {
decorated = decorate(decorated)
}
return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
decorated(w, req, ps)
}
}