测试环境macOS26 macOS 15
1 安装nsp:
brew install nsq
2 启动nsp&测试
2.1.在第一个shell中,启动nsqlookupd
nsqlookupd
2.2.在第二个shell中,启动nsqd
nsqd --lookupd-tcp-address=127.0.0.1:4160
2.3.在第三个shell中,启动nsqadmin
nsqadmin --lookupd-http-address=127.0.0.1:4161
2.4. 在第四个shell中,发布第一个消息(同时创建topic)
curl -d 'hello world 1' 'http://127.0.0.1:4151/pub?topic=test'
- 通过 HTTP API 发布消息(不推荐用于生产环境,HTTP 端口(4151)),生产环境应该直接使用TCP,4150端口
- 指定topic为test; Producer 只需要指定 Topic
2.5.在第五个shell中,使用nsq_to_file启动一个client来接收消息
nsq_to_file --topic=test --output-dir=/tmp --lookupd-http-address=127.0.0.1:4161
说明
- nsq_to_file 是 NSQ 工具集中的一个命令行工具,用于将指定 topic 的消息保存到本地文件。
- 参数说明
- --topic=test
- 订阅的 topic 名称
- 只接收 test 的消息
- --output-dir=/tmp
- 输出目录
- 消息会写入 /tmp 下的日志文件
- 文件名格式通常为 topic名.时间戳.log(例如 test.1234567890.log)
- --lookupd-http-address=127.0.0.1:4161
- NSQ Lookupd 的 HTTP 地址
- 127.0.0.1:4161 是本地默认端口
- 通过 Lookupd 发现可用的 nsqd 节点,而不是直连 nsqd
- --topic=test
- 工作原理
- 连接到 127.0.0.1:4161 的 nsqlookupd
- 查询 test topic 的 nsqd 节点
- 订阅并消费消息
- 将消息写入 /tmp 目录的日志文件
- 参数说明
2.6.在第四个shell中,向nsqd发布更多的消息
curl -d 'hello world 2' 'http://127.0.0.1:4151/pub?topic=test'
curl -d 'hello world 3' 'http://127.0.0.1:4151/pub?topic=test'
2.7 怎么验证我们的搭建NSQ实时分布式消息平台已经正常工作了?有两种方式:
第一种方式:在浏览器中输入网址http://127.0.0.1:4171,打开nsqadmin的UI界面,查看统计数据。
第二种方式:转到/tmp目录(nsq_to_file命令的--output-dir参数值),查看“名称符合test.*.log模式”的文件内容。
3 nsq默认端口解释
3.1 在nsq中4150 4151 4160 4161 端口,分别对应什么服务?
-
4150 端口 - nsqd 的 TCP 端口
- 用途:客户端(Producer 和 Consumer)与 nsqd 通信
- 客户端(Producer/Consumer)连接端口
-
4151 端口 - nsqd 的 HTTP 端口
- 用途:nsqd 的 HTTP API(REST API),不是可视化 Web 界面
- 功能:通过 HTTP 请求查看统计、创建 topic/channel、发布消息等
- 例如:http://localhost:4151/stats 查看统计信息
-
4160 端口 - nsqlookupd 的 TCP 端口
- 用途:nsqd 向 nsqlookupd 注册,客户端查询 topic 信息
-
4161 端口 - nsqlookupd 的 HTTP 端口
- 用途:nsqlookupd 的 HTTP API(REST API),不是可视化 Web 界面
- 功能:通过 HTTP 请求查询注册的 nsqd 节点、topic 信息等
- nsqadmin 通过此端口连接 nsqlookupd 获取集群信息
-
4171 端口 - nsqadmin 的 Web 管理界面
- 用途:可视化的 Web 管理界面
- 访问:http://localhost:4171
-
关键点: *Producer 只需要指定 Topic(如 topic_demo)
- Consumer 需要指定 Topic 和 Channel(如 topic_demo + channel_demo)
- 同一个 Topic 可以有多个 Channel,实现消息的多路分发
- 同一个 Channel 可以有多个 Consumer,实现负载均衡
NSQ 中的 Channel 概念
在 NSQ 中,Channel(通道) 是消费者(Consumer)订阅 Topic 时使用的概念。
基本关系
Topic(主题) └── Channel(通道) └── Consumer(消费者)
### 详细说明
- Topic(主题)
- 消息的分类/队列名称
- 生产者向 Topic 发布消息
- 你的代码中:topic_demo
- Channel(通道)
- 属于某个 Topic
- 多个 Channel 可以订阅同一个 Topic
- 每个 Channel 会收到该 Topic 的所有消息副本
- 用于实现不同的消费逻辑或不同的消费者组
- Consumer(消费者)
- 连接到某个 Channel
- 从 Channel 中消费消息
- 同一个 Channel 可以有多个消费者(负载均衡)
示例场景
假设你有一个 order Topic:
Topic: order
├── Channel: email_notification
│ └── Consumer: 发送邮件服务
├── Channel: sms_notification
│ └── Consumer: 发送短信服务
└── Channel: inventory_update
└── Consumer: 库存更新服务
- 一条订单消息发布到 order Topic
- 三个 Channel 都会收到这条消息
- 每个 Channel 的消费者独立处理
关键点: Producer 只需要指定 Topic(如 topic_demo) Consumer 需要指定 Topic 和 Channel(如 topic_demo + channel_demo) 同一个 Topic 可以有多个 Channel,实现消息的多路分发 同一个 Channel 可以有多个 Consumer,实现负载均衡
go 代码实现
生产者/发布者
package main
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/nsqio/go-nsq"
)
// NSQ Producer Demo
var producer *nsq.Producer
// 初始化生产者
func initProducer(str string) (err error) {
config := nsq.NewConfig()
producer, err = nsq.NewProducer(str, config)
if err != nil {
fmt.Printf("create producer failed, err:%v\n", err)
return err
}
return nil
}
func main() {
nsqAddress := "192.168.10.13:4150"
err := initProducer(nsqAddress)
if err != nil {
fmt.Printf("init producer failed, err:%v\n", err)
return
}
fmt.Printf("init producer success")
reader := bufio.NewReader(os.Stdin) // 从标准输入读取
for {
data, err := reader.ReadString('\n')
if err != nil {
fmt.Printf("read string from stdin failed, err:%v\n", err)
continue
}
data = strings.TrimSpace(data)
if strings.ToUpper(data) == "Q" { // 输入Q退出
break
}
// 向 'topic_demo' publish 数据
err = producer.Publish("topic_demo", []byte(data))
if err != nil {
fmt.Printf("publish msg to nsq failed, err:%v\n", err)
continue
}
}
}
消费者(使用lookupd查询)
// nsq_consumer/main.go
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
"github.com/nsqio/go-nsq"
)
// NSQ Consumer Demo
// 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: "收到了消息:",
}
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_demo", "first", "192.168.10.13: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 // 阻塞
}
为什么生产者发布使用TCP端口(4150),而消费者使用HTTP端口(4161)?
关键区别
-
生产者(Producer)- 直接连接 nsqd
nsqAddress := "192.168.10.13:4150" err := initProducer(nsqAddress)
producer, err = nsq.NewProducer(str, config)
生产者直接连接到 nsqd 的 TCP 端口(4150) 不需要通过 lookupd,因为生产者通常已知 nsqd 地址 使用 TCP 协议发布消息,性能更高
-
消费者(Consumer)- 通过 lookupd 发现服务
if err := c.ConnectToNSQLookupd(address); err != nil { // 通过lookupd查询
err := initConsumer("topic_demo", "first", "192.168.10.13:4161")
消费者连接到 nsqlookupd 的 HTTP 端口(4161) 通过 lookupd 查询可用的 nsqd 节点 查询到 nsqd 地址后,消费者会通过 TCP 连接到 nsqd 消费消息
为什么这样设计?
生产者使用 TCP(直接连接 nsqd):
- 性能:TCP 二进制协议,开销更小
- 简单:生产者通常知道 nsqd 地址,无需服务发现
- 实时:直接连接,延迟更低
消费者使用 HTTP(通过 lookupd):
- 服务发现:需要动态发现可用的 nsqd 节点
- 高可用:lookupd 维护多个 nsqd 节点列表,支持故障转移
- 负载均衡:可以从多个 nsqd 节点消费消息
完整流程
生产者:
Producer → TCP(4150) → nsqd (直接发布消息)
消费者:
Consumer → HTTP(4161) → nsqlookupd (查询可用节点)
↓
Consumer → TCP(4150) → nsqd (实际消费消息)
总结
-
生产者:直接连接 nsqd(TCP 4150),发布消息
-
消费者:先通过 lookupd(HTTP 4161)发现 nsqd,再通过 TCP 连接 nsqd 消费消息
-
消费者最终也是通过 TCP 连接 nsqd 消费消息,只是先通过 lookupd 找到 nsqd 的地址。这是 NSQ 的分布式架构设计:生产者直接连接,消费者通过服务发现动态连接。
消费者2(直接连NSQD查询)
// nsq_consumer/main.go
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"github.com/nsqio/go-nsq"
)
// NSQ Consumer Demo
// 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 // 直接连接NSQD时不需要此配置
c, err := nsq.NewConsumer(topic, channel, config)
if err != nil {
fmt.Printf("create consumer failed, err:%v\n", err)
return
}
consumer := &MyHandler{
Title: "收到了消息:",
}
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() {
// 使用ConnectToNSQLookupd 端口是 4161; 使用 ConnectToNSQD端口是 4150
err := initConsumer("topic_demo", "first", "192.168.10.13:4150")
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 // 阻塞
}
两种连接方式的区别
| 方式 | 连接地址 | 端口 | 适用场景 |
|---|---|---|---|
| 直接连接 | nsqd 地址 | 4150 (TCP) | 单节点、已知 nsqd 地址 |
| 通过 lookupd | lookupd 地址 | 4161 (HTTP) | 多节点、需要服务发现、高可用 |
现在消费者会直接连接到 nsqd 的 TCP 端口 4150,不再通过 lookupd 进行服务发现。注意:直接连接方式适用于单节点或已知 nsqd 地址的场景。如果 nsqd 节点故障,消费者无法自动切换到其他节点。
使用 ConnectToNSQD端口为什么是4150
NSQ 端口说明:
| 服务 | TCP 端口 | HTTP 端口 | 用途 |
|---|---|---|---|
| nsqd | 4150 | 4151 | 消息队列节点(实际存储和传输消息) |
| nsqlookupd | 4160 | 4161 | 服务发现(管理 nsqd 节点列表) |
ConnectToNSQD() 需要连接到 nsqd 节点,而不是 lookupd
nsqd 的 TCP 端口是 4150,用于消息传输
如果使用 4160(lookupd 的 TCP 端口),连接会失败,因为: ConnectToNSQD() 期望连接到 nsqd 协议
lookupd 使用不同的协议和端口
注意:
- 消费者无法使用 NSQLookupd 的 4160 TCP 端口,
- ConnectToNSQLookupd 方法只支持 HTTP 协议,必须使用 HTTP 端口(4161)
- 4160 是 TCP 端口,用于 NSQD 注册,不是给客户端查询用的
参考文档: www.cnblogs.com/you-men/p/1… blog.csdn.net/chinawangfe… blog.csdn.net/u012189747/… cloud.tencent.com/developer/a… zhuanlan.zhihu.com/p/115368450