今天主要講的是 Lookupd 中 TCP 服務的實現,TCP服務主要用於響應 NSQD 的心跳檢查,NSQD 註冊等信息。Lookupd 的 TCP 服務與 NSQD 中的 TCP 服務 在代碼結構及實現方面非常相似。都是通過 IOLoop 循環讀取客戶端請求、解析命令、調用命令函數、執行命令、返回響應。
主要代碼文件:
1.nsqlookupd/lookup_protocol_v1.go
IOLoop 函數
func (p *LookupProtocolV1) IOLoop(conn net.Conn) error {
var err error
var line string
//包裝conn
client := NewClientV1(conn)
reader := bufio.NewReader(client)
for {
//以回車作爲命令分隔
line, err = reader.ReadString('\n')
if err != nil {
break
}
//去除左右空格
line = strings.TrimSpace(line)
//以空格分隔,生成命令數組
params := strings.Split(line, " ")
var response []byte
//執行命令
response, err = p.Exec(client, reader, params)
if err != nil {
....
//響應錯誤
_, sendErr := protocol.SendResponse(client, []byte(err.Error()))
if sendErr != nil {
p.ctx.nsqlookupd.logf(LOG_ERROR, "[%s] - %s%s", client, sendErr, ctx)
break
}
...
continue
}
if response != nil {
//響應請求
_, err = protocol.SendResponse(client, response)
if err != nil {
break
}
}
}
conn.Close()
if client.peerInfo != nil {
//清除客戶信息
registrations := p.ctx.nsqlookupd.DB.LookupRegistrations(client.peerInfo.id)
for _, r := range registrations {
if removed, _ := p.ctx.nsqlookupd.DB.RemoveProducer(r, client.peerInfo.id); removed {
p.ctx.nsqlookupd.logf(LOG_INFO, "DB: client(%s) UNREGISTER category:%s key:%s subkey:%s",
client, r.Category, r.Key, r.SubKey)
}
}
}
return err
}
Exec 函數
func (p *LookupProtocolV1) Exec(client *ClientV1, reader *bufio.Reader, params []string) ([]byte, error) {
switch params[0] {
case "PING": //心跳
return p.PING(client, params)
case "IDENTIFY": //NSQD 註冊
return p.IDENTIFY(client, reader, params[1:])
case "REGISTER": //註冊Topic or Channel
return p.REGISTER(client, reader, params[1:])
case "UNREGISTER": //註銷Topic or Channel
return p.UNREGISTER(client, reader, params[1:])
}
return nil, protocol.NewFatalClientErr(nil, "E_INVALID", fmt.Sprintf("invalid command %s", params[0]))
}
PING 函數 心跳檢測
func (p *LookupProtocolV1) PING(client *ClientV1, params []string) ([]byte, error) {
if client.peerInfo != nil {
// we could get a PING before other commands on the same client connection
cur := time.Unix(0, atomic.LoadInt64(&client.peerInfo.lastUpdate))
now := time.Now()
//更新心跳檢測時間
atomic.StoreInt64(&client.peerInfo.lastUpdate, now.UnixNano())
}
return []byte("OK"), nil
}
IDENTIFY 函數 NSQD註冊
func (p *LookupProtocolV1) IDENTIFY(client *ClientV1, reader *bufio.Reader, params []string) ([]byte, error) {
var err error
//防止重複註冊
if client.peerInfo != nil {
return nil, protocol.NewFatalClientErr(err, "E_INVALID", "cannot IDENTIFY again")
}
... //讀取請求
... // 收集客戶端信息,包括IP,TPC 端口,域名,廣播地址等。
client.peerInfo = &peerInfo
//向DB註冊客戶端
if p.ctx.nsqlookupd.DB.AddProducer(Registration{"client", "", ""}, &Producer{peerInfo: client.peerInfo}) {
p.ctx.nsqlookupd.logf(LOG_INFO, "DB: client(%s) REGISTER category:%s key:%s subkey:%s", client, "client", "", "")
}
//建立響應,將服務相關的IP,端口等返回給客戶端。
return response, nil
}
REGISTER 函數註冊 Topic or Channel
func (p *LookupProtocolV1) REGISTER(client *ClientV1, reader *bufio.Reader, params []string) ([]byte, error) {
...
//getTopicChan 主要是解析出param 中的 topic 和 channel
topic, channel, err := getTopicChan("REGISTER", params)
if err != nil {
return nil, err
}
//註冊channel
if channel != "" {
key := Registration{"channel", topic, channel}
if p.ctx.nsqlookupd.DB.AddProducer(key, &Producer{peerInfo: client.peerInfo}) {
p.ctx.nsqlookupd.logf(LOG_INFO, "DB: client(%s) REGISTER category:%s key:%s subkey:%s",
client, "channel", topic, channel)
}
}
//註冊topic
key := Registration{"topic", topic, ""}
if p.ctx.nsqlookupd.DB.AddProducer(key, &Producer{peerInfo: client.peerInfo}) {
p.ctx.nsqlookupd.logf(LOG_INFO, "DB: client(%s) REGISTER category:%s key:%s subkey:%s",
client, "topic", topic, "")
}
return []byte("OK"), nil
}
UNREGISTER 函數註銷 Topic or Channel
func (p *LookupProtocolV1) UNREGISTER(client *ClientV1, reader *bufio.Reader, params []string) ([]byte, error) {
...
if channel != "" {
//刪除channel
key := Registration{"channel", topic, channel}
removed, left := p.ctx.nsqlookupd.DB.RemoveProducer(key, client.peerInfo.id)
...
//刪除臨時節點
if left == 0 && strings.HasSuffix(channel, "#ephemeral") {
p.ctx.nsqlookupd.DB.RemoveRegistration(key)
}
} else {
//查找 topic 下所有的channel
registrations := p.ctx.nsqlookupd.DB.FindRegistrations("channel", topic, "*")
for _, r := range registrations {
//刪除channel
removed, _ := p.ctx.nsqlookupd.DB.RemoveProducer(r, client.peerInfo.id)
if removed {
...
}
}
//刪除topic
key := Registration{"topic", topic, ""}
removed, left := p.ctx.nsqlookupd.DB.RemoveProducer(key, client.peerInfo.id)
...
//刪除臨時節點
if left == 0 && strings.HasSuffix(topic, "#ephemeral") {
p.ctx.nsqlookupd.DB.RemoveRegistration(key)
}
}
return []byte("OK"), nil
}
總結:Lookupd 中的 TCPServer 主要提供了 NSQD 註冊,心跳檢測、Topic or Channel 註冊及 Topic or Channel 註銷。
下一篇分享:Lookupd 中的 HttpServer