NSQ源码分析之nsqd

概述

最近想通过观察分析NSQ的源码,来更进一步学习golang,编程嘛,就是多看、多写、多思考,没有捷径。
系列文章采用的nsq版本为1.2.1

什么是NSQ

引用官方文档的话:NSQ is a realtime distributed messaging platform designed to operate at scale, handling billions of messages per day.

NSQ特性和使用

网上很多介绍NSQ的文章,这里就不再赘述。
文章链接

NSQ的架构

文本源码分析的思路就是根据NSQ的不同功能模块,逐一击破。这样我就需要首先了解其架构。
在这里插入图片描述
从整体架构上来看,NSQ可以分为五个部分:

  • producer:服务端生产者。
  • comsumer:服务端消费者。
  • lookupd:lookupd是守护进程负责管理拓扑信息。客户端通过查询 lookupd 来发现指定 topic的生产者,并且nsqd 节点广播 topic 和 channel 信息。
  • nsqd:nsqd 是一个守护进程,负责接收,排队,投递消息给客户端。
  • admin: 是一套web UI,用来汇集集群的实时统计,并执行不同的管理任务。

在这篇文章中,我们首先对nsqd的源码进行分析。

Topic和Channel

每个nsqd实例旨在一次处理多个数据流。这些数据流称为“topics”,一个topic具有1个或多个“channels”。每个channel都会收到topic所有消息的副本,实际上下游的服务是通过对应的channel来消费topic消息。

topic和channel不是预先配置的。topic在首次使用时创建,方法是将其发布到指定topic,或者订阅指定topic上的channel。channel是通过订阅指定的channel在第一次使用时创建的。

topic和channel都相互独立地缓冲数据,防止缓慢的消费者导致其他chennel的积压(同样适用于topic级别)。

channel可以并且通常会连接多个客户端。假设所有连接的客户端都处于准备接收消息的状态,则每条消息将被传递到随机客户端。
在这里插入图片描述
参考博客

源码分析

主要的一些结构体

nsqd

首先我们从最开始的nsqd来一步步看看对应的数据,可以窥探出一些模块的主要功能和依赖关系。

type NSQD struct {
	// 64bit atomic vars need to be first for proper alignment on 32bit platforms
	clientIDSequence int64

	// 读写锁
	sync.RWMutex
	// 线程参数
	ctx context.Context
	// ctxCancel cancels a context that main() is waiting on
	ctxCancel context.CancelFunc
	// 启动时的配置参数
	opts atomic.Value
	// 锁,里面就是存一个字符串,存储着持久化diskqueue的系统文件路径
	dl        *dirlock.DirLock
	isLoading int32
	isExiting int32
	errValue  atomic.Value
	startTime time.Time
	// 存储这个nsqd的所有Topic
	topicMap map[string]*Topic

	lookupPeers atomic.Value
	// TCP服务器控制块,用于存储所有TCP链接的conn
	tcpServer     *tcpServer
	// TCP、http、https监听器
	tcpListener   net.Listener
	httpListener  net.Listener
	httpsListener net.Listener
	// 安全传输层协议配置
	tlsConfig     *tls.Config
	// 缓存池大小
	poolSize int
	// 不同功能的通道
	notifyChan           chan interface{}
	optsNotificationChan chan struct{}
	exitChan             chan int
	// 多线程时的同步等待组
	waitGroup            util.WaitGroupWrapper

	ci *clusterinfo.ClusterInfo
}

topic

从上面的接口题可以看到,一个nsqd存储了所有的Topic,我们再看看Topic的数据结构。

