nsqd解析:nsqd链接的建立

在 nsqd 中,每个 nsqd 节点初始化时会默认监听两个端口:

[nsqd] INFO: TCP: listening on [::]:4150     TCP 连接
[nsqd] INFO: HTTP: listening on [::]:4151    HTTP 连接

  那么这两个端口有什么作用呢?nsqd 是怎么和 lookupd 以及客户端进行通信的呢?如何处理新到达的链接呢?在 nsqd 的 Main 函数中,启动了三个服务器分别用来监听 TCP,HTTP,HTTPS 的连接,下面依次来看一下

nsqd 中的 TCP 连接

使用如下代码创建一个 TCP 服务器来监听 4150 端口上的新连接:

protocol.TCPServer(n.tcpListener, n.tcpServer, n.logf)

  创建一个 TCPServer 需要传入三个参数:TCP 监听地址,tcpServer 结构体,日志打印方法.1,3 不需要解释,来看看什么是 tcpServer,以及为什么要传入一个 tcpServer:

// 管理所有 TCP 连接
// 通过实现 TCPHandler 接口来对新连接进行处理
type tcpServer struct {
	ctx   *context
	conns sync.Map   // 这里使用了 syncmap,用 Addr 作为 key,Conn 作为 value
}

// nsqd 中对新 TCP 连接的处理函数
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)
		clientConn.Close()
		return
	}
	protocolMagic := string(buf)
	// 打印协议版本,v1.2.0的协议版本打印出来的是 "  V2"
	p.ctx.nsqd.logf(LOG_INFO, "CLIENT(%s): desired protocol magic '%s'",
		clientConn.RemoteAddr(), protocolMagic)
	// 创建对应的协议管理器
	var prot protocol.Protocol
	switch protocolMagic {
	case "  V2":
		// 使用 V2 版本的协议操作来进行处理,创建对应的版本管理器即可
		prot = &protocolV2{ctx: p.ctx}
	default:
		// 暂时仅支持 V2 一个版本,其余的版本直接断开连接并返回
		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
	}
	// 将连接加入到 TCP 服务器的 sync.map 中
	p.conns.Store(clientConn.RemoteAddr(), clientConn)
	// 协议定义的操作,开始运行这个客户端的连接,进行数据的交换
	err = prot.IOLoop(clientConn)
	if err != nil {
		p.ctx.nsqd.logf(LOG_ERROR, "client(%s) - %s", clientConn.RemoteAddr(), err)
	}
	// 连接断开
	p.conns.Delete(clientConn.RemoteAddr())
}

// nsqd 退出时调用
func (p *tcpServer) CloseAll() {
	// 遍历 sync.map 进行 close 操作即可
	p.conns.Range(func(k, v interface{}) bool {
		v.(net.Conn).Close()
		return true
	})
}

  protocolV2 后面再进行讲解,从上面代码可以看出 tcpServer 只有两个方法,用来处理新的连接的 Handle,用来关闭所有连接的 CloseAll,然后在调用 TCPServer 时把 tcpServer 转换成 TCPHandler 接口进行传入即可
  在 TCPServer 内部运行了一个 for 循环不断地接收新的连接并将其添加到 nsqd.tcpServer 中即可,代码很简单如下:

func TCPServer(listener net.Listener, handler TCPHandler, logf lg.AppLogFunc) error {
	// 开始监听 TCP 端口默认是 4150
	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
			}
			// 这里错误直接使用 fmt 进行打印的,因为没有将错误暴露出去?但是将这个错误传递出去了啊
			// 可能是因为,仅仅传递出去了错误,但是外面并没有进行处理,因为这个错误直接导致了系统的关闭
			// 传出去的错误是 LOG_FATAL 级别,并不会进行打印
			if !strings.Contains(err.Error(), "use of closed network connection") {
				return fmt.Errorf("listener.Accept() error - %s", err)
			}
			break
		}
		// 这里进行 Add(1) 会一直保持已有连接不会因为错误连接导致 TCPServer 退出而断开,后面有 wg.Wait()
		wg.Add(1)
		go func() {
			// 执行连接处理函数,其实就是把连接放进 map 中进行管理而已
			handler.Handle(clientConn)
			wg.Done()
		}()
	}

	// 等待所有协程完成
	wg.Wait()

	logf(lg.INFO, "TCP: closing %s", listener.Addr())

	return nil
}

  到这里我们就建立了一个 TCP 连接.到这里一个客户端完成的操作为:建立连接,把连接添加到 tcpServer.conns 中,然后创建一个 protocolV2 并执行 IOLoop 来使该客户端和 nsqd 进行 IO 通信.

nsqd 中的 HTTP 连接

同样的,在 NSQD.Main() 中启动了 HTTP 的监听 goroutine.

	// 开启对 HTTP 进行监听
	httpServer := newHTTPServer(ctx, false, n.getOpts().TLSRequired == TLSRequired)
	n.waitGroup.Wrap(func() {
		exitFunc(http_api.Serve(n.httpListener, httpServer, "HTTP", n.logf))
	})

  先创建一个 HTTPServer 并注册好一系列路由操作,然后调用 http_api.Serve 开始启动 HTTPServer 并在 4151 端口进行 HTTP 通信.

