Nsq的简单入门|青训营笔记

236 阅读5分钟

Nsq的简单入门

介绍:

这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记

NSQ由三个基本组件组成:

  • nsqadmin:可视化的管理工具
  • nsqd:这是真正的队列所在的进程,如果想要使用nsqlookupd的话,需要在启动的时候传入参数
  • nsqlookupd:通过nsqlookupd可以注册和访问多个nsqd
  • go-nsq:nsq 官方的go语言客户端

了解了上述概念之后,再来继续普及一下,nsqd可以在多个机器上部署,consumer通过nsqlookupd来连接到多个nsqd进行消费, nsqadmin连上nsqlookupd进行管理。

nsqlookupd:

nsqlookupd是拓扑结构信息的管理者,有了他才能组成一个简单易用的无中心化的分布式拓扑网络结构。在终端1启动它:

$ nsqlookupd

nsqd:

nsqd是一个负责接收、排队、投递消息给客户端的守护进程。客户端(consumer)通过查询 nsqlookupd 来发现指定话题(topic)的nsqd生产者,nsqd节点会广播话题(topic)和通道(channel)信息。数据流模型如下:

img

单个nsqd可以有多个topic,每个topic可以有多个channel。channel接收这个topic所有消息的副本,从而实现多播分发,而channel上的每个消息被分发给它的订阅者,从而实现负载均衡。

在终端2启动nsqd:

 nsqd –lookupd-tcp-address=127.0.0.1:4160

下面介绍consumer 连接 nsqd 的两种连接方式:

直连方式:

nsqd是独立运行的,我们可以直接使用部署几个nsqd然后使用客户端直连的方式使用

image.png

也就是根据我们在终端开启nsqd的地址+端口直接去连接相应的nsqd,简单粗暴,但这样也会带来以下问题:

这种做的话,需要客户端做一些额外的工作,需要频繁的去检查所有nsqd的状态,如果发现出现问题需要客户端主动去处理这些问题。

总结

我使用的客户端库是官方库 go-nsq,使用直接连nsqd的方式,

  • 如果有nsqd出现问题,现在的处理方式,他会每隔一段时间执行一次重连操作。想去掉这个连接信息就要额外做一些处理了。
  • 如果对nsqd进行横向扩充,只能是自己民额外的写一些代码调用ConnectToNSQDs或者ConnectToNSQD方法

去中心化连接方式 nsqlookupd

官方推荐使用连接nsqlookupd的方式,nsqlookupd用于做服务的注册和发现,这样可以做到去中心化

image.png 也就是说这里我们直接连接的对象变了,我们连的是nsqlookupd,然后由nsqlookupd来给我们分配匹配上我们要的 topic 的nsql

去中心化实现原理

nsqlookupd用于管理整个网络拓扑结构,nsqd用他实现服务的注册,客户端使用他得到所有的nsqd服务节点信息,然后所有的consumer端连接 实现原理如下,

  • nsqd把自己的服务信息广播给一个或者多个nsqlookupd
  • 客户端连接一个或者多个nsqlookupd,通过nsqlookupd得到所有的nsqd的连接信息,进行连接消费,
  • 如果某个nsqd出现问题,down机了,会和nsqlookupd断开,这样客户端nsqlookupd得到的nsqd的列表永远是可用的。客户端连接的是所有的nsqd,一个出问题了就用其他的连接,所以也不会受影响。

nsqadmin:

nsqadmin –lookupd-http-address=127.0.0.1:4161

简单使用:

首先先介绍在Go 基础的生产者和消费者的建立。当然在使用之前你得按照上面的方式打开好 nsqlookupnsqd

生产者:

package main

import (
    "fmt"

    nsq "github.com/nsqio/go-nsq"
)

func main() {
    // 定义nsq生产者
    var producer *nsq.Producer
    // 初始化生产者
    // producer, err := nsq.NewProducer("地址:端口", nsq.*Config )
    producer, err := nsq.NewProducer("127.0.0.1:4150", nsq.NewConfig())
    if err != nil {
        panic(err)
    }

    err = producer.Ping()
    if nil != err {
        // 关闭生产者
        producer.Stop()
        producer = nil
    }

    // 生产者写入nsq,10条消息,topic = "test"
    topic := "test"
    for i := 0; i < 10; i++ {
        message := fmt.Sprintf("message:%d", i)
        if producer != nil && message != "" { //不能发布空串,否则会导致error
            err = producer.Publish(topic, []byte(message)) // 发布消息
            if err != nil {
                fmt.Printf("producer.Publish,err : %v", err)
            }
            fmt.Println(message)
        }
    }

    fmt.Println("producer.Publish success")

}

消费者:

//Nsq接收测试
package main

import (
    "fmt"
    "time"

    "github.com/nsqio/go-nsq"
)

// 消费者
type ConsumerT struct{}

// 主函数
func main() {
    InitConsumer("test", "test-channel", "127.0.0.1:4161")
    for {
        time.Sleep(time.Second * 10)
    }
}

//处理消息
func (*ConsumerT) HandleMessage(msg *nsq.Message) error {
    fmt.Println("receive", msg.NSQDAddress, "message:", string(msg.Body))
    return nil
}

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

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

    //建立多个nsqd连接
    // if err := c.ConnectToNSQDs([]string{"127.0.0.1:4150", "127.0.0.1:4152"}); err != nil {
    //  panic(err)
    // }

    // 建立一个nsqd连接
    // if err := c.ConnectToNSQD("127.0.0.1:4150"); err != nil {
    //  panic(err)
    // }
}

真实使用:

下面再看看真实场景的使用:

生产者:

先说简单点的生产者:

其实就和上面的生产者建立demo差不多,先在nsq中定义好创建producer的方法,

image.png 然后想好你到时要传递的内容,再者是topic,topic设置为传入的方式方便后面重复利用这个函数。

消费者:

其实也和上面的消费者demo差不多,但是和生产者一个函数可多次复用的情况不同的是,如果我们要接受多个不同topic的内容,那我们就要相应的创建多个不同的Consumer的结构体,并给不同的结构体绑定上他们的方法。

image.png

image.png

image.png

image.png

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

好了,到此为止,希望对你有帮助。

参考文章:

NSQ - 地鼠文档 (topgoer.cn)

使用NSQ(附Golang代码) - 掘金 (juejin.cn)

剖析nsq消息队列(一) 简介及去中心化实现原理 - li-peng - 博客园 (cnblogs.com)