type Topic struct {
	// 64bit atomic vars need to be first for proper alignment on 32bit platforms
	messageCount uint64
	messageBytes uint64
	// 读写锁
	sync.RWMutex
	// topic的名称
	name              string
	// 这个topic对应的所有channel
	channelMap        map[string]*Channel
	// 负责向磁盘文件中写入消息、从磁盘文件中读取消息,是NSQ实现数据持久化的最重要结构
	backend           BackendQueue
	// 内存消息通道
	memoryMsgChan     chan *Message
	startChan         chan int
	exitChan          chan int
	channelUpdateChan chan int
	// 多线程时的同步等待组
	waitGroup         util.WaitGroupWrapper
	// 退出标记
	exitFlag          int32
	// 生产消息ID的工厂对象
	idFactory         *guidFactory
	// 是否为临时topic
	ephemeral      bool
	// 删除topic的方法指针
	deleteCallback func(*Topic)
	// 用于调用上面的删除回调函数,保证并发请客下,只调用一次
	deleter        sync.Once
	// 暂停标记
	paused    int32
	pauseChan chan int
	// 对应的nsqd指针
	nsqd *NSQD
}

channel

Topic中有一个哈希表,存储其对应的所有的channel,我们再看channel的数据结构。

type Channel struct {
	// 64bit atomic vars need to be first for proper alignment on 32bit platforms
	requeueCount uint64
	messageCount uint64
	timeoutCount uint64
	// 读写锁
	sync.RWMutex
	// chanel对应的topic名字
	topicName string
	// channel名字
	name      string
	// 对应的nsqd指针
	nsqd      *NSQD
	// 负责向磁盘文件中写入消息、从磁盘文件中读取消息,是NSQ实现数据持久化的最重要结构
	backend BackendQueue
	// 内存消息通道
	memoryMsgChan chan *Message
	// 退出标志
	exitFlag      int32
	// 退出读写锁
	exitMutex     sync.RWMutex

	// state tracking
	// 存储监听这个channel的所有的消费者
	clients        map[int64]Consumer
	// 暂停标志
	paused         int32
	// 是否为临时channel
	ephemeral      bool
	// 删除channel函数指针
	deleteCallback func(*Channel)
	// 用于调用上面的删除回调函数,保证并发请客下,只调用一次
	deleter        sync.Once

	// Stats tracking
	e2eProcessingLatencyStream *quantile.Quantile

	// TODO: these can be DRYd up
	// 对应的消息队列
	deferredMessages map[MessageID]*pqueue.Item
	deferredPQ       pqueue.PriorityQueue
	deferredMutex    sync.Mutex
	inFlightMessages map[MessageID]*Message
	inFlightPQ       inFlightPqueue
	inFlightMutex    sync.Mutex
}

主要的一些流程

nsqd创建

观察nsqd的创建流程,也可以帮助我们理解nsqd结构体中一些成员的含义。最后,可以看到nsqd守护进程开启的所有功能协程,比较清晰。

