分布式实时消息平台NSQ: NSQLookup & NSQAdmin设计原理

NSQLookup & NSQAdmin设计原理

nsqlookup是管理中心,负责管理拓扑信息。nsqd节点在启动的时候会向nsqlookup进行注册,并且nsqd节点通过nsqlookup广播话题(topic)和通道(channel)信息。消费者通过查询nsqlookupd来发现指定话题(topic)的nsqd,并与nsqd建立连接进行通信和订阅。

NSQLookup源码实现

启动流程

main 函数:apps/nsqlookup/main.go

启动流程基本上和nsqd查不多,主要是Main 是主要的入口逻辑

type NSQLookupd struct {
    sync.RWMutex				       // 读写锁
    opts         *Options		        // nsqlookup配置
    tcpListener  net.Listener           // 监听的tcp服务
    httpListener net.Listener           // 监听的http服务
    waitGroup    util.WaitGroupWrapper  // 用于等待goroutine结束
    DB           *RegistrationDB        // 注册数据库,存放着topic和producer的映射关系注册表,存储在内存的Map中
}

type RegistrationDB struct {
	sync.RWMutex
	registrationMap map[Registration]ProducerMap 
}

func (l *NSQLookupd) Main() error {
    // ... 省略代码

    // tcp 服务启动
	l.waitGroup.Wrap(func() {
		exitFunc(protocol.TCPServer(l.tcpListener, l.tcpServer, l.logf))
	})
    // http 服务启动
	httpServer := newHTTPServer(l)
	l.waitGroup.Wrap(func() {
		exitFunc(http_api.Serve(l.httpListener, httpServer, "HTTP", l.logf))
	})

	err := <-exitCh
	return err
}

在这里插入图片描述

总结

  • nsqlookupd提供了两种请求方式:
    • 基于http的方式
    • 基于tcp的方式

与NSQD和消费者交互流程

与NSQD交互流程

  • NSQLookup 与 NSQD的交互主要是用于NSQD信息的上报和Topic管理
  • NSQD在启动时会将自己的一些基本信息上报给NSQLookup
  • 有消费者在订阅了Topic和Channel时会将Topic和Channe上报给NSQLookup,有NSQDLookup进行存储
NSQD启动上报基础信息
  • 会上报基础的信息,比如端口,主机名称
  • 上报Topic信息
func connectCallback(n *NSQD, hostname string) func(*lookupPeer) {
	return func(lp *lookupPeer) {
        // 基础信息的上报
		ci := make(map[string]interface{})
		ci["version"] = version.Binary
		ci["tcp_port"] = n.getOpts().BroadcastTCPPort
		ci["http_port"] = n.getOpts().BroadcastHTTPPort
		ci["hostname"] = hostname
		ci["broadcast_address"] = n.getOpts().BroadcastAddress

		cmd, err := nsq.Identify(ci)
		if err != nil {
			lp.Close()
			return
		}

		resp, err := lp.Command(cmd)
        // ... 省略代码
		var commands []*nsq.Command
		n.RLock()
        // 获取 topic 信息
		for _, topic := range n.topicMap {
			topic.RLock()
			if len(topic.channelMap) == 0 {
				commands = append(commands, nsq.Register(topic.name, ""))
			} else {
				for _, channel := range topic.channelMap {
					commands = append(commands, nsq.Register(channel.topicName, channel.name))
				}
			}
			topic.RUnlock()
		}
		n.RUnlock()
        // 上报 topic 信息
		for _, cmd := range commands {
			n.logf(LOG_INFO, "LOOKUPD(%s): %s", lp, cmd)
			_, err := lp.Command(cmd)
			if err != nil {
				n.logf(LOG_ERROR, "LOOKUPD(%s): %s - %s", lp, cmd, err)
				return
			}
		}
	}
}

与消费者交互流程

消费者获取到Topic和Channel的NSQD



// nsqlookup  查询 topic 下的 
func (s *httpServer) doLookup(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) {
	reqParams, err := http_api.NewReqParams(req)
	if err != nil {
		return nil, http_api.Err{400, "INVALID_REQUEST"}
	}

	topicName, err := reqParams.Get("topic")
	if err != nil {
		return nil, http_api.Err{400, "MISSING_ARG_TOPIC"}
	}

	registration := s.nsqlookupd.DB.FindRegistrations("topic", topicName, "")
	if len(registration) == 0 {
		return nil, http_api.Err{404, "TOPIC_NOT_FOUND"}
	}

	channels := s.nsqlookupd.DB.FindRegistrations("channel", topicName, "*").SubKeys()
	producers := s.nsqlookupd.DB.FindProducers("topic", topicName, "")
    // 如果长时间没有更新,就认为这个节点有问题,不会把这个节点的信息加入到可用列表。
	producers = producers.FilterByActive(s.nsqlookupd.opts.InactiveProducerTimeout,
		s.nsqlookupd.opts.TombstoneLifetime)
	return map[string]interface{}{
		"channels":  channels,
		"producers": producers.PeerInfo(),
	}, nil
}

