nsq源码阅读 nsqd源码四 nsqd/lookup.go 与nsqlookupd服务的交互

NSQD对象的Main()方法中有一段代码:

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

启动一个goroutine,处理与nsqlookupd进程的交互。封装在nsqd/lookup.go中。

这个goroutine的主要功能有:

1、连接nsqlookupd服务,执行IDENTIFY操作;

2、将nsqd的Metadata中的topic、channel注册到nsqlookupd服务;

3、15秒心跳一次,对nsqlookupd执行一次PING操作;

4、新增或删除topic、channel时,REGISTER或UNREGISTER到nsqlookupd服务。

首先连接nsqlookupd服务,连接到配置文件中nsqlookupd_tcp_addresses和命令行参数中“-lookupd-tcp-address”给定的nsqlookupd服务。

		if connect {
			// 配置文件中或启动命令中指定的nsqlookupd服务的地址
			for _, host := range n.getOpts().NSQLookupdTCPAddresses {
				// 如果已经连接过了,跳过
				if in(host, lookupAddrs) {
					continue
				}
				n.logf("LOOKUP(%s): adding peer", host)
				lookupPeer := newLookupPeer(host, n.getOpts().MaxBodySize, n.getOpts().Logger,
					connectCallback(n, hostname, syncTopicChan))
				// 开始链接lookupd服务
				lookupPeer.Command(nil) // start the connection
				lookupPeers = append(lookupPeers, lookupPeer)
				lookupAddrs = append(lookupAddrs, host)
			}
			// 赋值给n.lookupPeers
			n.lookupPeers.Store(lookupPeers)
			connect = false
		}

初始化时,connect默认为true,会执行这段代码,连接到nsqlookupd服务,执行IDENTIFY操作。newLookupPeer()时,使用了修饰器,关于golang的修饰器,可以看看阿里陈皓大神的博客。然后将connect设为false,避免再次执行。

连接上nsqlookupd服务后,会发送"  V1"(前面两个空格),说明协议的版本。之后发送"IDENTIFY\n",执行IDENTIFY操作,成功后,nsqd每15秒会执行一次心跳程序,与nsqlookupd进行一次PING操作。

nsqd初始化时,在运行NSQD对象的Main()方法之前,会先运行NSQD的LoadMetadata()方法,加载Metadata。在执行完IDENTIFY操作之后,nsqd会立即将Metadata中的topic、channel注册到nsqlookupd服务中。

之后新增或删除topic和channel时,会向nsqlookupd发送REGISTER、UNREGISTER命令,执行相应的操作。

添加或减少nsqlookupd服务时,会对已连接的nsqlookupd进行过滤(主要针对减少nsqlookupd服务),然后将connect设置为true,再次执行上面的连接操作。

下面贴上完整的代码:

package nsqd

import (
	"bytes"
	"encoding/json"
	"net"
	"os"
	"strconv"
	"time"

	"github.com/nsqio/go-nsq"
	"github.com/nsqio/nsq/internal/version"
)

// golang修饰器,https://coolshell.cn/articles/17929.html
// 链接之后,执行IDENTIFY操作
func connectCallback(n *NSQD, hostname string, syncTopicChan chan *lookupPeer) func(*lookupPeer) {
	return func(lp *lookupPeer) {
		ci := make(map[string]interface{})
		ci["version"] = version.Binary
		ci["tcp_port"] = n.RealTCPAddr().Port
		ci["http_port"] = n.RealHTTPAddr().Port
		ci["hostname"] = hostname
		ci["broadcast_address"] = n.getOpts().BroadcastAddress

		cmd, err := nsq.Identify(ci)
		if err != nil {
			lp.Close()
			return
		}
		// 执行IDENTIFY操作
		resp, err := lp.Command(cmd)
		if err != nil {
			n.logf("LOOKUPD(%s): ERROR %s - %s", lp, cmd, err)
		} else if bytes.Equal(resp, []byte("E_INVALID")) {
			n.logf("LOOKUPD(%s): lookupd returned %s", lp, resp)
		} else {
			err = json.Unmarshal(resp, &lp.Info)
			if err != nil {
				n.logf("LOOKUPD(%s): ERROR parsing response - %s", lp, resp)
			} else {
				n.logf("LOOKUPD(%s): peer info %+v", lp, lp.Info)
				if lp.Info.BroadcastAddress == "" {
					n.logf("LOOKUPD(%s): ERROR - no broadcast address", lp)
				}
			}
		}

		go func() {
			syncTopicChan <- lp
		}()
	}
}