// 传入的参数是一个巨大的配置类,包括nsqd的各种参数信息
func New(opts *Options) (*NSQD, error) {
	var err error

	// 获取持久化的文件夹路径信息
	dataPath := opts.DataPath
	if opts.DataPath == "" {
		cwd, _ := os.Getwd()
		dataPath = cwd
	}
	// 配置日志信息
	if opts.Logger == nil {
		opts.Logger = log.New(os.Stderr, opts.LogPrefix, log.Ldate|log.Ltime|log.Lmicroseconds)
	}
	// 实例化一个nsqd,分配内存
	n := &NSQD{
		startTime:            time.Now(),
		topicMap:             make(map[string]*Topic),
		exitChan:             make(chan int),
		notifyChan:           make(chan interface{}),
		optsNotificationChan: make(chan struct{}, 1),
		// 这里可以看到,将持久化的系统路径传入了这个锁中,应该是用于持久化时的并发安全
		dl:                   dirlock.New(dataPath),
	}
	n.ctx, n.ctxCancel = context.WithCancel(context.Background())
	// 获取http的客户端
	httpcli := http_api.NewClient(nil, opts.HTTPClientConnectTimeout, opts.HTTPClientRequestTimeout)
	n.ci = clusterinfo.New(n.logf, httpcli)
	// 保存所有nsqlookup的连接和读写
	n.lookupPeers.Store([]*lookupPeer{})
	// 存入opts
	n.swapOpts(opts)
	n.errValue.Store(errStore{})
	// 给路径加锁,不过当前函数内部还是空逻辑
	err = n.dl.Lock()
	if err != nil {
		return nil, fmt.Errorf("failed to lock data-path: %v", err)
	}
	// 都是一些参数的合法性校验
	if opts.MaxDeflateLevel < 1 || opts.MaxDeflateLevel > 9 {
		return nil, errors.New("--max-deflate-level must be [1,9]")
	}

	if opts.ID < 0 || opts.ID >= 1024 {
		return nil, errors.New("--node-id must be [0,1024)")
	}

	if opts.TLSClientAuthPolicy != "" && opts.TLSRequired == TLSNotRequired {
		opts.TLSRequired = TLSRequired
	}
	// 传输层密钥配置
	tlsConfig, err := buildTLSConfig(opts)
	if err != nil {
		return nil, fmt.Errorf("failed to build TLS config - %s", err)
	}
	if tlsConfig == nil && opts.TLSRequired != TLSNotRequired {
		return nil, errors.New("cannot require TLS client connections without TLS key and cert")
	}
	n.tlsConfig = tlsConfig
	// 端到端网络延迟百分数
	for _, v := range opts.E2EProcessingLatencyPercentiles {
		if v <= 0 || v > 1 {
			return nil, fmt.Errorf("invalid E2E processing latency percentile: %v", v)
		}
	}
	// 日志打印
	n.logf(LOG_INFO, version.String("nsqd"))
	n.logf(LOG_INFO, "ID: %d", opts.ID)
	// 注册TCP服务端
	n.tcpServer = &tcpServer{nsqd: n}
	// 注册TCP监听器
	n.tcpListener, err = net.Listen("tcp", opts.TCPAddress)
	if err != nil {
		return nil, fmt.Errorf("listen (%s) failed - %s", opts.TCPAddress, err)
	}
	// 注册HTTP监听器
	n.httpListener, err = net.Listen("tcp", opts.HTTPAddress)
	if err != nil {
		return nil, fmt.Errorf("listen (%s) failed - %s", opts.HTTPAddress, err)
	}
	// 注册HTTPS监听器,上面配置的传输层加密协议就用到了这里
	if n.tlsConfig != nil && opts.HTTPSAddress != "" {
		n.httpsListener, err = tls.Listen("tcp", opts.HTTPSAddress, n.tlsConfig)
		if err != nil {
			return nil, fmt.Errorf("listen (%s) failed - %s", opts.HTTPSAddress, err)
		}
	}
	// 配置默认HTTP广播端口号
	if opts.BroadcastHTTPPort == 0 {
		opts.BroadcastHTTPPort = n.RealHTTPAddr().Port
	}
	// 配置默认TCP广播端口号
	if opts.BroadcastTCPPort == 0 {
		opts.BroadcastTCPPort = n.RealTCPAddr().Port
	}
	// 配置默认统计前缀
	if opts.StatsdPrefix != "" {
		var port string = fmt.Sprint(opts.BroadcastHTTPPort)
		statsdHostKey := statsd.HostKey(net.JoinHostPort(opts.BroadcastAddress, port))
		prefixWithHost := strings.Replace(opts.StatsdPrefix, "%s", statsdHostKey, -1)
		if prefixWithHost[len(prefixWithHost)-1] != '.' {
			prefixWithHost += "."
		}
		opts.StatsdPrefix = prefixWithHost
	}

	return n, nil
}

nsqd启动

nsqd也是一个单独的守护进程,看看它在运行的时候到底做了哪些事情。

