• go笔记 NSQ (4) ( nsqd启动监听来了解go如何编写tcp与http服务端,以及sync.WaitGroup线程同步工具使用 )


    在上节中已经成功的解析了系统配置并创建了核心结构体nsqd,本文主要从nsqd的main方法入手

    在main方法中,主要会创建一系列的tcp监听器,以及轮询检测。

    func (n *NSQD) Main() {
    	var err error
    	ctx := &context{n}
    	//检车是否能开启tcp
    	broadcastAddr, err := net.ResolveTCPAddr("tcp", n.getOpts().BroadcastAddress+":0")
    	if err != nil {
    		n.logf(LOG_FATAL, "failed to resolve broadcast address (%s) - %s", n.getOpts().BroadcastAddress, err)
    		os.Exit(1)
    	}
    	//tcp监听地址   主要用来联系消费以及nsqlookupd
    	n.tcpListener, err = net.Listen("tcp", n.getOpts().TCPAddress)
    	if err != nil {
    		n.logf(LOG_FATAL, "listen (%s) failed - %s", n.getOpts().TCPAddress, err)
    		os.Exit(1)
    	}
    	//http监听器 主要用来联系生产者
    	n.httpListener, err = net.Listen("tcp", n.getOpts().HTTPAddress)
    	if err != nil {
    		n.logf(LOG_FATAL, "listen (%s) failed - %s", n.getOpts().HTTPAddress, err)
    		os.Exit(1)
    	}
    	//https监听  如果需要的话
    	if n.tlsConfig != nil && n.getOpts().HTTPSAddress != "" {
    		n.httpsListener, err = tls.Listen("tcp", n.getOpts().HTTPSAddress, n.tlsConfig)
    		if err != nil {
    			n.logf(LOG_FATAL, "listen (%s) failed - %s", n.getOpts().HTTPSAddress, err)
    			os.Exit(1)
    		}
    	}
    	
    	tcpServer := &tcpServer{ctx: ctx}
    	//运行tcp监听器   tcpServer为处理器
    	n.waitGroup.Wrap(func() {
    		protocol.TCPServer(n.tcpListener, tcpServer, n.logf)
    	})
    	httpServer := newHTTPServer(ctx, false, n.getOpts().TLSRequired == TLSRequired)
    	//运行http监听器   httpServer为处理器
    	n.waitGroup.Wrap(func() {
    		http_api.Serve(n.httpListener, httpServer, "HTTP", n.logf)
    	})
    	//运行https监听器   httpsServer为处理器
    	if n.tlsConfig != nil && n.getOpts().HTTPSAddress != "" {
    		httpsServer := newHTTPServer(ctx, true, true)
    		n.waitGroup.Wrap(func() {
    			http_api.Serve(n.httpsListener, httpsServer, "HTTPS", n.logf)
    		})
    	}
    
    
    	/**************监听有关处理完毕**************/
    	
    	/**************轮询处理器**************/
    	n.waitGroup.Wrap(n.queueScanLoop)
    	n.waitGroup.Wrap(n.lookupLoop)
    	if n.getOpts().StatsdAddress != "" {
    		n.waitGroup.Wrap(n.statsdLoop)
    	}
    
    	var httpsAddr *net.TCPAddr
    	if n.httpsListener != nil {
    		httpsAddr = n.RealHTTPSAddr()
    	}
    
    	if n.getOpts().GossipAddress != "" {
    		serf, err := initSerf(
    			n.getOpts(),
    			n.serfEventChan,
    			n.RealTCPAddr(),
    			n.RealHTTPAddr(),
    			httpsAddr,
    			broadcastAddr,
    			n.initialGossipKey(),
    		)
    		if err != nil {
    			n.logf(LOG_FATAL, "failed to initialize Serf - %s", err)
    			os.Exit(1)
    		}
    		n.serf = serf
    		n.waitGroup.Wrap(func() { n.serfEventLoop() })
    		n.waitGroup.Wrap(func() { n.gossipLoop() })
    	}
    }
    

    1.sync.WaitGroup的使用

      在代码中我们可以看到很多如下的操作

    n.waitGroup.Wrap(func() {
    		。。。
    	})
    

      其实点进这个方法看,就是包装了一下而已

    type WaitGroupWrapper struct {
    sync.WaitGroup
    }

    func (w *WaitGroupWrapper) Wrap(cb func()) { w.Add(1) go func() { cb() w.Done() }() }

      关于这个的设计意义nsq官方有做说明

      

     通过源代码我们也可以知道 ,在svc中 最后的Stop方法需要在这些goroute中执行的方法都执行完后才进行最后的解锁操作。我们主要关注下sync.WaitGroup的用法,其主要的作用用来让一个或者多个goroute等待其他一组goroute完成后再进行操作。如果有了解过java的可以将其理解为java中的CountDownLatch 。使用demo如下

    var wt sync.WaitGroup
    func main() {
    	wt.Add(2)
    	go func() {
    		fmt.Println("hello 1")
    		wt.Done()
    	}()
    	go func() {
    		fmt.Println("hello 2")
    		time.Sleep(2*time.Second)
    		wt.Done()
    	}()
    	wt.Wait()
    	fmt.Println("time end...")
    }
    

       wt.Wait方法会阻塞到wt中保存的计数减为0才会继续执行后面的操作

    2.tcp编程

      从main方法中我可以注意下儿操作tcp的代码

    	n.tcpListener, err = net.Listen("tcp", n.getOpts().TCPAddress)
    	if err != nil {
    		n.logf(LOG_FATAL, "listen (%s) failed - %s", n.getOpts().TCPAddress, err)
    		os.Exit(1)
    	}
    
            ....
    	tcpServer := &tcpServer{ctx: ctx}
    	n.waitGroup.Wrap(func() {
    		protocol.TCPServer(n.tcpListener, tcpServer, n.logf)
    	})
    

      可以看到创建了一个tcp监听,并且创建了一个tcp处理器tcpServer对其进行处理。我们主要看下这个tcpServer 其主要负责处理消费者以及nsqlookupd信息。(也可以用来处理生产者生产的消息)

    func TCPServer(listener net.Listener, handler TCPHandler, logf lg.AppLogFunc) {
    	logf(lg.INFO, "TCP: listening on %s", listener.Addr())
    
    	for {
    		//监听新的连接
    		clientConn, err := listener.Accept()
    		if err != nil {
    			..//处理错误
    			break
    		}
    		//使用一个goroute去处理
    		go handler.Handle(clientConn)
    	}
    
    	logf(lg.INFO, "TCP: closing %s", listener.Addr())
    }
    

      这是一种比较典型的处理方式,在一个goroute中负责监听新的连接信息。然后每个新的连接信息分发个一个goroute处理。  由于创建和销毁goroute的成本很低廉,所以我们用这种很简单的方式也可以写出一个支持高并发的网络应用服务端。在看具体的处理信息之前我们可以先用这种模式编写一个tcp服务端的demo。

      tcp服务端demo

    func main() {
    	tcpServerInit()
    }
    
    func tcpServerInit() {
    	tpcListener, err := net.Listen("tcp", "127.0.0.1:7070")
    	if err != nil {
    		log.Printf("出现错误:", err)
    		return
    	}
    
    	for {
    		con, err := tpcListener.Accept()
    		if err != nil {
    			log.Printf("接收套接字出现错误:", err)
    			break
    		}
    		go handleConn(con)
    	}
    }
    func handleConn(con net.Conn )  {
    	magicByte := make([]byte ,4)
    
    	io.ReadFull(con,magicByte)
    		magicStr := string(magicByte)
    	switch magicStr {
    	case "  V1":
    		log.Printf("处理v1协议数据 
    ")
    		err := handleMsg(con)
    		log.Println("发生错误",err)
    	case "  V2":
    		log.Printf("处理v2协议数据 
    ")
    		err := handleMsg(con)
    		log.Println("发生错误",err)
    	default:
    		//错误协议
    		con.Write([]byte("error protocol 
    "))
    		return
    	}
    }
    func handleMsg(con net.Conn) error  {
    	defer con.Close()
    	var errs error
    	msgReader := bufio.NewReader(con)
    	for  {
    		msg,err := msgReader.ReadString('
    ')
    		errs = err
    		if err != nil {
    			break
    		}
    		log.Printf("接收到消息%s 
    ",msg)
    
    	}
    	return errs
    }
    

      我们编写了一个简单的tcp客户端,并支持处理两种协议的数据。魔数为"  v1" 和"  v2"的数据。否则返回错误协议的提示信息。我们可以启动并调试看一下效果

    可以看到我们使用v3开头的消息系统会直接返回错误协议的提示信息。而发送正确的消息则不会有错误信息,并且系统后端会打印。

      回到nsq中,我们可以看到nsq也使用的就是这种方式处理。

    func (p *tcpServer) Handle(clientConn net.Conn) {
    	p.ctx.nsqd.logf(LOG_INFO, "TCP: new client(%s)", clientConn.RemoteAddr())
    
    	//魔数验证
    	buf := make([]byte, 4)
    	_, err := io.ReadFull(clientConn, buf)
    	if err != nil {
    		p.ctx.nsqd.logf(LOG_ERROR, "failed to read protocol version - %s", err)
    		return
    	}
    	protocolMagic := string(buf)
    
    	p.ctx.nsqd.logf(LOG_INFO, "CLIENT(%s): desired protocol magic '%s'",
    		clientConn.RemoteAddr(), protocolMagic)
    
    	var prot protocol.Protocol
    	switch protocolMagic {
    	case "  V2":
    		prot = &protocolV2{ctx: p.ctx}
    	default:
    		//错误魔数返回错误信息
    		protocol.SendFramedResponse(clientConn, frameTypeError, []byte("E_BAD_PROTOCOL"))
    		clientConn.Close()
    		p.ctx.nsqd.logf(LOG_ERROR, "client(%s) bad protocol magic '%s'",
    			clientConn.RemoteAddr(), protocolMagic)
    		return
    	}
    	//使用protocolV2的IOLoop 轮询处理套接字信息
    	err = prot.IOLoop(clientConn)
    	...
    }
    

      这儿会使用protocolV2来轮询处理后面发送的套接字信息。具体的处理逻辑是nsq的核心处理,后面章节应该会讲到,这儿就先不说明了。

    3.http编程

      nsqd.Main方法中不但有tcp处理器,还有http处理器。用来监听生产者有关信息。

    	n.httpListener, err = net.Listen("tcp", n.getOpts().HTTPAddress)
    	if err != nil {
    		n.logf(LOG_FATAL, "listen (%s) failed - %s", n.getOpts().HTTPAddress, err)
    		os.Exit(1)
    	}
    	httpServer := newHTTPServer(ctx, false, n.getOpts().TLSRequired == TLSRequired)
    	n.waitGroup.Wrap(func() {
    		http_api.Serve(n.httpListener, httpServer, "HTTP", n.logf)
    	})
    

      这儿nsq主要是封装了一下对特定请求方法以及地址的处理方法。可以看下newHTTPServer方法。

    func newHTTPServer(ctx *context, tlsEnabled bool, tlsRequired bool) *httpServer {
    	log := http_api.Log(ctx.nsqd.logf)
    	//路由处理
    	router := httprouter.New()
    	router.HandleMethodNotAllowed = true
    	router.PanicHandler = http_api.LogPanicHandler(ctx.nsqd.logf)
    	router.NotFound = http_api.LogNotFoundHandler(ctx.nsqd.logf)
    	router.MethodNotAllowed = http_api.LogMethodNotAllowedHandler(ctx.nsqd.logf)
    	//注意httpServer实现了http.Handler接口
    	s := &httpServer{
    		ctx:         ctx,
    		tlsEnabled:  tlsEnabled,
    		tlsRequired: tlsRequired,
    		router:      router,
    	}
    	//ping 信息
    	router.Handle("GET", "/ping", http_api.Decorate(s.pingHandler, log, http_api.PlainText))
    
    	// 消息发布
    	router.Handle("POST", "/pub", http_api.Decorate(s.doPUB, http_api.V1))
    
    	// 创建topic请求
    	router.Handle("POST", "/topic/create", http_api.Decorate(s.doCreateTopic, log, http_api.V1))
    	//创建channel请求
    	router.Handle("POST", "/channel/create", http_api.Decorate(s.doCreateChannel, log, http_api.V1))
    
    
    
    	return s
    }
    

      这儿router主要添加了一些特定处理器,我只展示了几个比较常见的,例如ping ,消息发布,常见topic,创建channel等。   注意httpServer结构体实现了实现了http.Handler接口,所以后面其能够作为http处理器使用。http_api.Decorate方法中的第一个参数则是具体的处理http请求的逻辑。

      在http_api.Serve方法中可以看到具体的http服务器的实现细节。

    func Serve(listener net.Listener, handler http.Handler, proto string, logf lg.AppLogFunc) {
    	logf(lg.INFO, "%s: listening on %s", proto, listener.Addr())
    	//创建http.Server   这儿handler则是之前创建的实现了http.Handler接口的httpServer 
    	server := &http.Server{
    		Handler:  handler,
    		ErrorLog: log.New(logWriter{logf}, "", 0),
    	}
    	//开启监听
    	err := server.Serve(listener)
    	// theres no direct way to detect this error because it is not exposed
    	if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
    		logf(lg.ERROR, "http.Serve() - %s", err)
    	}
    
    	logf(lg.INFO, "%s: closing %s", proto, listener.Addr())
    }
    

      最终有请求到达时会执行handler的ServeHTTP(ResponseWriter, *Request)方法。依旧写一个简单demo来看下如和创建一个http服务端。

      http服务端demo

    type hpHandler struct{
    	//Handle http.Handler
    }
    
    func (p *hpHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    	user :=req.URL.Query().Get("username")
    	fmt.Printf("获取到用户名:%s 
    ",user)
    	fmt.Fprintf(w,"成功获取到信息 :%s","200")
    	return
    }
    
    
    func main() {
    	testHandler := &hpHandler{}
    
    	htpListener ,err:=net.Listen("tcp","127.0.0.1:9090")
    	if err != nil {
    		 log.Fatalln(err)
    	}
    	server := &http.Server{
    		Handler:testHandler,
    
    	}
    	er :=server.Serve(htpListener)
    	if er != nil {
    		log.Fatalln(er)
    	}
    }
    

      这样我们访问请求地址

     控制台输出

       

      具体的http接收请求后的处理逻辑涉及到nsq如何接收以及处理生产者生产的信息,后面应该会专门讲到,所以这儿就不说明了。

      后面还有两个较为核心的操作,也会到后面专门说明的模块对其进行说明

    	n.waitGroup.Wrap(n.queueScanLoop)
    	n.waitGroup.Wrap(n.lookupLoop)
    

      n.waitGroup.Wrap(n.lookupLoop)用来保证lookupLoop中的信息一致性

      n.waitGroup.Wrap(n.queueScanLoop)//暂时未知。。

  • 相关阅读:
    python-列表基本方法
    成员/身份运算
    python-字符串常用方法
    python基础知识1
    jmeter_性能测试及报告分析
    Day3_linux_工作目录切换命令
    Day2_linux_查看系统状态
    Day1_liunx_常用系统工作命令
    互联网是有记忆的
    第3章 Python基础
  • 原文地址:https://www.cnblogs.com/hetutu-5238/p/13036399.html
Copyright © 2020-2023  润新知