nsq学习记录

前言

学习 https://zhuanlan.zhihu.com/p/665893174

✅ go-nsq 库的优点

  • 官方维护、Go 原生风格:API 简洁符合 Go 习惯(HandlerFunc / Producer / Consumer),学习成本极低
  • 充分利用 goroutine + channel:高并发消息处理,单机可支撑高吞吐
  • 自动服务发现:通过 nsqlookupd 自动发现 nsqd 节点,支持动态扩缩容
  • 完善连接管理:内置重连、心跳、背压控制(MaxInFlight)、消息 ACK / Requeue / 超时控制
  • 配置丰富:支持 TLS、压缩、认证、异步发布(PublishAsync)、消费重试与退避

⚠️ go-nsq / NSQ 的局限与注意点

类别 说明
消息不保证有序 NSQ 无 partition,同一 channel 内也不保序
At-Least-Once 投递 可能重复投递,业务需自己做幂等
无事务 / 无回溯 不像 Kafka 可按 offset 回溯历史消息,无分布式事务
默认内存优先 消息主要在内存,重启可能丢(需配 --mem-queue-size=0 才强制落盘)
消息体大小限制 默认单条 ≤1MB,超大会被拒绝
无原生消费者组协调 多实例消费靠 nsqlookupd 发现 + channel 名相同实现,无 Kafka 那种 group coordinator
  • 使用注意:Handler 里不要随意起 goroutine 且不控制并发,需正确设置 MaxInFlight、调用 Stop() + WaitStopped() 防消息丢失或 goroutine 泄漏
  • NSQ 本身运维局限:无原生 K8s Operator,topic 创建需调 HTTP API;nsqlookupd 挂掉不影响已连消费者但影响新节点发现

✅ 适合 & 不适合场景

适合:

• Go 微服务异步解耦、任务队列、日志聚合、实时事件推送

• 中小规模、追求轻量部署和运维低成本

不适合:

  • 严格顺序消费(库存扣减、金融交易)
  • 消息需精确一次投递 / 事务消息
  • 超大规模流式处理(日千亿级,建议 Kafka/Pulsar)

go-nsq 是 Go 生态里最顺手的轻量消息队列客户端,配合 NSQ 适合高吞吐、低延迟、最终一致性场景;如果需要严格有序、exactly-once、消息回溯,应选 Kafka / RocketMQ / Pulsar。

如果选择go-nsq, 运行代码前需要用docker跑起来一个镜像

bash 复制代码
docker run -d --name nsqd -p 4150:4150 -p 4151:4151 nsqio/nsq /nsqd

demo

go 复制代码
package main

import (
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/nsqio/go-nsq"
)

const (
	// 用于测试的消息主题
	testTopic = "my_test_topic"
	// nsqd 服务端地址
	nsqdAddr = "localhost:4150"
	// 订阅 channelGroupA 的消费者 A1
	consumerA1 = "consumer_a1"
	// 订阅 channelGroupA 的消费者 A2
	consumerA2 = "consumer_a2"
	// 单独订阅 channelGroupB 的消费者 B
	consumerB = "consumer_b"
	// testTopic 下的 channelA
	channelGroupA = "channel_a"
	// testTopic 下的 channelB
	channelGroupB = "channel_b"
)

// 建立 consumer 与 channel 之间的映射关系
// consumerA1、consumerA2 -> channelA
// consumerB -> channelB
var consumerToChannelGroup = map[string]string{
	consumerA1: channelGroupA,
	consumerA2: channelGroupA,
	consumerB:  channelGroupB,
}

func runNsq() {
	msgCallback := func(consumerName string, msg []byte) error {
		fmt.Printf("i am %s, receive msg: %s\n", consumerName, msg)
		return nil
	}

	if err := runProducer(); err != nil {
		log.Fatalf("producer: %v", err)
	}

	var wg sync.WaitGroup
	for consumer := range consumerToChannelGroup {
		wg.Add(1)
		go func(consumer string) {
			defer wg.Done()
			if err := runConsumer(consumer, msgCallback); err != nil {
				log.Printf("consumer %s: %v", consumer, err)
			}
		}(consumer)
	}
	wg.Wait()
}

// 运行生产者
func runProducer() error {
	// 通过 addr 直连 nsqd 服务端
	producer, err := nsq.NewProducer(nsqdAddr, nsq.NewConfig())
	if err != nil {
		return err
	}
	defer producer.Stop()

	// 通过 producer.Publish 方法,往 testTopic 中发送三条消息
	for i := 0; i < 3; i++ {
		msg := fmt.Sprintf("hello xiaoxu %d", i)
		if err := producer.Publish(testTopic, []byte(msg)); err != nil {
			return err
		}
	}
	return nil
}

