nsq 消息队列-02组件nsqd篇

142 阅读1分钟

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)
	}
}

queueScanLoop

lookupLoop