func (n *NSQD) Main() error {
    // 创建退出通道,当进程需要关闭的时候,通知各个携程
	exitCh := make(chan error)
	// 保证并发下只执行一次的锁
	var once sync.Once
	// 定义协程退出函数,输出退出错误原因
	exitFunc := func(err error) {
		once.Do(func() {
			if err != nil {
				n.logf(LOG_FATAL, "%s", err)
			}
			exitCh <- err
		})
	}
	// 开启TCP监听
	n.waitGroup.Wrap(func() {
		exitFunc(protocol.TCPServer(n.tcpListener, n.tcpServer, n.logf))
	})
	// 开启HTTP服务监听
	httpServer := newHTTPServer(n, false, n.getOpts().TLSRequired == TLSRequired)
	n.waitGroup.Wrap(func() {
		exitFunc(http_api.Serve(n.httpListener, httpServer, "HTTP", n.logf))
	})
	// 开启HTTPS监听
	if n.tlsConfig != nil && n.getOpts().HTTPSAddress != "" {
		httpsServer := newHTTPServer(n, true, true)
		n.waitGroup.Wrap(func() {
			exitFunc(http_api.Serve(n.httpsListener, httpsServer, "HTTPS", n.logf))
		})
	}
  // 开启队列扫描,维护在传输和存储的优先队列
	n.waitGroup.Wrap(n.queueScanLoop)
	// 开启lookup协程
	n.waitGroup.Wrap(n.lookupLoop)
	// 开启统计协程
	if n.getOpts().StatsdAddress != "" {
		n.waitGroup.Wrap(n.statsdLoop)
	}
	// 接收错误信息,返回
	err := <-exitCh
	return err
}

可以看到,nsqd进程中主要包括了:

  • TCP监听
  • HTTP监听
  • HTTPS监听
  • queueScanLoop
  • lookupLoop
  • statsdLoop

其实主要可以包括两大部分,连接建立和逻辑循环,然后我们下一步分析每个开启协程中做了什么。

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 {
		// 死循环,不停监听TCP端口
		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
}

当建立连接之后,就会将连接交给handle处理。

func (p *tcpServer) Handle(conn net.Conn) {
	p.nsqd.logf(LOG_INFO, "TCP: new client(%s)", conn.RemoteAddr())

	// 获取客户端发送的协议名称来初始化
	buf := make([]byte, 4)
	_, err := io.ReadFull(conn, buf)
	if err != nil {
		p.nsqd.logf(LOG_ERROR, "failed to read protocol version - %s", err)
		conn.Close()
		return
	}
	protocolMagic := string(buf)

	p.nsqd.logf(LOG_INFO, "CLIENT(%s): desired protocol magic '%s'",
		conn.RemoteAddr(), protocolMagic)
  // 创建对应的协议管理器
	var prot protocol.Protocol
	switch protocolMagic {
	case "  V2":
	    // 创建一个protocolV2处理与消费者的会话
		prot = &protocolV2{nsqd: p.nsqd}
	default:
		protocol.SendFramedResponse(conn, frameTypeError, []byte("E_BAD_PROTOCOL"))
		conn.Close()
		p.nsqd.logf(LOG_ERROR, "client(%s) bad protocol magic '%s'",
			conn.RemoteAddr(), protocolMagic)
		return
	}
	// 将连接存储到TCPmap当中
	client := prot.NewClient(conn)
	p.conns.Store(conn.RemoteAddr(), client)
  // 开始运行这个客户端的连接protocol,处理会话
	err = prot.IOLoop(client)
	if err != nil {
		p.nsqd.logf(LOG_ERROR, "client(%s) - %s", conn.RemoteAddr(), err)
	}
	// 退出连接
	p.conns.Delete(conn.RemoteAddr())
	client.Close()
}

HTTP监听

nsqd中很多指令是基于HTTP协议进行传传输的,可以从下面的HTTP服务建立中看到注册了不同的报文处理方法。
观察命令格式,可以发现类似于文件夹的树形结构,比如对于topic的操作方法有创建删除暂停之类的。

