查看nsq的几个模块,发现nsqlookupd模块的代码量最少,所以从它开始。从nsqlookupd/nsqlookupd.go文件开始:
package nsqlookupd
import (
"fmt"
"log"
"net"
"os"
"sync"
"github.com/nsqio/nsq/internal/http_api"
"github.com/nsqio/nsq/internal/protocol"
"github.com/nsqio/nsq/internal/util"
"github.com/nsqio/nsq/internal/version"
)
type NSQLookupd struct {
//读写互斥锁 https://golang.org/pkg/sync/#RWMutex
//http://blog.csdn.net/aslackers/article/details/62044726
/**
* 基本遵循两大原则:
* 1、可以随便度,多个goroutine同时读
* 2、写的时候,啥也不能干。不能读也不能写
*/
sync.RWMutex
//在文件nsqlookupd/options.go中定义,记录NSQLookupd的配置信息
opts *Options
tcpListener net.Listener
httpListener net.Listener
//在文件internal/util/wait_group_wrapper.go中定义,与sync.WaitGroup相关,用于线程同步
waitGroup util.WaitGroupWrapper
//在文件nsqlookupd/registration_db.go中定义,与数据存储有关
DB *RegistrationDB
}
//根据配置的nsqlookupd options信息,创建一个NSQLookupd实例
func New(opts *Options) *NSQLookupd {
if opts.Logger == nil {
opts.Logger = log.New(os.Stderr, opts.LogPrefix, log.Ldate|log.Ltime|log.Lmicroseconds)
}
n := &NSQLookupd{
opts: opts,
DB: NewRegistrationDB(),
}
n.logf(version.String("nsqlookupd"))
return n
}
func (l *NSQLookupd) logf(f string, args ...interface{}) {
l.opts.Logger.Output(2, fmt.Sprintf(f, args...))
}
//Main函数,启动nsqlockupd进程时,
//首先运行nsqlookupd/options.go里的NewOptions()这个方法,设置默认配置
//然后运行上面的New()方法,根据配置信息创建一个NSQLookupd实例
//再运行这里的Main()方法,启动nsqlockupd进程
//详情见apps/nsqlookupd/nsqlookupd.go里的Start()方法
func (l *NSQLookupd) Main() {
//Context实例,在文件nsqlookupd/context.go中定义,NSQLookupd类型的指针
ctx := &Context{l}
//监听TCP,监听地址取options中的默认参数,0.0.0.0:4160
tcpListener, err := net.Listen("tcp", l.opts.TCPAddress)
if err != nil {
l.logf("FATAL: listen (%s) failed - %s", l.opts.TCPAddress, err)
os.Exit(1)
}
//RWMutex写锁
l.Lock()
//将Listener保存到NSQLookupd对象元素里,供后面的方法调用,如:RealTCPAddr()
l.tcpListener = tcpListener
l.Unlock()
//tcpServer实例,在文件nsqlookupd/tcp.go中定义,处理TCP接收到的数据
tcpServer := &tcpServer{ctx: ctx}
//使用sync.WaitGroup,关于sync.WaitGroup介绍,http://blog.csdn.net/aslackers/article/details/62046306
//通过下面的Exit()方法,可知,当关闭nsqlookupd进程时,主线程(goroutine)会等待所有TCP监听关闭,才关闭自己
l.waitGroup.Wrap(func() {
//在文件internal/protocol/tcp_server.go中定义,
//接收监听并处理TCP数据
//第二个参数tcpServer实现了internal/protocol/tcp_server.go文件中定义的TCPHandler接口
//tcpServer接收到TCP数据时,会调用其Handle()方法(nsqlookupd/tcp.go)处理。
protocol.TCPServer(tcpListener, tcpServer, l.opts.Logger)
})
//监听并处理HTTP,逻辑和上面TCP差不多 0.0.0.0:4161
httpListener, err := net.Listen("tcp", l.opts.HTTPAddress)
if err != nil {
l.logf("FATAL: listen (%s) failed - %s", l.opts.HTTPAddress, err)
os.Exit(1)
}
l.Lock()
l.httpListener = httpListener
l.Unlock()
//httpServer实例,在文件nsqlookupd/http.go中定义,处理HTTP接收到的数据
//并定义了一系列HTTP接口(路由)
httpServer := newHTTPServer(ctx)
l.waitGroup.Wrap(func() {
http_api.Serve(httpListener, httpServer, "HTTP", l.opts.Logger)
})
}
//获取监听的TCP地址
func (l *NSQLookupd) RealTCPAddr() *net.TCPAddr {
l.RLock()
defer l.RUnlock()
return l.tcpListener.Addr().(*net.TCPAddr)
}
//获取HTTP地址
func (l *NSQLookupd) RealHTTPAddr() *net.TCPAddr {
l.RLock()
defer l.RUnlock()
return l.httpListener.Addr().(*net.TCPAddr)
}
//退出nsqloopupd进程,关闭两个Listener,等待TCP和HTTP线程都关闭了,才关闭自身
func (l *NSQLookupd) Exit() {
if l.tcpListener != nil {
l.tcpListener.Close()
}
if l.httpListener != nil {
l.httpListener.Close()
}
l.waitGroup.Wait()
}
程序执行步骤:
1、执行nsqlookupd/options.go中的NewOptions()方法,创建Options实例,生成NSQLookupd配置信息
2、执行上面的New()方法,将第1步中创建的Options配置信息作为参数,创建NSQLookupd实例
3、执行NSQLookupd实例的Main()方法,创建nsqloopupd进程,TCP监听0.0.0.0:4160,HTTP监听0.0.0.0:4161
nsqloopupd进程退出时,调用NSQLookupd实例的Exit()方法,关闭TCP和HTTP监听,主线程(goroutine)等待子线程(goroutine)退出,程序退出