// 用于处理消息的 processor,需要实现 go-nsq 中定义的 msgProcessor interface,核心是实现消息回调处理方法: func HandleMessage(msg *nsq.Message) error
type msgProcessor struct {
	// 消费者名称
	consumerName string
	// 消息回调处理函数
	callback func(consumerName string, msg []byte) error
}

func newMsgProcessor(consumerName string, callback func(consumerName string, msg []byte) error) *msgProcessor {
	return &msgProcessor{
		consumerName: consumerName,
		callback:     callback,
	}
}

// 消息回调处理
func (m *msgProcessor) HandleMessage(msg *nsq.Message) error {
	// 执行用户定义的业务处理逻辑
	if err := m.callback(m.consumerName, msg.Body); err != nil {
		return err
	}
	// 倘若业务处理成功,则调用 Finish 方法,发送消息的 ack
	msg.Finish()
	return nil
}

// 运行消费者
func runConsumer(consumerName string, callback func(consumerName string, msg []byte) error) error {
	// 根据消费者名获取到对应的 channel
	channel, ok := consumerToChannelGroup[consumerName]
	if !ok {
		return fmt.Errorf("bad name: %s", consumerName)
	}

	// 指定 topic 和 channel,创建 consumer 实例
	consumer, err := nsq.NewConsumer(testTopic, channel, nsq.NewConfig())
	if err != nil {
		return err
	}
	defer consumer.Stop()

	// 添加消息回调处理函数
	consumer.AddHandler(newMsgProcessor(consumerName, callback))

	// consumer 连接到 nsqd 服务端,开启消费流程
	if err = consumer.ConnectToNSQD(nsqdAddr); err != nil {
		return err
	}

	<-time.After(5 * time.Second)
	return nil
}

func main() {
	runNsq()
}
bash 复制代码
andydennis@AndydeMacBook-Pro goDraft % go run main.go                                                     
2026/06/01 14:29:48 INF    1 (localhost:4150) connecting to nsqd
2026/06/01 14:29:48 INF    1 (localhost:4150) stopping
2026/06/01 14:29:48 INF    1 (localhost:4150) exiting router
2026/06/01 14:29:48 INF    1 (localhost:4150) beginning close
2026/06/01 14:29:48 INF    1 (localhost:4150) readLoop exiting
2026/06/01 14:29:48 INF    1 (localhost:4150) breaking out of writeLoop
2026/06/01 14:29:48 INF    1 (localhost:4150) writeLoop exiting
2026/06/01 14:29:48 INF    4 [my_test_topic/channel_b] (localhost:4150) connecting to nsqd
2026/06/01 14:29:48 INF    2 [my_test_topic/channel_a] (localhost:4150) connecting to nsqd
2026/06/01 14:29:48 INF    3 [my_test_topic/channel_a] (localhost:4150) connecting to nsqd
i am consumer_b, receive msg: hello xiaoxu 0
i am consumer_a1, receive msg: hello xiaoxu 0
i am consumer_a2, receive msg: hello xiaoxu 1
i am consumer_a2, receive msg: hello xiaoxu 2
i am consumer_b, receive msg: hello xiaoxu 1
i am consumer_b, receive msg: hello xiaoxu 2
2026/06/01 14:29:48 INF    1 (localhost:4150) finished draining, cleanup exiting
2026/06/01 14:29:48 INF    1 (localhost:4150) clean close complete
2026/06/01 14:29:53 INF    2 [my_test_topic/channel_a] stopping...
2026/06/01 14:29:53 INF    3 [my_test_topic/channel_a] stopping...
2026/06/01 14:29:53 INF    4 [my_test_topic/channel_b] stopping...
相关推荐
韦胖漫谈IT3 小时前
选语言不是站队,是选适合问题的工具
java·python·ai·rust·go·技术落地
喵个咪14 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
夜悊19 小时前
Go网络编程的学习代码示例:客户端/服务端(C/S)模型
go
审判长烧鸡1 天前
【AI问答】GO代码循环返值
go
捧 花1 天前
Eino框架记忆功能实现指南
go·agent·eino
Java陈序员1 天前
主流数据库通吃!一款开源实用的数据库备份管理工具!
react.js·postgresql·go
云浪1 天前
搞懂 Go WaitGroup:一篇文章彻底理解并发等待机制
后端·go
喵个咪2 天前
选择第三方IAM还是自建权限体系?中小型后台系统权限架构决策指南
后端·架构·go
喵个咪2 天前
AI重构软件开发范式:框架与脚手架为何仍是生产级开发的刚需?
架构·go·ai编程