nsqlookupd:高效能訊息中介軟體 NSQ 解析
摘要:本篇將會結合原始碼介紹 nsqlookupd 的實現細節。
本篇將會結合原始碼介紹 nsqlookupd 的實現細節。nsqlookupd 主要流程與nsqd 執行邏輯相似,區別在於具體執行的任務不同。
nsqlookupd是nsq管理叢集拓撲資訊以及用於註冊和發現nsqd服務。所以,也可以把nsqlookupd理解為註冊發現服務。當nsq叢集中有多個nsqlookupd服務時,因為每個nsqd都會向所有的nsqlookupd上報本地資訊,因此nsqlookupd具有最終一致性。
入口函式
在 nsq/apps/nsqlookupd/main.go 可以找到執行入口檔案。
// 位於apps/nsqlookupd/main.go:45 func main() { prg := &program{} if err := svc.Run(prg, syscall.SIGINT, syscall.SIGTERM); err != nil { logFatal("%s", err) } } func (p *program) Init(env svc.Environment) error { if env.IsWindowsService() { dir := filepath.Dir(os.Args[0]) return os.Chdir(dir) } return nil } func (p *program) Start() error { opts := nsqlookupd.NewOptions() flagSet := nsqlookupdFlagSet(opts) ... }
同樣,透過第三方 svc 包進行優雅的後臺程式管理,svc.Run() -> svc.Init() -> svc.Start(),啟動 nsqlookupd 例項。
// 位於 apps/nsqlookupd/main.go:80 options.Resolve(opts, flagSet, cfg) nsqlookupd, err := nsqlookupd.New(opts) if err != nil { logFatal("failed to instantiate nsqlookupd", err) } p.nsqlookupd = nsqlookupd go func() { err := p.nsqlookupd.Main() if err != nil { p.Stop() os.Exit(1) } }()
初始化配置引數(優先順序:flagSet-命令列引數 > cfg-配置檔案 > opts-預設值),開啟協程,進入 nsqlookupd.Main() 主函式。
監聽請求
我們來看下 nsqlookupd 是如何監聽請求的,程式碼實現如下:
// 位於 nsqlookupd/nsqlookupd.go:53 func (l *NSQLookupd) Main() error { ctx := &Context{l} exitCh := make(chan error) var once sync.Once exitFunc := func(err error) { once.Do(func() { if err != nil { l.logf(LOG_FATAL, "%s", err) } exitCh <- err }) } tcpServer := &tcpServer{ctx: ctx} l.waitGroup.Wrap(func() { exitFunc(protocol.TCPServer(l.tcpListener, tcpServer, l.logf)) }) httpServer := newHTTPServer(ctx) l.waitGroup.Wrap(func() { exitFunc(http_api.Serve(l.httpListener, httpServer, "HTTP", l.logf)) }) err := <-exitCh return err }
開啟 goroutine 執行 tcpServer, httpServer,分別監聽 nsqd, nsqadmin 的客戶端請求。
處理請求
// 位於 internal/protocol/tcp_server.go:17 func TCPServer(listener net.Listener, handler TCPHandler, logf lg.AppLogFunc) error { logf(lg.INFO, "TCP: listening on %s", listener.Addr()) for { 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 } go handler.Handle(clientConn) } logf(lg.INFO, "TCP: closing %s", listener.Addr()) return nil }
TCPServer 迴圈監聽客戶端請求,建立長連線進行通訊,並開啟 handler 處理每一個客戶端 conn。
裝飾 http 路由
httpServer 透過 http_api.Decorate 裝飾器實現對各 http 路由進行 handler 裝飾,如加 log 日誌、V1 協議版本號的統一格式輸出等;
func newHTTPServer(ctx *Context) *httpServer { log := http_api.Log(ctx.nsqlookupd.logf) router := httprouter.New() router.HandleMethodNotAllowed = true router.PanicHandler = http_api.LogPanicHandler(ctx.nsqlookupd.logf) router.NotFound = http_api.LogNotFoundHandler(ctx.nsqlookupd.logf) router.MethodNotAllowed = http_api.LogMethodNotAllowedHandler(ctx.nsqlookupd.logf) s := &httpServer{ ctx: ctx, router: router, } 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("GET", "/debug", http_api.Decorate(s.doDebug, log, http_api.V1)) router.Handle("GET", "/lookup", http_api.Decorate(s.doLookup, log, http_api.V1)) router.Handle("GET", "/topics", http_api.Decorate(s.doTopics, log, http_api.V1)) router.Handle("GET", "/channels", http_api.Decorate(s.doChannels, log, http_api.V1)) }
處理客戶端命令
tcp 解析 V1 協議,內部協議封裝的 prot.IOLoop(conn) 進行迴圈處理客戶端命令,直到客戶端命令全部解析處理完畢才關閉連線。
var prot protocol.Protocol switch protocolMagic { case " V1": prot = &LookupProtocolV1{ctx: p.ctx} default: protocol.SendResponse(clientConn, []byte("E_BAD_PROTOCOL")) clientConn.Close() p.ctx.nsqlookupd.logf(LOG_ERROR, "client(%s) bad protocol magic '%s'", clientConn.RemoteAddr(), protocolMagic) return } err = prot.IOLoop(clientConn)
執行命令
透過內部協議進行 p.Exec(執行命令)、p.SendResponse(返回結果),保證每個 nsqd 節點都能正確的進行服務註冊(register)與登出(unregister),並進行心跳檢測(ping)節點的可用性,確保客戶端取到的 nsqd 節點列表都是最新可用的。
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 { ctx := "" if parentErr := err.(protocol.ChildErr).Parent(); parentErr != nil { ctx = " - " + parentErr.Error() } _, 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()
nsqlookupd 服務同時開啟 tcp 和 http 兩個監聽服務,nsqd 會作為客戶端,連上 nsqlookupd 的 tcp 服務,並上報自己的 topic 和 channel 資訊,以及透過心跳機制判斷 nsqd 狀態;還有個 http 服務提供給 nsqadmin 獲取叢集資訊。
小結
本文主要介紹 nsqlookupd 的實現,nsqlookupd 同樣是一個守護程式,負責管理拓撲資訊。客戶端透過查詢 nsqlookupd 來發現指定話題( topic )的生產者,並且 nsqd 節點廣播話題(topic)和通道( channel )資訊。有兩個介面: TCP 介面, nsqd 用它來廣播。 HTTP 介面,客戶端用它來發現和管理。
下一篇文章,將會繼續介紹 nsq 中其他模組實現的細節。
本文分享自華為雲社群《》,原文作者:aoho 。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2249/viewspace-2796116/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 解析訊息中介軟體之RabbitMQMQ
- 訊息中介軟體
- 中介軟體之訊息中介軟體-pulsar
- 訊息中介軟體rabbitMQMQ
- 訊息中介軟體—RocketMQ訊息傳送MQ
- 分散式訊息中介軟體分散式
- 訊息中介軟體之ActiveMQMQ
- 訊息中介軟體選型
- PHP 訊息中介軟體 工具庫PHP
- 訊息中介軟體 — 使用場景
- 訊息中介軟體 — RocketMQ簡介MQ
- 訊息中介軟體 RocketMQ 原始碼解析 —— 除錯環境搭建MQ原始碼除錯
- 訊息中介軟體RocketMQ原始碼解析-- --除錯環境搭建MQ原始碼除錯
- 常見訊息中介軟體之RocketMQMQ
- 常見訊息中介軟體之ActiveMQMQ
- 深入訊息中介軟體選型分析
- 訊息中介軟體—RocketMQ訊息消費(三)(訊息消費重試)MQ
- 從通訊開始聊聊訊息中介軟體
- 訊息中介軟體—RocketMQ的RPC通訊(一)MQRPC
- [分散式][訊息中介軟體]訊息中介軟體如何實現每秒幾十萬的高併發寫入分散式
- MQ系列:訊息中介軟體執行原理MQ
- 訊息中介軟體通用化封裝封裝
- 訊息型中介軟體之RabbitMQ叢集MQ
- 訊息中介軟體的應用場景
- 訊息中介軟體—Kafka 的設計思想Kafka
- 訊息中介軟體(RabbitMq、Kafka)分析比較MQKafka
- 淺談訊息佇列及常見的訊息中介軟體佇列
- 聊聊訊息中介軟體(1),AMQP那些事兒MQ
- 訊息型中介軟體之RabbitMQ基礎使用MQ
- 頭條終面:寫個訊息中介軟體
- 訊息中介軟體-kafka學習筆記一Kafka筆記
- .net core Redis訊息佇列中介軟體【InitQ】Redis佇列
- 快速入門大資料訊息中介軟體大資料
- 深入理解阿里分散式訊息中介軟體阿里分散式
- vivo 超大規模訊息中介軟體實踐之路
- 訊息中介軟體RabbitMQ_RabbitMQ叢集搭建8MQ
- 訊息中介軟體RabbitMQ_RabbitMQ快速入門3MQ
- C#中的訊息中介軟體(RabbitMQ 和 Redis)C#MQRedis