Go-NSQ消息队列
简介
-
用Go编写,开源的内存分布式消息队列中间件。
-
开源大规模地处理每天数以十亿计级别的消息。
-
分布式和去中心化拓扑结构、无单点故障,这样集群中任意一台机器挂了不会影响集群稳定性。
https://github.com/nsqio/nsq
应用场景
- 异步处理,把非关键流程异步化,提高系统的响应时间和健壮性。
- 用户注册 <--------> 注册信息写入数据库(50ms) ----> 写入队列5ms <--异步读取--- 发送注册邮件短信(50ms),如果不使用异步处理,整个请求的时间为100ms,使用异步则为55ms。
- 应用解耦,通过消息队列。
- 订单系统 ------> 消息队列 -------> 库存系统
- 流量削峰
- 抢购系统:用户请求 --------- > 消息队列 <--------秒杀业务处理
NSQ基本架构
Nsq组件
-
NSQD
- 负责消息接收、保存以及发送消息给消费者的进程,可以同时部署多个nsqd进程,这样消息就能分布在多台服务器上。
-
NSQLookupd
- 负责维护所有NSQD状态,提供服务发现的进程。也就是说每起一个NSQD服务则会把ip跟端口注册到nsqlookupd中。
-
NSQAdmin
-
web管理平台,实时监控集群以及执行各种管理任务。
-
NSQ架构图解
Topic:对应一个具体的队列。
Channel:每个消费者对应一个Channel,实现消息可重复消费。
NSQ接收和发送消息流程
-
消息默认不支持持久化,可配置成持久化。
-
每条消息至少传递一次
-
消息不保证有序。
由于NSQ消息都是存储在内存中,当内存不足时,会由一个Goroutine存入硬盘,当NSQ内存配置为0时,会全部存储到硬盘中,同时也实现了数据持久化。
部署实例
生产者将消息放入所有的Channel中,也就是每个Channel都能收到这条消息(这样能保证每个系统都能收到消息),然后metrics这个管道对应的有ABC三个消费者,这时候假设A服务器性能比较好,那么会不会一直是A服务器在小菲数据,而BC一直空闲着,NSQD消费规则轮流消费。
NSQ搭建
具体安装启动步骤可以参考官网文档
Windows下解压后可以直接点击开启NsqLookUpd服务
nsqadmin.exe --lookupd-http-address 127.0.0.1:4161 // 启动nsqandmin,绑定lookupd nsqd.exe --lookupd-tcp-address 127.0.0.1:4160 // 启动nsqd 绑定lookupd
接下来就能通过nsqadmin可视化管理nsq了,amdin默认端口:4171
NSQ使用
go get github.com/nsqio/go-nsq
通过NSQ实现生产者消费者模型

package main import ( "bufio" "fmt" "os" "strings" "github.com/nsqio/go-nsq" ) // 生产者 var producer *nsq.Producer // 初始化生产者 func initProducer(str string) error { var err error config := nsq.NewConfig() producer, err = nsq.NewProducer(str, config) if err != nil { return err } return nil } func main() { // nsq 的地址 nsqAddress := "127.0.0.1:4150" err := initProducer(nsqAddress) if err != nil { fmt.Printf("initProducer failed err:%v\n", err) } // 读取控制台输入,放入队列 reader := bufio.NewReader(os.Stdin) for { data, err := reader.ReadString('\n') if err != nil { fmt.Printf("read string failed err:%v\n", err) continue } data = strings.TrimSpace(data) // 去掉空格 if data == "stop" { break } // 放入消息队列中 err = producer.Publish("login_queue", []byte(data)) if err != nil { fmt.Printf("publish messge failed err:%v\n", err) } fmt.Printf("publish data:%s ok\n", data) } }

package main import ( "fmt" "os" "os/signal" "syscall" "time" "github.com/nsqio/go-nsq" ) type Consumer struct { } func (*Consumer) HandleMessage(msg *nsq.Message) error { fmt.Println("receive: ", msg.NSQDAddress, "message:", string(msg.Body)) return nil } func initConsumer(topic string, channel string, address string) error { config := nsq.NewConfig() // 设置服务发现的轮询时间 config.LookupdPollInterval = 15 * time.Second // 新建一个消费者 conn, err := nsq.NewConsumer(topic, channel, config) if err != nil { return err } consumer := &Consumer{} // 添加消费者接口 conn.AddHandler(consumer) // 建立nsqlookupd连接 if err := conn.ConnectToNSQLookupd(address); err != nil { return err } return nil } func main() { // 监听nsqlookupd服务 err := initConsumer("login_queue", "first", "127.0.0.1:4161") if err != nil { fmt.Printf("init consumer failed err:%v\n", err) return } // 声明信道 conn := make(chan os.Signal) signal.Notify(conn, syscall.SIGINT) <-conn }