简介
学习一个东西最好的方式就是直接上手撸一遍,让所有的资料为实践所用,这才是我认为的工科学习方法,让实践去检验真理。本篇从安装开始,到nsq实际的介绍和使用教程,会分篇幅讲述
nsq介绍
NSQ 是实时的分布式消息处理平台,其设计的目的是用来大规模地处理每天数以十亿计级别的消息。它具有分布式和去中心化拓扑结构,该结构具有无单点故障、故障容错、高可用性以及能够保证消息的可靠传递的特征
nsq服务架构
NSQ 是一个分布式实时消息队列系统,由 nsqlookupd、nsqd 和 nsqadmin 这三个核心服务组成
nsqd:最核心的消息队列后端模块,负责接收、存储和发送消息
nsqlookupd:nsq 中的服务发现与注册中心,负责管理 nsqd 节点、topic、channel 之间拓扑映射关系
nsqadmin:提供了用于实时监控 nsq 集群的 web 可视化界面
nsqd服务
用于创建topic、channel,分发消息给消费者,会向nsqlookup注册自己的元数据信息(topic、channel、consumer)。
特点:
- 保证了同一channel上的负载均衡
nsqlookupd服务
- 存储了nsqd的元数据和服务信息(endpoind)
- 向消费者提供服务发现功能
- 向nsqadmin提供数据查询功能.
nsqadmin服务
提供可视化的监控界面,可以查看nsqd、nsqlookupd、topic、channel、message等状态信息。
nsq基础概念
topic:消息的类别,生产者将消息发布到某个topic,消费者订阅该topic中的某个channel来接收消息
channel:消息的队列,每个channel对应一个消费者,消费者从channel中消费消息
producer:消息的生产者,负责将消息发布到指定的topic,一个生产者对应一个topic
consumer:消息的消费者,负责从channel中消费消息,一个消费者对应一个channel
Topic和Channel
Topic
topic概念
Topic是消息的分类,比如电商里,支付成功、创建订单这两个消息,会被分发到不同的topic中,用于区分不同类型的业务消息。
topic和其他组件的关系
一个topic可以对应多个channel,一个channel只能对应一个topic。
topic的元数据会被一个nsqd节点进行管理,并向nsqlookupd注册,nsqlookupd会向消费者提供服务发现功能
生产者负责将消息推送到topic,消费者通过订阅topic下的channel来消费消息
Channel
channel概念
channel是topic的细分,每个channel对应一个消费者,消费者从channel中消费消息。
它确保一个消息只能被一个消费者实例处理,从而实现负载均衡和消息可靠传递。
channel和其他组件的关系
channel属于某个特定的 Topic,消费者通过订阅 Topic 中的特定 Channel 来接收消息
NSQD 负责将 Topic 中的消息分发到各个 Channel,并确保消息在 Channel 上的有序性和可靠性
还是比如电商系统,支付成功这个消息,每个topic下会有多个channel,比如数据统计服务,会订阅一个channel; 其他相关的服务,也会根据他们自己的channel中消费消息。
负载均衡实现
分配策略
NSQ 采用了一种基于轮询的分配策略来实现负载均衡。当有新消息到达 Channel 时,NSQ 会按照顺序依次将消息分配给消费者组中的各个消费者实例。例如,有消费者实例 A、B、C 订阅了同一个 Channel,那么消息 1 会被分配给 A,消息 2 分配给 B,消息 3 分配给 C,消息 4 又会回到 A,以此类推。这样可以确保每个消费者实例都能均匀地接收消息,避免出现某个消费者实例负载过重的情况。
动态调整
NSQ 能够动态地感知消费者实例的状态。如果某个消费者实例出现故障或停止工作,NSQ 会自动将原本分配给该实例的消息重新分配给其他正常的消费者实例,从而保证系统的稳定性和可靠性。同时,当有新的消费者实例加入到消费者组中时,NSQ 也会自动调整分配策略,将一部分消息分配给新的实例,实现负载的动态均衡。
消息可靠传递保障
确认机制
:消费者在接收到消息并处理完成后,需要向 NSQ 发送一个确认(ACK)信号。只有当 NSQ 收到消费者的 ACK 后,才会认为该消息已经被成功处理,并将其从队列中删除。如果消费者在处理消息过程中出现故障或崩溃,没有及时发送 ACK,NSQ 会认为消息处理失败,并会在一段时间后将该消息重新发送给其他消费者实例进行处理,确保消息不会丢失。 重试机制
:对于处理失败的消息,NSQ 提供了重试机制。当消费者处理消息失败并返回错误信息时,NSQ 会根据预设的重试策略,将消息重新放入队列中等待再次被消费。可以设置重试的次数和间隔时间,以确保消息能够被成功处理。
实战
安装
首先我们需要安装一个nsq服务,我是MAC,运行命令
brew install nsq
运行nsq-demo
demo
go
package nsq
import (
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/nsqio/go-nsq"
)
func NSQDemo() {
// 生产者示例
go produceMessages()
// 消费者示例
go consumeMessages()
// 等待中断信号
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
<-c
log.Println("Shutting down...")
}
func produceMessages() {
config := nsq.NewConfig()
w, err := nsq.NewProducer("127.0.0.1:4150", config)
if err != nil {
log.Fatal(err)
}
defer w.Stop()
for i := 0; ; i++ {
message := []byte("Hello, NSQ! Message " + string(i))
err := w.Publish("test_topic", message)
if err != nil {
log.Println("Error publishing message:", err)
}
time.Sleep(1 * time.Second)
}
}
func consumeMessages() {
config := nsq.NewConfig()
q, err := nsq.NewConsumer("test_topic", "test_channel", config)
if err != nil {
log.Fatal(err)
}
q.AddHandler(nsq.HandlerFunc(func(message *nsq.Message) error {
log.Println("Received message:", string(message.Body))
return nil
}))
err = q.ConnectToNSQD("127.0.0.1:4150")
if err != nil {
log.Fatal(err)
}
}
运行
直接运行,会有报错:
yaml
2025/04/16 16:45:47 INF 1 (127.0.0.1:4150) connecting to nsqd
2025/04/16 16:45:47 INF 2 [test_topic/test_channel] (127.0.0.1:4150) connecting to nsqd
2025/04/16 16:45:47 ERR 1 (127.0.0.1:4150) error connecting to nsqd - dial tcp 127.0.0.1:4150: connect: connection refused
2025/04/16 16:45:47 Error publishing message: dial tcp 127.0.0.1:4150: connect: connection refused
2025/04/16 16:45:47 dial tcp 127.0.0.1:4150: connect: connection refused
这时候发现要起nsq的服务端口,需要启动nsqlookupd、nsqd、nsqadmin三个服务,我们才能使用
