nsqlookupd执行流程源码剖析
func (p *program) Start() error {
opts := nsqlookupd.NewOptions()
flagSet := nsqlookupdFlagSet(opts)
flagSet.Parse(os.Args[1:])
if flagSet.Lookup("version").Value.(flag.Getter).Get().(bool) {
fmt.Println(version.String("nsqlookupd"))
os.Exit(0)
}
var cfg map[string]interface{}
configFile := flagSet.Lookup("config").Value.String()
if configFile != "" {
_, err := toml.DecodeFile(configFile, &cfg)
if err != nil {
logFatal("failed to load config file %s - %s", configFile, err)
}
}
options.Resolve(opts, flagSet, cfg)
nsqlookupd, err := nsqlookupd.New(opts)
if err != nil {
logFatal("failed to instantiate nsqlookupd", err)
}
p.nsqlookupd = nsqlookupd
go func() {
err := p.nsqlookupd.Main()
if err != nil {
p.Stop()
os.Exit(1)
}
}()
return nil
}
- 初始化配置参数,构造一个新的 nsqlookupd,仍后以一个goroutine的方式启动 nsqlookupd.Main()函数。
func NewOptions() *Options {
hostname, err := os.Hostname()
if err != nil {
log.Fatal(err)
}
return &Options{
LogPrefix: "[nsqlookupd] ",
LogLevel: lg.INFO,
TCPAddress: "0.0.0.0:4160",
HTTPAddress: "0.0.0.0:4161",
BroadcastAddress: hostname,
InactiveProducerTimeout: 300 * time.Second,
TombstoneLifetime: 45 * time.Second,
}
}
- 可以看到 nsqlookupd 的默认配置还是很少的。
// Main starts an instance of nsqlookupd and returns an
// error if there was a problem starting up.
func (l *NSQLookupd) Main() error {
exitCh := make(chan error)
var once sync.Once
exitFunc := func(err error) {
once.Do(func() {
if err != nil {
l.logf(LOG_FATAL, "%s", err)
}
exitCh <- err
})
}
l.tcpServer = &tcpServer{nsqlookupd: l}
l.waitGroup.Wrap(func() {
exitFunc(protocol.TCPServer(l.tcpListener, l.tcpServer, l.logf))
})
httpServer := newHTTPServer(l)
l.waitGroup.Wrap(func() {
exitFunc(http_api.Serve(l.httpListener, httpServer, "HTTP", l.logf))
})
err := <-exitCh
return err
}
- 它的主要工作就是启动了TCP和HTTP对指定端口的监听,我们现在所关注的也就变成对客户端传来的信息的处理,先来看一下TCP的处理过程,nsqd和nsqloolupd建立的就是TCP连接。
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 {
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
logf(lg.WARN, "temporary Accept() failure - %s", err)
runtime.Gosched()
continue
}
// theres no direct way to detect this error because it is not exposed
if !strings.Contains(err.Error(), "use of closed network connection") {
return fmt.Errorf("listener.Accept() error - %s", err)
}
break
}
wg.Add(1)
go func() {
handler.Handle(clientConn)
wg.Done()
}()
}
// wait to return until all handler goroutines complete
wg.Wait()
logf(lg.INFO, "TCP: closing %s", listener.Addr())
return nil
}
- nsqlookupd监听指定的端口,每当与一个新用户建立连接就启动一个goroutine来处理。
func (p *tcpServer) Handle(clientConn net.Conn) {
p.nsqlookupd.logf(LOG_INFO, "TCP: new client(%s)", clientConn.RemoteAddr())
// The client should initialize itself by sending a 4 byte sequence indicating
// the version of the protocol that it intends to communicate, this will allow us
// to gracefully upgrade the protocol away from text/line oriented to whatever...
buf := make([]byte, 4)
_, err := io.ReadFull(clientConn, buf)
if err != nil {
p.nsqlookupd.logf(LOG_ERROR, "failed to read protocol version - %s", err)
clientConn.Close()
return
}
protocolMagic := string(buf)
p.nsqlookupd.logf(LOG_INFO, "CLIENT(%s): desired protocol magic '%s'",
clientConn.RemoteAddr(), protocolMagic)
var prot protocol.Protocol
switch protocolMagic {
case " V1":
prot = &LookupProtocolV1{nsqlookupd: p.nsqlookupd}
default:
protocol.SendResponse(clientConn, []byte("E_BAD_PROTOCOL"))
clientConn.Close()
p.nsqlookupd.logf(LOG_ERROR, "client(%s) bad protocol magic '%s'",
clientConn.RemoteAddr(), protocolMagic)
return
}
p.conns.Store(clientConn.RemoteAddr(), clientConn)
err = prot.IOLoop(clientConn)
if err != nil {
p.nsqlookupd.logf(LOG_ERROR, "client(%s) - %s", clientConn.RemoteAddr(), err)
}
p.conns.Delete(clientConn.RemoteAddr())
}
- 我们发现,nsqd和nsqlookupd在建立起连接的第一次通信之后会商定通信的协议,只能是V1,之后获取协议对应的实例,执行IOLoop方法。
func (p *LookupProtocolV1) IOLoop(conn net.Conn) error {
var err error
var line string
client := NewClientV1(conn)
reader := bufio.NewReader(client)
for {
line, err = reader.ReadString('
')
if err != nil {
break
}
line = strings.TrimSpace(line)
params := strings.Split(line, " ")
var response []byte
response, err = p.Exec(client, reader, params)
if err != nil {
ctx := ""
if parentErr := err.(protocol.ChildErr).Parent(); parentErr != nil {
ctx = " - " + parentErr.Error()
}
p.nsqlookupd.logf(LOG_ERROR, "[%s] - %s%s", client, err, ctx)
_, sendErr := protocol.SendResponse(client, []byte(err.Error()))
if sendErr != nil {
p.nsqlookupd.logf(LOG_ERROR, "[%s] - %s%s", client, sendErr, ctx)
break
}
// errors of type FatalClientErr should forceably close the connection
if _, ok := err.(*protocol.FatalClientErr); ok {
break
}
continue
}
if response != nil {
_, err = protocol.SendResponse(client, response)
if err != nil {
break
}
}
}
conn.Close()
p.nsqlookupd.logf(LOG_INFO, "CLIENT(%s): closing", client)
if client.peerInfo != nil {
registrations := p.nsqlookupd.DB.LookupRegistrations(client.peerInfo.id)
for _, r := range registrations {
if removed, _ := p.nsqlookupd.DB.RemoveProducer(r, client.peerInfo.id); removed {
p.nsqlookupd.logf(LOG_INFO, "DB: client(%s) UNREGISTER category:%s key:%s subkey:%s",
client, r.Category, r.Key, r.SubKey)
}
}
}
return err
}
- 首先对客户端的连接包装了一下,并且获取连接的带缓冲区的reader,接下来就是不断从reader里面读取客户端发来的信息,解析完之后传入exec方法进行响应,最后将处理结果response返回给客户端。