客户端和nsqlookupd、nsqd的通信实现

连接NSQLookup
func InitConsumer(topic string, channel string, address string) {
	cfg := nsq.NewConfig()
	cfg.LookupdPollInterval = time.Second          //设置重连时间
	c, err := nsq.NewConsumer(topic, channel, cfg) // 新建一个消费者, 消费 topic&channel 数据
	if err != nil {
		panic(err)
	}
	c.SetLogger(nil, 0)       //屏蔽系统日志
	c.AddHandler(&Consumer{}) // 添加消费者接口

	// 建立NSQLookup连接
	if err := c.ConnectToNSQLookupd(address); err != nil {
		panic(err)
	}

   // 	c.ConnectToNSQD() 也可以使用 ConnectToNSQD 直接链接 nsqd

}
链接 NSQD
  • queryLookupd 会查询到可用的 NSQD列表有哪一些,会跟这些NSQD建立链接
  • lookupdLoop 会定时更新可用的 NSQD列表
func (r *Consumer) ConnectToNSQLookupd(addr string) error {
    // ... 省略代码
	if numLookupd == 1 {
		r.queryLookupd()
		r.wg.Add(1)
		go r.lookupdLoop() // 其实就是在不定的定时调用 queryLookupd
	}

	return nil
}

// poll all known lookup servers every LookupdPollInterval
func (r *Consumer) lookupdLoop() {
    // ... 省略代码
	for {
		select {
		case <-ticker.C:
			r.queryLookupd()
		case <-r.lookupdRecheckChan:
			r.queryLookupd()
		case <-r.exitChan:
			goto exit
		}
	}
   // ... 省略代码
}

Topic 管理

Topic 创建

topic的创建可以从HTTP 接口和 TCP 接口创建,topic 数据存储在 RegistrationDB 中, Registration 是 topic ,channel,client 的信息,通过Category字段来区别,ProducerMap 是nsqd 的信息

type RegistrationDB struct {
	sync.RWMutex
	registrationMap map[Registration]ProducerMap
}

// HTTP 接口创建topic
func (s *httpServer) doCreateTopic(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) {
    // ... 省略代码
	key := Registration{"topic", topicName, ""}
	s.nsqlookupd.DB.AddRegistration(key) // 注册到 RegistrationDB 中
	return nil, nil
}

// TCP 接口创建topic
func (p *LookupProtocolV1) REGISTER(client *ClientV1, reader *bufio.Reader, params []string) ([]byte, error) {
	if client.peerInfo == nil {
		return nil, protocol.NewFatalClientErr(nil, "E_INVALID", "client must IDENTIFY")
	}

	topic, channel, err := getTopicChan("REGISTER", params)
	if err != nil {
		return nil, err
	}

	if channel != "" {
        // Category 是 channel
		key := Registration{"channel", topic, channel}
		if p.nsqlookupd.DB.AddProducer(key, &Producer{peerInfo: client.peerInfo}) {
			p.nsqlookupd.logf(LOG_INFO, "DB: client(%s) REGISTER category:%s key:%s subkey:%s",
				client, "channel", topic, channel)
		}
	}
     // Category 是 topic
	key := Registration{"topic", topic, ""}
	if p.nsqlookupd.DB.AddProducer(key, &Producer{peerInfo: client.peerInfo}) {
		p.nsqlookupd.logf(LOG_INFO, "DB: client(%s) REGISTER category:%s key:%s subkey:%s",
			client, "topic", topic, "")
	}

	return []byte("OK"), nil
}

NSQAdmin设计原理

NSQAdmin 是一套 WEB UI,用来汇集集群的实时统计,并执行不同的管理任务。

  • NSQAdmin 提供页面给用户查询信息,页面地址在:http://127.0.0.1:4171/
  • 当获取信息时,NSQAdmin会向NSQLookup 或者 NSQD 请求相关信息
  • NSQAdmin 就像是一个Proxy,替用户请求相关的数据,自己本身并不存储信息

参考

NSQ源码解析(2)-nsqlookup实现原理详解
剖析nsq消息队列(二) 去中心化源码解析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CoLiuRs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值