Go 连接消息队列Nsq | 青训营

146 阅读5分钟

消息队列(Message Queue,简称MQ),指保存消息的一个容器,本质是个队列。许多人认为 MQ 通过消息的发送和接受来实现程序的异步和解耦,mq主要用于异步操作,这个不是 MQ 的真正目的,只不过是 MQ 的应用,MQ 真正的目的是为了通讯。

一、NSQ介绍

NSQ是Go语言编写的一个开源的实时分布式内存消息队列,其性能十分优异。 NSQ有以下优势:

  1. NSQ提倡分布式和分散的拓扑,没有单点故障,支持容错和高可用性,并提供可靠的消息交付保证
  2. NSQ支持横向扩展,没有任何集中式代理。
  3. NSQ易于配置和部署,并且内置了管理界面。

特点:

  • 追求简单部署
  • 追求高可用、避免单点故障、无中心设计
  • 确保消息送达
  • 生产者消费者自动发现、消费者连接所有生产者、向消费者推的模式
  • 提供 HTTP 接口
  • 提供几乎所有编程语言的客户端开发包

NSQ的应用场景:

  1. 异步处理:显著降低业务请求的响应时间。
  2. 应用解耦:通过使用消息队列将不同的业务逻辑解耦,降低系统间的耦合,提高系统的健壮性。
  3. 流量削峰:类似秒杀(大秒)等场景下,某一时间可能会产生大量的请求,使用消息队列能够为后端处理请求提供一定的缓冲区,保证后端服务的稳定性。

二、NSQ安装(win端)

1、进入官网下载地址获取nsq压缩包,选择最新版本即可,我这里选择的是1.2.1版本

DmrLJ8pyzV.png

2、解压压缩包,打开里面 bin 目录,在cmd里面执行 nsqlookupd 来启动nsqlookupd

  • 监听TCP 4160端口,用于管理nsqd服务;
  • 监听HTTP 4161端口,用于给nsqadmin提供服务。

4ifqYWgpVz.png

3、启动nsqd,再启动一个cmd窗口,需指定要连接的nsqlookupd的地址和端口,即上述启动的nsqlookupd,地址为127.0.0.1

nsqd --lookupd-tcp-address=127.0.0.1:4160

nsqd连接成功nsqlookupd后,会初始化topic、channal的元数据,用以获取nsqlookupd信息。

jzz5TXaEch.png

4、启动nsqadmin,新建cmd窗口,输入下面命令:

nsqadmin --lookupd-http-address=127.0.0.1:4161

访问地址:http://127.0.0.1:4171/

sEtVucIS4R.png

三、使用Go连接Nsq

1、安装官方驱动,官方文档地址

go get -u github.com/nsqio/go-nsq

2、生产者代码

在下面代码中,我们可以定义一个全局变量producer,用于存储NSQ的生产者实例,我的NSQ的地址为127.0.0.1:4150,如果不同,可以根据实际情况来修改,接下来,我们通过bufio.NewReader(os.Stdin)来创建一个读取标准输入的reader对象,然后进入循环中,在每次循环的过程中,我们使用reader.ReadString('\n')从标准输入读取一行数据;接下来我们需要对读取的数据进行处理,首先使用strings.TrimSpace(data)去除字符串两端的空格,在循环的过程中,如果读取的数据是"QUIT"(不区分大小写),则中断循环并退出程序,否则,将读取到的数据通过producer.Publish("topic", []byte(data))发送到名为"topic"的NSQ主题。

package main
​
import (
   "bufio"
   "fmt"
   "github.com/nsqio/go-nsq"
   "os"
   "strings"
)
​
var producer *nsq.Producer
​
// 初始化生产者
func initProducer(str string) (err error) {
   config := nsq.NewConfig()
   producer, err = nsq.NewProducer(str, config)
   if err != nil {
      fmt.Printf("[ERROR] create producer failed, err:%v\n", err)
      return err
   }
   return nil
}
​
func main() {
   nsqAddress := "127.0.0.1:4150"
   err := initProducer(nsqAddress)
   if err != nil {
      fmt.Printf("[ERROR] init producer failed, err:%v\n", err)
      return
   }
   // 从标准输入读取
   reader := bufio.NewReader(os.Stdin)
   for {
      data, err := reader.ReadString('\n')
      if err != nil {
         continue
      }
      // 去除空
      data = strings.TrimSpace(data)
      // 输入quit
      if strings.ToUpper(data) == "QUIT" {
         break
      }
      // 向 'topic' publish 数据
      err = producer.Publish("topic", []byte(data))
      if err != nil {
         continue
      }
   }
}

运行代码,然后根据代码提示输入两条数据123和456

iZazg9l756.png

接下来我们打开web管理地址:http://127.0.0.1:4171/ 来查看当前的topic信息

gwmSLgbQpG.png

在/nodes这个页面我们能够看见当前接入lookupd的nsqd节点

f5Ux3vrnZ0.png

在/counte这个r页面能看见当前处理的消息数量,因为之前运行过代码,所以现在的处理的消息数量为2

gskQJlgc11.png

3、消费者代码

首先我们定义了一个结构体类型MyHandler作为消费者的处理类型。有一个字段Title表示消费者的标题。

HandleMessage(msg *nsq.Message)方法是需要实现的处理消息的方法。当消费者接收到消息时,会调用该方法进行处理。在这个例子中,它简单地将消息的内容打印出来。

initConsumer(topic string, channel string, address string)函数用于初始化NSQ消费者。它接受三个参数:主题(topic)、通道(channel)和地址(address)。

initConsumer()函数中,首先创建了一个NSQ配置对象config。然后使用提供的主题、通道和配置创建了一个新的NSQ消费者实例c

创建了一个MyHandler类型的消费者实例consumer,并通过c.AddHandler(consumer)将其注册到消费者上。

使用c.ConnectToNSQLookupd(address)方法与NSQ的Lookupd服务建立连接,并监听来自指定主题的消息。

package main
​
import (
   "fmt"
   "github.com/nsqio/go-nsq"
   "os"
   "os/signal"
   "syscall"
   "time"
)
​
// MyHandler 是一个消费者类型
type MyHandler struct {
   Title string
}
​
// HandleMessage 是需要实现的处理消息的方法
func (m *MyHandler) HandleMessage(msg *nsq.Message) (err error) {
   fmt.Printf("%s recv from %v, msg:%v\n", m.Title, msg.NSQDAddress, string(msg.Body))
   return
}
​
// 初始化消费者
func initConsumer(topic string, channel string, address string) (err error) {
   config := nsq.NewConfig()
   config.LookupdPollInterval = 15 * time.Second
   c, err := nsq.NewConsumer(topic, channel, config)
   if err != nil {
      fmt.Printf("create consumer failed, err:%v\n", err)
      return
   }
   consumer := &MyHandler{
      Title: "test测试",
   }
   c.AddHandler(consumer)
​
   // if err := c.ConnectToNSQD(address); err != nil { // 直接连NSQD
   if err := c.ConnectToNSQLookupd(address); err != nil { // 通过lookupd查询
      return err
   }
   return nil
​
}
​
func main() {
   err := initConsumer("topic", "first", "127.0.0.1:4161")
   if err != nil {
      fmt.Printf("init consumer failed, err:%v\n", err)
      return
   }
   c := make(chan os.Signal)        // 定义一个信号的通道
   signal.Notify(c, syscall.SIGINT) // 转发键盘中断信号到c
   <-c                              // 阻塞
}

运行上面代码,就能够获取之前publish的两条消息了

3FvlHDJZTX.png