這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
從本節開始,將逐步閱讀nsq各模組的代碼。
讀一份代碼,我的思路一般是:
1、瞭解用法,知道了怎麼使用,對理解代碼有宏觀上有很大協助。如第一篇文章:go語言nsq源碼解讀-基本介紹。
2、瞭解各大模組的功能特點,同時再想想,如果讓自己來實現這些模組,會是怎麼樣的思路。如第二篇文章:go語言nsq源碼解讀二 nsqlookupd、nsqd與nsqadmin
3、開始上手試讀,為不打擊閱讀的積極性,可以選擇一個簡單的模組,或者某一個功能點開始讀。對nsq而言,開啟源碼的目錄看一下,發現nsqlookupd和nsqadmin的代碼相對較少,而nsqd的代碼量較多。再比較nsqlookupd和nsqadmin,發現nsqadmin下還有一個templates目錄,這大概是在第一篇文章裡用來顯示裡的網頁的模板檔案。再考慮到nsqlookupd的中樞作用,我決定從nsqlookupd的代碼開始讀起。
4、讀代碼的第一遍,偏向於讀懂,瞭解功能的實現即可。所有代碼全部讀過一往遍後,看一下檔案名稱,就能知道這個檔案裡的代碼實現了什麼功能。碰到讀不懂的地方,可以通過加註釋輸出變數、打斷點跟蹤等方式輔助學習。
5、之後讀第二遍,理解宏觀的架構體系,心裡始終要想的問題是: 為什麼要這麼做?如果是我,我會怎麼做?這兩種做法有什麼利弊?多揣摩,細研讀,並把體會到的精華思想吸引牢記,轉為已有。
6、再之後,可以讀第三遍,這基本就是撥雲見日的境界了,對代碼了如之掌,考慮是否有更好的實現,然後可以對代碼動手改造。如果在代碼還沒讀懂前改代碼,那屬於在給白雪公主喂屎,噁心的要死了。
好了,廢話就說到這裡了,給大學推薦一本學習go語言的書籍:Go Web 編程,中文編寫,內容階層清晰,知識全面,適合入門閱讀。
下面我們開始nsqlookupd的源碼解讀。
nsqlookupd的代碼位於源碼根目錄的nsqlookupd下。目錄下共十一個檔案,去掉README.md檔案和兩個以_test.go結尾(這是單元測試檔案)的檔案,共有八個檔案。另外nsqlookupd還會用到util目錄下的一些功能代碼,這個也會閱讀到。對代碼的解釋我都會放在注釋裡,等第一遍代碼閱讀完,我會把所有代碼打包傳上來。
OK,首先從nsqlookupd\nsqlookupd.go檔案開始:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
package nsqlookupd
import ( "log" "net"
"github.com/bitly/nsq/util" )
type NSQLookupd struct { //在檔案nsqlookupd\options.go中定義,記錄NSQLookupd的配置資訊 options *nsqlookupdOptions
//nsqlookupd監聽TCP資料的地址 tcpAddr *net.TCPAddr
//nsqlookupd監聽HTTP資料的地址 httpAddr *net.TCPAddr
//使用上面的tcpAddr建立的Listener tcpListener net.Listener
//使用上面的httpAddr建立的Listener httpListener net.Listener
//在util\wait_group_wrapper.go檔案中定義,與sync.WaitGroup相關,用於線程同步。 waitGroup util.WaitGroupWrapper
//在nsqlookupd\registration_db.go檔案中定義,看字面意思DB(database)就可知道這涉及到資料的存取 DB *RegistrationDB } // //根據配置的nsqlookupdOptions建立一個NSQLookupd的執行個體 // func NewNSQLookupd(options *nsqlookupdOptions) *NSQLookupd {
//使用配置參數的TCPAddress建立TCP地址,用於和nsqd通訊。 tcpAddr, err := net.ResolveTCPAddr("tcp", options.TCPAddress) if err != nil { log.Fatal(err) }
//使用配置參數的HTTPAddress參數,建立http連結,可以供nsqadmin訪問,以讀取統計資料 httpAddr, err := net.ResolveTCPAddr("tcp", options.HTTPAddress) if err != nil { log.Fatal(err) }
return &NSQLookupd{ options: options, tcpAddr: tcpAddr, httpAddr: httpAddr, DB: NewRegistrationDB(), } }
// //Main函數,啟動時首先執行本函數 //補註:閱讀options.go時,發現nsqlookupd啟動時,首先啟動並執行並不是這個Main方法。而是apps\nsqlookupd\nsqlookupd.go裡的main方法,這個下篇文章會提到。 // func (l *NSQLookupd) Main() { //定義了Context的執行個體,Context在nsqlookupd\context.go檔案中定義,其中只包含了一個nsqlookupd的指標,注意花括弧裡是字元L的小寫,不是數字一. context := &Context{l}
//監聽TCP tcpListener, err := net.Listen("tcp", l.tcpAddr.String()) if err != nil { log.Fatalf("FATAL: listen (%s) failed - %s", l.tcpAddr, err.Error()) }
//把Listener存在NSQLookupd的struct裡 l.tcpListener = tcpListener
//建立tcpServer的執行個體,tcpServer在nsqlookupd\tcp.go檔案中定義,用於處理TCP串連中接收到的資料。通過前面閱讀知道,context裡只是一個NSQLookupd類型的指標。 tcpServer := &tcpServer{context: context}
//調用util.TCPServer方法(在util\tcp_server.go中定義)開始接收監聽並註冊handler。 //傳入的兩個參數第一個是tcpListener //第二個tcpServer實現了util\tcp_server.go中定義的TCPHandler介面。 //tcpServer接到TCP資料時,會調用其Handle方法(見nsqlookupd\tcp.go)來處理。 //此處為何要用到waitGroup,目前還比較迷糊 l.waitGroup.Wrap(func() { util.TCPServer(tcpListener, tcpServer) })
//監聽HTTP httpListener, err := net.Listen("tcp", l.httpAddr.String()) if err != nil { log.Fatalf("FATAL: listen (%s) failed - %s", l.httpAddr, err.Error()) }
//把Listener存在NSQLookupd的struct裡 l.httpListener = httpListener
//建立httpServer的執行個體,httpServer在nsqlookupd\http.go檔案中定義 httpServer := &httpServer{context: context}
//調用util.HTTPServer方法(在util\http_server.go中定義)開始在指定的httpListener上接收http串連。 //傳入的兩個參數第一個是httpListener //第二個httpServer定義了http handler,用於處理HTTP請求。 //同樣,對waitGroup的用法還不是很理解。 l.waitGroup.Wrap(func() { util.HTTPServer(httpListener, httpServer) })
//經過以上閱讀,基本上會有兩個發現: //1、tcpServer和httpServer的代碼很相似。 //2、util\tcp_server.go在註冊handler之前,先定義了一個介面,而tuil\http_server.go卻沒有。 //如果再仔細研究這兩個檔案,還會發現,tcp_server裡,通過go handler.Handle(clientConn)這段代碼,把串連clientConn做為變數,傳給了handler //而在http_server,是把handler傳給了HTTPServer //這主要是因為net/http包和net包用法不一樣,net/http做了進一步有封裝。 }
// //退出 關閉兩個Listener // func (l *NSQLookupd) Exit() { if l.tcpListener != nil { l.tcpListener.Close() }
if l.httpListener != nil { l.httpListener.Close() } l.waitGroup.Wait() } |
上面的代碼裡共涉及到幾個外部檔案:
nsqlookupd\options.go
nsqlookupd\context.go
nsqlookupd\tcp.go
util\tcp_server.go
nsqlookupd\http.go
util\http_server.go
util\wait_group_wrapper.go
nsqlookupd\registration_db.go
這些檔案,將在後續文章中繼續閱讀,其中下一篇為:
go語言nsq源碼解讀四 nsqlookupd源碼options.go、context.go和wait_group_wrapper.go