func newHTTPServer(nsqd *NSQD, tlsEnabled bool, tlsRequired bool) *httpServer {
	log := http_api.Log(nsqd.logf)

	router := httprouter.New()
	router.HandleMethodNotAllowed = true
	router.PanicHandler = http_api.LogPanicHandler(nsqd.logf)
	router.NotFound = http_api.LogNotFoundHandler(nsqd.logf)
	router.MethodNotAllowed = http_api.LogMethodNotAllowedHandler(nsqd.logf)
	s := &httpServer{
		nsqd:        nsqd,
		tlsEnabled:  tlsEnabled,
		tlsRequired: tlsRequired,
		router:      router,
	}
	// 根据http中不同的字段,添加不同的处理方法,这里还用到装饰器模式
	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
}

HTTPs应该也是类似的,这里就不做赘述了。

queueScanLoop

直接翻译过来就是队列扫描循环。
可以看到这里定义了两个不同的定时器,工作定时器用来唤醒处理操作,刷新定时器用来触发将从nsqd的chanel缓存拷贝的当前线程本地缓存中。这里采用了Redis的probabilistic expiration algorithm。

func (n *NSQD) queueScanLoop() {
	// 创建一个工作通道,通道里传输的就是nsq的channel
	workCh := make(chan *Channel, n.getOpts().QueueScanSelectionCount)
	// 创建一个响应通道
	responseCh := make(chan bool, n.getOpts().QueueScanSelectionCount)
	// 创建一个关闭通道
	closeCh := make(chan int)
	// 创建一个工作定时器,默认间隔是100毫秒,
	workTicker := time.NewTicker(n.getOpts().QueueScanInterval)
	// 创建一个刷新定时器,默认间隔是5秒
	refreshTicker := time.NewTicker(n.getOpts().QueueScanRefreshInterval)

	channels := n.channels()
	n.resizePool(len(channels), workCh, responseCh, closeCh)

	for {
		select {
		case <-workTicker.C:
			// 工作定时器触发,如果通道里数据是空的,那么跳过下面的处理操作
			if len(channels) == 0 {
				continue
			}
		case <-refreshTicker.C:
		    // 刷新定时器触发,将nsqd中的channel都拿出来进行处理
			channels = n.channels()
			// 并且清空nsqd的channel缓存
			n.resizePool(len(channels), workCh, responseCh, closeCh)
			continue
		case <-n.exitChan:
			// 接收到了退出命令,那么跳出队列扫描循环
			goto exit
		}
		// 从缓存中获得一个随机的通道
		num := n.getOpts().QueueScanSelectionCount
		if num > len(channels) {
			num = len(channels)
		}

	loop:
		// 将每个通道里的channel放到这里的工作通道中
		for _, i := range util.UniqRands(num, len(channels)) {
			workCh <- channels[i]
		}
		// 脏通道计数
		numDirty := 0
		// 如果队列有工作要做,那么就会认为通道是脏的
		for i := 0; i < num; i++ {
			if <-responseCh {
				numDirty++
			}
		}
		
		if float64(numDirty)/float64(num) > n.getOpts().QueueScanDirtyPercent {
		    // 如果所选通道中默认QueueScanDirtyPercent分之一是脏的,那么就会当前线程就会继续循环
			goto loop
		}
	}

exit:
	// 退出队列扫描循环
	n.logf(LOG_INFO, "QUEUESCAN: closing")
	close(closeCh)
	workTicker.Stop()
	refreshTicker.Stop()
}

lookupLoop

该协程的主要作用为lookupd的服务发现和信息通知。

