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协议数据 \n")
		err := handleMsg(con)
		log.Println("发生错误",err)
	case "  V2":
		log.Printf("处理v2协议数据 \n")
		err := handleMsg(con)
		log.Println("发生错误",err)
	default:
		//错误协议
		con.Write([]byte("error protocol \r\n"))
		return
	}
}
func handleMsg(con net.Conn) error  {
	defer con.Close()
	var errs error
	msgReader := bufio.NewReader(con)
	for  {
		msg,err := msgReader.ReadString('\n')
		errs = err
		if err != nil {
			break
		}
		log.Printf("接收到消息%s \n",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 \n",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)//暂时未知。。

 

posted @ 2020-06-03 13:29  雨落寒沙  阅读(452)  评论(0编辑  收藏  举报