关于nsq的功能和特性
NSQ 是一个基于 Go 语言的分布式实时消息平台,它基于 MIT 开源协议发布,代码托管在 GitHub,其当前最新版本是 0.3.1 版。NSQ 可用于大规模系统中的实时消息服务,并且每天能够处理数亿级别的消息,其设计目标是为在分布式环境下运行的去中心化服务提供一个强大的基础架构。NSQ 具有分布式、去中心化的拓扑结构,该结构具有无单点故障、故障容错、高可用性以及能够保证消息的可靠传递的特征。NSQ 非常容易配置和部署,且具有最大的灵活性,支持众多消息协议。另外,官方还提供了拆箱即用 Go 和 Python 库。如果读者兴趣构建自己的客户端的话,还可以参考官方提供的协议规范。
NSQ 是由四个重要组件构成:
- nsqd :一个负责接收、排队、转发消息到客户端的守护进程
- nsqlookupd :管理拓扑信息并提供最终一致性的发现服务的守护进程
- nsqadmin :一套 Web 用户界面,可实时查看集群的统计数据和执行各种各样的管理任务
- utilities :常见基础功能、数据流处理工具,如 nsq_stat、nsq_tail、nsq_to_file、nsq_to_http、nsq_to_nsq、to_nsq
NSQ 的主要特点如下:
- 具有分布式且无单点故障的拓扑结构 支持水平扩展,在无中断情况下能够无缝地添加集群节点
- 低延迟的消息推送,参见官方提供的性能说明文档
- 具有组合式的负载均衡和多播形式的消息路由
- 既擅长处理面向流(高吞吐量)的工作负载,也擅长处理面向 Job 的(低吞吐量)工作负载
- 消息数据既可以存储于内存中,也可以存储在磁盘中
- 实现了生产者、消费者自动发现和消费者自动连接生产者,参见 nsqlookupd
- 支持安全传输层协议(TLS),从而确保了消息传递的安全性
- 具有与数据格式无关的消息结构,支持 JSON、Protocol Buffers、MsgPacek 等消息格式
- 非常易于部署(几乎没有依赖)和配置(所有参数都可以通过命令行进行配置)
- 使用了简单的 TCP 协议且具有多种语言的客户端功能库
- 具有用于信息统计、管理员操作和实现生产者等的 HTTP 接口
- 为实时检测集成了统计数据收集器 StatsD
- 具有强大的集群管理界面,参见 nsqadmin
为了达到高效的分布式消息服务,NSQ 实现了合理、智能的权衡,从而使得其能够完全适用于生产环境中,具体内容如下:
- 支持消息内存队列的大小设置,默认完全持久化(值为 0),消息即可持久到磁盘也可以保存在内存中
- 保证消息至少传递一次, 以确保消息可以最终成功发送
- 收到的消息是无序的, 实现了松散订购
- 发现服务 nsqlookupd 具有最终一致性, 消息最终能够找到所有 Topic 生产者
官方和第三方还为 NSQ 开发了众多客户端功能库,如官方提供的基于 HTTP 的 nsqd 、Go 客户端 go-nsq 、Python 客户端 pynsq 、基于 Node.js 的 JavaScript 客户端 nsqjs 、异步 C 客户端 libnsq 、Java 客户端 nsq-java 以及基于各种语言的众多第三方客户端功能库。更多客户端功能库, 请读者点击这里查看。
从 NSQ 的设计文档中得知,单个nsqd 被设计为一次能够处理多个流数据,NSQ 中的数据流模型是由stream 和consumer 组成。Topic 是一种独特的stream,Channel 是一个订阅了给定Topic 的consumer 逻辑分组。NSQ 的数据流模型结构如下图所示:
从上图可以看出,单个 nsqd 可以有多个 Topic,每个 Topic 又可以有多个 Channel。Channel 能够接收 Topic 所有消息的副本,从而实现了消息多播分发;而 Channel 上的每个消息被分发给它的订阅者,从而实现负载均衡,所有这些就组成了一个可以表示各种简单和复杂拓扑结构的强大框架。
NSQ 最初为提供短链接服务的应用 Bitly 而开发。另外,还有众多著名的应用在使用 NSQ,如社交新闻网站 Digg 、私密的社交应用 Path 、著名的开源的应用容器引擎 Docker 、支付公司 Stripe 、新闻聚合网站 Buzzfeed 、查看家人所在位置的移动应用 Life360 、网络工具公司 SimpleReach 等。
NSQ 的安装部署在另一篇文章中
对nsq完全没有了解的同学,可以读一下nsq官方文档,英文阅读能力有限的同学可以阅读翻译后的版本。但是翻译后的quick_start(快速开始)部分的代码有点问题。
$ curl -d 'hello world 1' 'http://127.0.0.1:4151/put?topic=test'
应该改成
$ curl -d 'hello world 1' 'http://127.0.0.1:4151/pub?topic=test'
也就是pub写成了put。大家拿来练习的时候要注意。
一、nsq安装
mac的同学直接
$ brew install nsq
linux请到nsq下载目录中下载,并解压到自己的工作路径中。如:
~/nsq_1.1.0
下面需要运行nsqd等程序的时候,mac的同学可以直接键入
nsqd <--paramters-->
而linux的同学还需要键入工作路径,如
./~/nsq_1.1.0/bin/nsqd <--paramters-->,
当然你也可以设置系统路径来避免键入工作路径这个步骤。
二、实现官方文档上面的入门代码
代码原址在此
使用mac的同学可能会发现自己运行到第六步时
$ nsq_to_file --topic=test --output-dir=/tmp --lookupd-http-address=127.0.0.1:4161
在这里会报错。这是由于nsq_to_file无法找到nsqd。无法找到的原因是我们在运行第三步
$ nsqd --lookupd-tcp-address=127.0.0.1:4160
没有指定--broadcast-address,Mac会默认--broadcast-address=“”。我们需要自己指定--broadcast-address,在本机运行时可以直接为127.0.0.1,如quick_start中的代码可以运行成功。这里参考了一篇博客,感恩的心~
nsqd --lookupd-tcp-address=127.0.0.1:4160 --broadcast-address=127.0.0.1
但是,如果你的nsqd和lookup是不在同一台机器上,你需要设置成你nsqd现在运行的机器的ip地址。这样等你访问admin的时候才能成功。
到这里,你已经能够完成nsq在单机上的运行了~
二、多个nsq的单机运行实例
大家可以参考这篇博客,但是你在具体运行这篇博客的代码时,当你运行第二个nsqd时
nsqd --lookupd-tcp-address=127.0.0.1:4160 --tcp-address=0.0.0.0:4152 --http-address=0.0.0.0:4153
会报错,--data-path已经被其他nsqd占用。在这里你需要自己为nsqd指定--data-path.
三、多机运行nsqd
我的开发环境为一台linux服务器(IP:10.224.8.168),一台mac主机(IP:10.95.48.67)
下面我会在服务器上运行:nsqlookupd,一个nsqd,consumser
mac主机上运行:一个nsqd,admin,producer
首先服务器上运行nsqlookupd
./nsqlookupd
服务器运行一个nsqd
./nsqd --lookupd-tcp-address=10.224.8.168:4160 --broadcast-address=10.224.8.168 --data-path=/tmp
本地主机运行一个nsqd
nsqd --lookupd-tcp-address=10.224.8.168:4160 --broadcast-address=10.95.48.67 --data-path=/tmp
本地主机运行nsqadmin
nsqadmin --lookupd-http-address=10.224.8.168:4161
本地主机运行producer,向两个nsqd发送数据
没有go-nsq包的先去get一个nsq包
go get github.com/nsqio/go-nsq
package main
import (
"time"
"fmt"
"log"
"github.com/nsqio/go-nsq"
)
func main() {
err := initConsumer("test1", "test-channel1", "10.224.8.168:4161")
if err != nil {
log.Fatal("init Consumer error")
}
err = initConsumer("test2","test-channel2","10.224.8.168:4161")
if err != nil {
log.Fatal("init Consumer error")
}
select {
}
}
type nsqHandler struct {
nsqConsumer *nsq.Consumer
messagesReceived int
}
//处理消息
func (nh *nsqHandler)HandleMessage(msg *nsq.Message) error{
nh.messagesReceived++
fmt.Printf("receive ID:%s,addr:%s,message:%s",msg.ID, msg.NSQDAddress, string(msg.Body))
fmt.Println()
return nil
}
func initConsumer(topic, channel, addr string) error {
cfg := nsq.NewConfig()
cfg.LookupdPollInterval = 3*time.Second
c,err := nsq.NewConsumer(topic,channel,cfg)
if err != nil {
log.Println("init Consumer NewConsumer error:",err)
return err
}
handler := &nsqHandler{nsqConsumer:c}
c.AddHandler(handler)
err = c.ConnectToNSQLookupd(addr)
if err != nil {
log.Println("init Consumer ConnectToNSQLookupd error:",err)
return err
}
return nil
}
服务器运行consumer,从两个nsqd中拉取信息
package main
import (
"time"
"fmt"
"log"
"github.com/nsqio/go-nsq"
)
func main() {
err := initConsumer("test1", "test-channel1", "10.224.8.168:4161")
if err != nil {
log.Fatal("init Consumer error")
}
err = initConsumer("test2","test-channel2","10.224.8.168:4161")
if err != nil {
log.Fatal("init Consumer error")
}
select {
}
}
type nsqHandler struct {
nsqConsumer *nsq.Consumer
messagesReceived int
}
//处理消息
func (nh *nsqHandler)HandleMessage(msg *nsq.Message) error{
nh.messagesReceived++
fmt.Printf("receive ID:%s,addr:%s,message:%s",msg.ID, msg.NSQDAddress, string(msg.Body))
fmt.Println()
return nil
}
func initConsumer(topic, channel, addr string) error {
cfg := nsq.NewConfig()
cfg.LookupdPollInterval = 3*time.Second
c,err := nsq.NewConsumer(topic,channel,cfg)
if err != nil {
log.Println("init Consumer NewConsumer error:",err)
return err
}
handler := &nsqHandler{nsqConsumer:c}
c.AddHandler(handler)
err = c.ConnectToNSQLookupd(addr)
if err != nil {
log.Println("init Consumer ConnectToNSQLookupd error:",err)
return err
}
return nil
}
最后上传两张图,供以后参考