消息队列(Message Queue,简称MQ),指保存消息的一个容器,本质是个队列。许多人认为 MQ 通过消息的发送和接受来实现程序的异步和解耦,mq主要用于异步操作,这个不是 MQ 的真正目的,只不过是 MQ 的应用,MQ 真正的目的是为了通讯。
一、NSQ介绍
NSQ是Go语言编写的一个开源的实时分布式内存消息队列,其性能十分优异。 NSQ有以下优势:
- NSQ提倡分布式和分散的拓扑,没有单点故障,支持容错和高可用性,并提供可靠的消息交付保证
- NSQ支持横向扩展,没有任何集中式代理。
- NSQ易于配置和部署,并且内置了管理界面。
特点:
- 追求简单部署
- 追求高可用、避免单点故障、无中心设计
- 确保消息送达
- 生产者消费者自动发现、消费者连接所有生产者、向消费者推的模式
- 提供 HTTP 接口
- 提供几乎所有编程语言的客户端开发包
NSQ的应用场景:
- 异步处理:显著降低业务请求的响应时间。
- 应用解耦:通过使用消息队列将不同的业务逻辑解耦,降低系统间的耦合,提高系统的健壮性。
- 流量削峰:类似秒杀(大秒)等场景下,某一时间可能会产生大量的请求,使用消息队列能够为后端处理请求提供一定的缓冲区,保证后端服务的稳定性。
二、NSQ安装(win端)
1、进入官网下载地址获取nsq压缩包,选择最新版本即可,我这里选择的是1.2.1版本
2、解压压缩包,打开里面 bin 目录,在cmd里面执行 nsqlookupd 来启动nsqlookupd
- 监听TCP 4160端口,用于管理nsqd服务;
- 监听HTTP 4161端口,用于给nsqadmin提供服务。
3、启动nsqd,再启动一个cmd窗口,需指定要连接的nsqlookupd的地址和端口,即上述启动的nsqlookupd,地址为127.0.0.1
nsqd --lookupd-tcp-address=127.0.0.1:4160
nsqd连接成功nsqlookupd后,会初始化topic、channal的元数据,用以获取nsqlookupd信息。
4、启动nsqadmin,新建cmd窗口,输入下面命令:
nsqadmin --lookupd-http-address=127.0.0.1:4161
三、使用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
接下来我们打开web管理地址:http://127.0.0.1:4171/ 来查看当前的topic信息
在/nodes这个页面我们能够看见当前接入lookupd的nsqd节点
在/counte这个r页面能看见当前处理的消息数量,因为之前运行过代码,所以现在的处理的消息数量为2
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的两条消息了