一般nsqd是部署在靠近client的,甚至是本机,用于收集client的pub信息
nsqd会吧topic和channel信息持久化,并同步到nsqlookup
入口代码位于github.com/nsqio/nsq/apps/nsqd.go
func (p *program) Start() error {
...
nsqd := nsqd.New(opts)
加载旧的metadata(topic,channnel)
err := nsqd.LoadMetadata()
过滤ephemeral的topic和channel后进行持久化
err = nsqd.PersistMetadata()
启动nsqd
nsqd.Main()
...
}
此处我们只看tcpserver(它用于client创建pub,sub等)
func (n *NSQD) Main() {
...
tcpListener, err := net.Listen("tcp", n.getOpts().TCPAddress)
tcpServer := &tcpServer{ctx: ctx}
protocol.TCPServer(n.tcpListener, tcpServer, n.logf)
...
用于同步本nsqd的topic,channel到nsqlookupd
n.waitGroup.Wrap(func() { n.lookupLoop() })
...
}
func (p *tcpServer) Handle(clientConn net.Conn) {
...
var prot protocol.Protocol
...
prot = &protocolV2{ctx: p.ctx}
进入读取命令
err = prot.IOLoop(clientConn)
...
}
func (p *protocolV2) IOLoop(conn net.Conn) error {
...
line, err = client.Reader.ReadSlice('\n')
params := bytes.Split(line, separatorBytes)
进入命令解析
response, err = p.Exec(client, params)
...
}
func (p *protocolV2) Exec(client *clientV2, params [][]byte) ([]byte, error) {
...
case bytes.Equal(params[0], []byte("PUB")):
进入发布命令
return p.PUB(client, params)
...
}
func (p *protocolV2) PUB(client *clientV2, params [][]byte) ([]byte, error) {
...
获取topic
topic := p.ctx.nsqd.GetTopic(topicName)
创建消息
msg := NewMessage(topic.GenerateID(), messageBody)
持久化消息
err = topic.PutMessage(msg)
...
}
func (n *NSQD) GetTopic(topicName string) *Topic {
...
新建topic
t = NewTopic(topicName, &context{n}, deleteCallback)
从nsqlookup查询所有此topic下的channel
channelNames, err := n.ci.GetLookupdTopicChannels(t.name, lookupdHTTPAddrs)
for _, channelName := range channelNames {
if strings.HasSuffix(channelName, "#ephemeral") {
// we don't want to pre-create ephemeral channels
// because there isn't a client connected
continue
}
...
}
func NewTopic(topicName string, ctx *context, deleteCallback func(*Topic)) *Topic {
...
用于持久化
t.backend = diskqueue.New(
...)
...
通知创建topic成功
t.ctx.nsqd.Notify(t)
...
}
func (n *NSQD) Notify(v interface{}) {
...
case n.notifyChan <- v:
...
if !persist {
return
}
n.Lock()
err := n.PersistMetadata()
持久化topic
...
}
func (t *Topic) PutMessage(m *Message) error {
...
err := t.put(m)
...
}
func (t *Topic) put(m *Message) error {
...
err := writeMessageToBackend(b, m, t.backend)
...
}
func writeMessageToBackend(buf *bytes.Buffer, msg *Message, bq BackendQueue) error {
buf.Reset()
_, err := msg.WriteTo(buf)
if err != nil {
return err
}
return bq.Put(buf.Bytes())
}
func (d *diskQueue) Put(data []byte) error {
d.RLock()
defer d.RUnlock()
if d.exitFlag == 1 {
return errors.New("exiting")
}
d.writeChan <- data
return <-d.writeResponseChan
}
func (d *diskQueue) ioLoop() {
...
case dataWrite := <-d.writeChan:
真正的持久化入口
d.writeResponseChan <- d.writeOne(dataWrite)
...
}
func (n *NSQD) lookupLoop() {
...
通过此方法来更新nsqlookupd重连后nsqlookupd的topic,channel信息
lookupPeer := newLookupPeer(host, n.getOpts().MaxBodySize, n.logf,
connectCallback(n, hostname, syncTopicChan))
...
select {
定期ping nsqlookupd
case <-ticker:
for _, lookupPeer := range lookupPeers {
n.logf(LOG_DEBUG, "LOOKUPD(%s): sending heartbeat", lookupPeer)
cmd := nsq.Ping()
_, err := lookupPeer.Command(cmd)
if err != nil {
n.logf(LOG_ERROR, "LOOKUPD(%s): %s - %s", lookupPeer, cmd, err)
}
...
同步topic到nsqlookup
case val := <-n.notifyChan:
switch val.(type) {
...
case *Topic:
...
topic := val.(*Topic)
if topic.Exiting() == true {
cmd = nsq.UnRegister(topic.name, "")
} else {
cmd = nsq.Register(topic.name, "")
}
}
for _, lookupPeer := range lookupPeers {
_, err := lookupPeer.Command(cmd)
...
}