• NSQ(5)-nsqlookupd执行流程源码剖析


    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返回给客户端。
    所有博文均为原著,如若转载,请注明出处!
  • 相关阅读:
    tomcat 安装时出现 Failed to install Tomcat7 service
    当Tomcat配置安装都成功端口号也没问题时网页输入http://localhost:8080/打不开如何解决
    渣渣HTML外加javascript做的第一个同乡会网页(好多功能都没写,只弄出来一个框架)
    jsp 有9个内置对象
    JavaScript编写的计时器/倒计时也可以改一下参数(用到的图片已经上传)
    当编写Servlet时出现type Status report message HTTP method GET is not supported by this URL description The specified HTTP method is not allowed for the requested resource.
    渣渣用JavaScript开发的消消乐,由于没有按照正规消消乐形式生成,是随机产生图形,所以存在一个算法bug,具体看下面,高手想到可以告诉我
    遇到浏览器设置后电脑上大部分应用打开都是浏览器方式
    Myeclipese建立servelet时出现带有javax.servlet.http.HttpServlet;变成了红色的解决方法
    PreferencesUtils SharedPreferences很好用的工具
  • 原文地址:https://www.cnblogs.com/zpcoding/p/14517539.html
Copyright © 2020-2023  润新知