// 链接lookupd服务
func (n *NSQD) lookupLoop() {
	// 已连接的lookupd服务实例
	var lookupPeers []*lookupPeer
	// 已链接的lookupd服务地址
	var lookupAddrs []string
	syncTopicChan := make(chan *lookupPeer)
	connect := true

	hostname, err := os.Hostname()
	if err != nil {
		n.logf("FATAL: failed to get hostname - %s", err)
		os.Exit(1)
	}

	// for announcements, lookupd determines the host automatically
	ticker := time.Tick(15 * time.Second)
	for {
		if connect {
			// 配置文件中或启动命令中指定的nsqlookupd服务的地址
			for _, host := range n.getOpts().NSQLookupdTCPAddresses {
				// 如果已经连接过了,跳过
				if in(host, lookupAddrs) {
					continue
				}
				n.logf("LOOKUP(%s): adding peer", host)
				lookupPeer := newLookupPeer(host, n.getOpts().MaxBodySize, n.getOpts().Logger,
					connectCallback(n, hostname, syncTopicChan))
				// 开始连接lookupd服务
				lookupPeer.Command(nil) // start the connection
				lookupPeers = append(lookupPeers, lookupPeer)
				lookupAddrs = append(lookupAddrs, host)
			}
			// 赋值给n.lookupPeers
			n.lookupPeers.Store(lookupPeers)
			connect = false
		}

		select {
		case <-ticker: // 15秒发送一次心跳包,执行一次PING操作
			// send a heartbeat and read a response (read detects closed conns)
			for _, lookupPeer := range lookupPeers {
				n.logf("LOOKUPD(%s): sending heartbeat", lookupPeer)
				cmd := nsq.Ping()
				_, err := lookupPeer.Command(cmd)
				if err != nil {
					n.logf("LOOKUPD(%s): ERROR %s - %s", lookupPeer, cmd, err)
				}
			}
		case val := <-n.notifyChan: // topic和channel有变化时(新增或删除),发送REGISTER、UNREGISTER命令,执行REGISTER、UNREGISTER操作
			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
				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
				branch = "topic"
				topic := val.(*Topic)
				if topic.Exiting() == true {
					cmd = nsq.UnRegister(topic.name, "")
				} else {
					cmd = nsq.Register(topic.name, "")
				}
			}

			for _, lookupPeer := range lookupPeers {
				n.logf("LOOKUPD(%s): %s %s", lookupPeer, branch, cmd)
				_, err := lookupPeer.Command(cmd)
				if err != nil {
					n.logf("LOOKUPD(%s): ERROR %s - %s", lookupPeer, cmd, err)
				}
			}
		case lookupPeer := <-syncTopicChan: // 将Metadata中的topic、channel注册到nsqlookupd服务中
			var commands []*nsq.Command
			// build all the commands first so we exit the lock(s) as fast as possible
			n.RLock()
			for _, topic := range n.topicMap {
				topic.RLock()
				if len(topic.channelMap) == 0 {
					commands = append(commands, nsq.Register(topic.name, ""))
				} else {
					for _, channel := range topic.channelMap {
						commands = append(commands, nsq.Register(channel.topicName, channel.name))
					}
				}
				topic.RUnlock()
			}
			n.RUnlock()

			for _, cmd := range commands {
				n.logf("LOOKUPD(%s): %s", lookupPeer, cmd)
				_, err := lookupPeer.Command(cmd)
				if err != nil {
					n.logf("LOOKUPD(%s): ERROR %s - %s", lookupPeer, cmd, err)
					break
				}
			}
		case <-n.optsNotificationChan: // 添加或减少nsqlookupd服务
			var tmpPeers []*lookupPeer
			var tmpAddrs []string
			for _, lp := range lookupPeers {
				if in(lp.addr, n.getOpts().NSQLookupdTCPAddresses) {
					tmpPeers = append(tmpPeers, lp)
					tmpAddrs = append(tmpAddrs, lp.addr)
					continue
				}
				n.logf("LOOKUP(%s): removing peer", lp)
				lp.Close()
			}
			lookupPeers = tmpPeers
			lookupAddrs = tmpAddrs
			connect = true
		case <-n.exitChan: // 退出
			goto exit
		}
	}

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

func in(s string, lst []string) bool {
	for _, v := range lst {
		if s == v {
			return true
		}
	}
	return false
}

// nsqlookupd服务的HTTP地址
func (n *NSQD) lookupdHTTPAddrs() []string {
	var lookupHTTPAddrs []string
	lookupPeers := n.lookupPeers.Load()
	if lookupPeers == nil {
		return nil
	}
	for _, lp := range lookupPeers.([]*lookupPeer) {
		if len(lp.Info.BroadcastAddress) <= 0 {
			continue
		}
		addr := net.JoinHostPort(lp.Info.BroadcastAddress, strconv.Itoa(lp.Info.HTTPPort))
		lookupHTTPAddrs = append(lookupHTTPAddrs, addr)
	}
	return lookupHTTPAddrs
}






你可能感兴趣的:(golang,NSQ源码阅读)