// 创建一个新的 HTTP server 并注册路由规则
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)
	s := &httpServer{
		ctx:         ctx,
		tlsEnabled:  tlsEnabled,
		tlsRequired: tlsRequired,
		router:      router,
	}
	// 这里是是所有的路由规则
	router.Handle("GET", "/ping", http_api.Decorate(s.pingHandler, log, http_api.PlainText))
	router.Handle("GET", "/info", http_api.Decorate(s.doInfo, log, http_api.V1))

	// v1 negotiate
	router.Handle("POST", "/pub", http_api.Decorate(s.doPUB, http_api.V1))
	router.Handle("POST", "/mpub", http_api.Decorate(s.doMPUB, http_api.V1))
	router.Handle("GET", "/stats", http_api.Decorate(s.doStats, log, http_api.V1))

	// only v1
	router.Handle("POST", "/topic/create", http_api.Decorate(s.doCreateTopic, log, http_api.V1))
	router.Handle("POST", "/topic/delete", http_api.Decorate(s.doDeleteTopic, log, http_api.V1))
	router.Handle("POST", "/topic/empty", http_api.Decorate(s.doEmptyTopic, log, http_api.V1))
	router.Handle("POST", "/topic/pause", http_api.Decorate(s.doPauseTopic, log, http_api.V1))
	router.Handle("POST", "/topic/unpause", http_api.Decorate(s.doPauseTopic, log, http_api.V1))
	router.Handle("POST", "/channel/create", http_api.Decorate(s.doCreateChannel, log, http_api.V1))
	router.Handle("POST", "/channel/delete", http_api.Decorate(s.doDeleteChannel, log, http_api.V1))
	router.Handle("POST", "/channel/empty", http_api.Decorate(s.doEmptyChannel, log, http_api.V1))
	router.Handle("POST", "/channel/pause", http_api.Decorate(s.doPauseChannel, log, http_api.V1))
	router.Handle("POST", "/channel/unpause", http_api.Decorate(s.doPauseChannel, log, http_api.V1))
	router.Handle("GET", "/config/:opt", http_api.Decorate(s.doConfig, log, http_api.V1))
	router.Handle("PUT", "/config/:opt", http_api.Decorate(s.doConfig, log, http_api.V1))

	// debug
	router.HandlerFunc("GET", "/debug/pprof/", pprof.Index)
	router.HandlerFunc("GET", "/debug/pprof/cmdline", pprof.Cmdline)
	router.HandlerFunc("GET", "/debug/pprof/symbol", pprof.Symbol)
	router.HandlerFunc("POST", "/debug/pprof/symbol", pprof.Symbol)
	router.HandlerFunc("GET", "/debug/pprof/profile", pprof.Profile)
	router.Handler("GET", "/debug/pprof/heap", pprof.Handler("heap"))
	router.Handler("GET", "/debug/pprof/goroutine", pprof.Handler("goroutine"))
	router.Handler("GET", "/debug/pprof/block", pprof.Handler("block"))
	router.Handle("PUT", "/debug/setblockrate", http_api.Decorate(setBlockRateHandler, log, http_api.PlainText))
	router.Handler("GET", "/debug/pprof/threadcreate", pprof.Handler("threadcreate"))

	return s
}

  只需要了解在 HTTPServer 中注册的一系列路由规则的使用即可.注册的命令除了状态查看的规则之外,就是对 topic 和 channel 进行管理的,这些都是由 nsq 的管理界面进行操作的.另外 HTTPS 的启动和 HTTP 的并无太多区别仅仅是开启了 TLS 而已.
所有操作如下:

  • GET: ping // 类似于 keep_alive 用来获取 nsqd 的 healthy 状态
  • GET: info // 获取 nsqd 的基础信息
  • POST: pub // 向 topic 推送一条消息
  • POST: mpub // 向 topic 推送多条消息
  • GET: stats // 获取 nsqd 的统计信息(版本,开始时间,topics 等等)
  • POST: /topic/create // 创建一个 topic
  • POST: /topic/delete // 删除一个 topic
  • POST: /topic/empty // 清空一个 topic
  • POST: /topic/pause // 暂停一个 topic
  • POST: /topic/unpause // 恢复一个 topic
  • POST: /channel/create // 创建一个 channel
  • POST: /channel/delete // 删除一个 channel
  • POST: /channel/empty // 清空一个 channel
  • POST: /channel/pause // 暂停一个 channel
  • POST: /channel/unpause // 恢复一个 channel
  • GET: /config/:opt // 获取 nsqd 的 options
  • PUT: /config/:opt // 更新 nsqd 的 options
  • …一系列统计操作

小结

  现在可以知道每个客户端通过 TCP 和服务器进行连接,建立后的连接统一使用 nsqd 中的 tcpServer 字段进行管理.创建 HTTP 和 HTTPS 来从管理界面对 nsqd 进行操作

最后附上 github 的 nsqd 的源码解析地址:https://github.com/nercoeus/nsq_source_note

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值