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)信息。数据流模型如下:
单个nsqd可以有多个topic,每个topic可以有多个channel。channel接收这个topic所有消息的副本,从而实现多播分发,而channel上的每个消息被分发给它的订阅者,从而实现负载均衡。
在终端2启动nsqd:
nsqd –lookupd-tcp-address=127.0.0.1:4160
下面介绍consumer 连接 nsqd 的两种连接方式:
直连方式:
nsqd是独立运行的,我们可以直接使用部署几个nsqd然后使用客户端直连的方式使用
也就是根据我们在终端开启nsqd的地址+端口直接去连接相应的nsqd,简单粗暴,但这样也会带来以下问题:
这种做的话,需要客户端做一些额外的工作,需要频繁的去检查所有nsqd的状态,如果发现出现问题需要客户端主动去处理这些问题。
总结
我使用的客户端库是官方库 go-nsq,使用直接连nsqd的方式,
- 如果有
nsqd出现问题,现在的处理方式,他会每隔一段时间执行一次重连操作。想去掉这个连接信息就要额外做一些处理了。 - 如果对
nsqd进行横向扩充,只能是自己民额外的写一些代码调用ConnectToNSQDs或者ConnectToNSQD方法
去中心化连接方式 nsqlookupd
官方推荐使用连接nsqlookupd的方式,nsqlookupd用于做服务的注册和发现,这样可以做到去中心化
也就是说这里我们直接连接的对象变了,我们连的是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 基础的生产者和消费者的建立。当然在使用之前你得按照上面的方式打开好 nsqlookup 和 nsqd
生产者:
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的方法,
然后想好你到时要传递的内容,再者是topic,topic设置为传入的方式方便后面重复利用这个函数。
消费者:
其实也和上面的消费者demo差不多,但是和生产者一个函数可多次复用的情况不同的是,如果我们要接受多个不同topic的内容,那我们就要相应的创建多个不同的Consumer的结构体,并给不同的结构体绑定上他们的方法。
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)
}
}
好了,到此为止,希望对你有帮助。
参考文章: