今天主要講的是 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 的變動通知。