func (n *NSQD) lookupLoop() {
    // 存放所有lookupd的信息
	var lookupPeers []*lookupPeer
	// 存放所有lookupd的地址
	var lookupAddrs []string
	connect := true
	// 当前节点地址
	hostname, err := os.Hostname()
	if err != nil {
		n.logf(LOG_FATAL, "failed to get hostname - %s", err)
		os.Exit(1)
	}

	// for announcements, lookupd determines the host automatically
	// 自动发现所有的lookupd的地址信息,并将相关信息通知给所有lookupd
	ticker := time.Tick(15 * time.Second)
	for {
		if connect {
		    // 从NSQ中获取所有lookupd的地址信息
			for _, host := range n.getOpts().NSQLookupdTCPAddresses {
				if in(host, lookupAddrs) {
					continue
				}
				// 加入到本地数组中
				n.logf(LOG_INFO, "LOOKUP(%s): adding peer", host)
				lookupPeer := newLookupPeer(host, n.getOpts().MaxBodySize, n.logf,
					connectCallback(n, hostname))
				lookupPeer.Command(nil) // start the connection
				lookupPeers = append(lookupPeers, lookupPeer)
				lookupAddrs = append(lookupAddrs, host)
			}
			n.lookupPeers.Store(lookupPeers)
			connect = false
		}

		select {
		case <-ticker:
			// send a heartbeat and read a response (read detects closed conns)
			// 满足时钟条件,发送心跳包给所有连接的lookupd
			for _, lookupPeer := range lookupPeers {
				n.logf(LOG_DEBUG, "LOOKUPD(%s): sending heartbeat", lookupPeer)
				cmd := nsq.Ping()
				_, err := lookupPeer.Command(cmd)
				if err != nil {
					n.logf(LOG_ERROR, "LOOKUPD(%s): %s - %s", lookupPeer, cmd, err)
				}
			}
		case val := <-n.notifyChan:
		    // 从通道接受到NSQ的通知信息
			var cmd *nsq.Command
			var branch string

			switch val.(type) {
			case *Channel:
				// notify all nsqlookupds that a new channel exists, or that it's removed
				// 如果是一个Channel类型的信息,那么就关系到channel的注册或者卸载
				branch = "channel"
				channel := val.(*Channel)
				if channel.Exiting() == true {
					cmd = nsq.UnRegister(channel.topicName, channel.name)
				} else {
					cmd = nsq.Register(channel.topicName, channel.name)
				}
			case *Topic:
				// notify all nsqlookupds that a new topic exists, or that it's removed
				// 同样的,如果是Topic信息
				branch = "topic"
				topic := val.(*Topic)
				if topic.Exiting() == true {
					cmd = nsq.UnRegister(topic.name, "")
				} else {
					cmd = nsq.Register(topic.name, "")
				}
			}
            // 向所有lookupd发送命令
			for _, lookupPeer := range lookupPeers {
				n.logf(LOG_INFO, "LOOKUPD(%s): %s %s", lookupPeer, branch, cmd)
				_, err := lookupPeer.Command(cmd)
				if err != nil {
					n.logf(LOG_ERROR, "LOOKUPD(%s): %s - %s", lookupPeer, cmd, err)
				}
			}
		case <-n.optsNotificationChan:
		    // 配置通知信息被触发
			var tmpPeers []*lookupPeer
			var tmpAddrs []string
			// 更新所有的lookupd的状态,移除那些下线了的
			for _, lp := range lookupPeers {
				if in(lp.addr, n.getOpts().NSQLookupdTCPAddresses) {
					tmpPeers = append(tmpPeers, lp)
					tmpAddrs = append(tmpAddrs, lp.addr)
					continue
				}
				n.logf(LOG_INFO, "LOOKUP(%s): removing peer", lp)
				lp.Close()
			}
			lookupPeers = tmpPeers
			lookupAddrs = tmpAddrs
			// 可能有新的lookupd上线,在下一个循环内进行添加
			connect = true
		case <-n.exitChan:
			goto exit
		}
	}

exit:
	n.logf(LOG_INFO, "LOOKUP: closing")
}

结尾

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值