今天主要讲的是 NSQD 中 lookup, 其主要作用是告知 Lookupd,当前 NSQD 实例的情况,包括心跳检测,Topic 和 Channel 的变化。
主要代码文件:
1.nsqd/lookup_peer.go 主要定义了一些集群的消息的结构体和相关的实例化
type lookupPeer struct {
logf lg.AppLogFunc //日志输出函数
addr string //Lookupd 地址
conn net.Conn
state int32 //状态
connectCallback func(*lookupPeer) //连接回调
maxBodySize int64 //最大消息长度
Info peerInfo //集群伙伴元数据
}
//lookupd的信息
type peerInfo struct {
TCPPort int `json:"tcp_port"`
HTTPPort int `json:"http_port"`
Version string `json:"version"`
BroadcastAddress string `json:"broadcast_address"`
}
Command 函数 用于发布请求到Lookupd
func (lp *lookupPeer) Command(cmd *nsq.Command) ([]byte, error) {
initialState := lp.state
if lp.state != stateConnected { //如果没有连接Lookupd
err := lp.Connect() //连接Lookupd
...
lp.state = stateConnected
_, err = lp.Write(nsq.MagicV1) //协议版本确认
....
if initialState == stateDisconnected {
lp.connectCallback(lp) //执行连接回调函数
}
..
}
...
_, err := cmd.WriteTo(lp) //写入请求
...
//读取响应
resp, err := readResponseBounded(lp, lp.maxBodySize)
if err != nil {
lp.Close()
return nil, err
}
return resp, nil
}
readResponseBounded 函数 读取响应
func readResponseBounded(r io.Reader, limit int64) ([]byte, error) {
var msgSize int32
//读取消息总长度
err := binary.Read(r, binary.BigEndian, &msgSize)
if err != nil {
return nil, err
}
....
//读取消息
buf := make([]byte, msgSize)
_, err = io.ReadFull(r, buf)
if err != nil {
return nil, err
}
return buf, nil
}
其他函数:
Connect、Read、Write 和 Close,都是字面上的意思
2.nsqd/lookup.go
connectCallback 函数,连接回调
func connectCallback(n *NSQD, hostname string) func(*lookupPeer) {
return func(lp *lookupPeer) {
ci := make(map[string]interface{})
ci["version"] = version.Binary //版本号
ci["tcp_port"] = n.RealTCPAddr().Port //NSQD 的 TCP 端口
ci["http_port"] = n.RealHTTPAddr().Port //NSQD 的 HTTP 端口
ci["hostname"] = hostname //NSQD的实例的地址
ci["broadcast_address"] = n.getOpts().BroadcastAddress //NSQD广播地址 默认配置取的是os.Hostname
cmd, err := nsq.Identify(ci) //生成Identify命令
...
resp, err := lp.Command(cmd) //发送Identify请求
if err != nil {
n.logf(LOG_ERROR, "LOOKUPD(%s): %s - %s", lp, cmd, err)
return
} else if bytes.Equal(resp, []byte("E_INVALID")) {
...
} else {
//解析 lookupd 的基本信息
err = json.Unmarshal(resp, &lp.Info)
...
}
//下面的都是 向lookupd 注册 NSQD 拥有的 Topic 和 Channel
var commands []*nsq.Command
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(LOG_INFO, "LOOKUPD(%s): %s", lp, cmd)
_, err := lp.Command(cmd)
if err != nil {
n.logf(LOG_ERROR, "LOOKUPD(%s): %s - %s", lp, cmd, err)
return
}
}
}
}
lookupLoop 函数 主要是用来监听NSQD的状态,并上报给lookupd。 lookupLook 会在 nsqd.Main 函数中调用,NSQD会开启一个 goroutine 进行为维护。
func (n *NSQD) lookupLoop() {
...
//每15秒进行心跳检查
ticker := time.Tick(15 * time.Second)
for {
if connect { //如果当前NSQD需要连接Lookupd
//lookupd 是一个集群,所有 NSQLookupdTCPAddresses 有多个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: //心跳检查
for _, lookupPeer := range lookupPeers {
cmd := nsq.Ping()
_, err := lookupPeer.Command(cmd)
}
case val := <-n.notifyChan: //Topic Channel 变动通知
var cmd *nsq.Command
var branch string
switch val.(type) {
case *Channel: //channel 变动
branch = "channel"
channel := val.(*Channel)
if channel.Exiting() == true { //注册新channel
cmd = nsq.UnRegister(channel.topicName, channel.name)
} else { //注销channel
cmd = nsq.Register(channel.topicName, channel.name)
}
case *Topic: //topic 变动
branch = "topic"
topic := val.(*Topic)
if topic.Exiting() == true { //注册新topic
cmd = nsq.UnRegister(topic.name, "")
} else { //注销topic
cmd = nsq.Register(topic.name, "")
}
}
for _, lookupPeer := range lookupPeers {
_, err := lookupPeer.Command(cmd)
}
case <-n.optsNotificationChan: //Lookupd 集群地址变动通知
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(LOG_INFO, "LOOKUP(%s): removing peer", lp)
lp.Close()
}
lookupPeers = tmpPeers
lookupAddrs = tmpAddrs
connect = true
case <-n.exitChan:
goto exit
}
}
exit:
n.logf(LOG_INFO, "LOOKUP: closing")
}
总结:lookup 的作用是 NSQD 集群上报,报告内容包括 新跳检测 和 Topic、Channel 的变动通知。