-
基本组件
(1) nsqd
官方原话:nsqd 是一个守护进程,负责接收,排队,投递消息给客户端
简单的说,真正干活的就是这个服务,它主要负责message的收发,队列的维护。nsqd会默认监听一个tcp端口(4150)和一个http端口(4151)以及一个可选的https端口
总的来说,nsqd 具有以下功能或特性
-
对订阅了同一个topic,同一个channel的消费者使用负载均衡策略(不是轮询)
-
只要channel存在,即使没有该channel的消费者,也会将生产者的message缓存到队列中(注意消息的过期处理)
-
保证队列中的message至少会被消费一次,即使nsqd退出,也会将队列中的消息暂存磁盘上(结束进程等意外情况除外)
-
限定内存占用,能够配置nsqd中每个channel队列在内存中缓存的message数量,一旦超出,message将被缓存到磁盘中
-
topic,channel一旦建立,将会一直存在,要及时在管理台或者用代码清除无效的topic和channel,避免资源的浪费
-
一个nsqd启动之后,会注册到nsqlookupd。
-
每个nsqd管理自己的Topic/channel;不同nsqd,相同的topic下的channel不一定相同;
-
Nsqd_to_nsqlookupd 信息更新
- nsqd 启动时会开启一个lookupLoop循环;
- topic/channel 创建删除的操作会加入到nsqd的notifyChan;
- lookupLoop循环中,nsqd从notifyChan取消息,向指定或新增的nsqlookupd 更新⾃己的Topic/channel信息;
- 如果nsqd进程挂了,nsqdlookupd在 5分钟 后断开连接;
(2) nsqlookupd
nsqlookupd是守护进程负责管理拓扑信息。客户端通过查询 nsqlookupd 来发现指定话题(topic)的所属nsqd集群,并且 nsqd 节点广播话题(topic)和通道(channel)信息
简单的说nsqlookupd就是中心管理服务,它使用tcp(默认端口4160)管理nsqd服务,使用http(默认端口4161)管理nsqadmin服务。同时为客户端提供查询功能
总的来说,nsqlookupd具有以下功能或特性
-
唯一性
在一个Nsq服务中只有一个nsqlookupd服务,也可以在集群中部署多个nsqlookupd,但它们之间是没有关联的
-
去中心化
即使nsqlookupd崩溃,也会不影响正在运行的nsqd服务
-
充当nsqd和naqadmin信息交互的中间件
-
提供一个http查询服务,给客户端定时更新nsqd的地址目录
(3) nsqadmin
是一套 WEB UI,用来汇集集群的实时统计,并执行不同的管理任务
总的来说,nsqadmin具有以下功能或特性
-
提供一个对topic和channel统一管理的操作界面以及各种实时监控数据的展示,界面设计的很简洁,操作也很简单
-
展示所有message的数量
-
能够在后台创建topic和channel,这个应该不常用到
nsqadmin的所有功能都必须依赖于nsqlookupd,nsqadmin只是向nsqlookupd传递用户操作并展示来自nsqlookupd的数据
nsqadmin默认的访问地址是http://127.0.0.1:4171/
nsqd/nsqlookupd/nsqadmin/consumer/producer的关系如图所示
[外链图片转存失败(img-QELYLVf3-1563328390566)(…/resources/17tow1895918.png)]
-
-
生产消费过程
(1) 生产者连接nsqd,通过http接口,写入消息到topic
(2) topic把消息分发到所有的channel
(3) 消费者通过nsqdlookupd发现topic对应的nsqd,连接到nsqd上,订阅指定的channel,当有消息到达时进行消费
(4) 同一个channel下多个消费者协作消费,channel会自动做负载均衡把消息发给不同的消费者
在 news_data_consumer 这个项目中,我们的配置中指定了一个topic和一个channel,而项目本身是单线程、多机器(多进程)运行的,所以不同机器构成了不同的consumer,然后同一个channel会自动做负载均衡交给多台机器消费
-
公司nsq双机房部署方案
同一个topic会在LF和HL两个集群上部署,每个集群的生产者发现自己集群上的nsqd,并往里面写入消息。当一个集群的nsqd发生故障时,nsq组会手动把consul的地址切到另外一个集群上,后期会支持自动切换。
[外链图片转存失败(img-L8GLX70s-1563328390567)(…/resources/jglkqjlktjlqkj.png)]
[外链图片转存失败(img-ShaiwjBx-1563328390567)(…/resources/Lark20190716-164105.png)]
生产者其实是先通过consul写到nsqd-proxy上,然后proxy决定往具体那个nsqd实例上写;
每个nsqd实例会向两个集群中的nsqlookupd注册;
消费者通过nsqlookupd,可发现两个集群上的所有nsqd,进行消费
-
消费者
消费者有两种方式与nsqd建立连接
-
消费者直连nsqd,这是最简单的方式,缺点是nsqd服务无法实现动态伸缩了(当然,自己去实现一个也是可以的)
-
消费者通过http查询nsqlookupd获取该nsqlookupd上所有nsqd的连接地址,然后再分别和这些nsqd建立连接(官方推荐的做法),但是客户端会不停的向nsqlookupd查询最新的nsqd地址目录
consumer如果通过 nsqlookupd 节点自动发现,会默认自动订阅所有包含该topic的nsqd节点;会消费所有nsqd节点的消息,消息的顺序不保证有序;如果指定连接某一个nsqd,那么只会订阅这一个nsqd,只会消费该nsqd下的消息。官方推荐的是通过 nsqlookupd 发现
-
-
生产者
生产者必须直连nsqd去投递message
这里有一个问题就是如果生产者所连接的nsqd炸了,那么message就会投递失败,所以在客户端必须自己实现相应的备用方案
-
生产者创建时要(1)指定ip+端口号(默认为4150),(2)publish消息的时候要指定topic
// 新建 producer producer, err := nsq.NewProducer("127.0.0.1:4150", nsq.NewConfig()) ... // 发布消息 err := producer.Publish("my_topic", []byte(fmt.Sprintf("Hello World "))) ...
消费者创建时要(1)指定topic和channel,并且(2)要指定消息处理的handler,然后(3)和nsqlookupd连接
注:channel名字如果以gajgl(我忘了是啥)结尾的话,那么这个channel是临时的,否则就是永久的
// 新建 consumer consumer, err := nsq.NewConsumer("my_topic", "channel-a", nsq.NewConfig()) ... // 添加消息处理的具体实现 consumer.AddHandler(nsq.HandlerFunc(func(msg *nsq.Message) error { fmt.Println("Consumer1:", string(msg.Body)) return nil })) ... // 连接 nsqlookupd err := consumer.ConnectToNSQLookupd("127.0.0.1:4161") ...
-
nsq集群运维
(1)新集群nsqd允许的最大message是20M
(2)nginx和nsqlookupd部署在同一个机器上,一般一个集群4个nginx和nsqlookupd的实例
-
生产者和nsqd实例连接靠HTTP,消费者和nsqlookupd连接(最终和nsqd连接)靠的是TCP长连接
-
(1)GO可以使用tmq接入生产者,其他语言可以直接通过http post进行消息的写入
(2)GO可通过tmq生成消费者代码,更改相应配置,重写handler方法,进行消费,其它代码可查找对应客户端
重写handler.go中MessageHandler函数进行消费,函数返回非nil时,nsq会把消息重新放到channel里
-
(1) 水平扩展
多播消息路由和负载均衡的结合。
多播是一个topic对应多个channel;
负载均衡是每个channel对应多个consumernsqlookupd运行时发现producer和consumer
简单的TCP协议
主内存并且可以透明地使用磁盘
(2) nsq Guarantees
-
消息至少被传递一次,有可能被消费多次,这就要求consumer多次消费消息没有副作用
-
消息是无序的,首先在不同nsqd之间不共享信息,对于一个nsqd有可能因为一些原因导致消息重新排序,同时磁盘和内存的结合使用导致没有一定的先后顺序(代码中两个是通过select中间两个channel来实现,没有严格的先后顺序)
-
nsqlookupd 能保证最终一致性,也就是说最终能够发现自己所需要所有的topic,nsqd和nsqdlookupd之间不会有一个强一致性的协议保证消息同步
-
消息可以放入内存中也可以放入到磁盘中,如果因为nsqd异常崩溃,导致一些数据丢失是无法恢复,可以通过将内存–mem-queue-size设置为0(以#ephemeral结尾的消息超过设置的内存之后就回直接被抛弃,不会写入到磁盘上去,可以做一些简单的测试或者对应的应用),这样可以将所有消息落盘,也可以通过启动多个nsqd实例,producer将消息的copy发送给两个nsqd作为备份,这样就需要下游consumer要能够接受消息具有幂等性(多次接受相同消息不会产生负面影响)
(3) 组件介绍
-
nsqd 将接收producer产生的消息,放入自己的队列(内存或者磁盘中),然后等待consumer连接自己,消费消息
-
nsqlookupd 管理nsqd的拓扑结构信息,并且提供最终一致性的保证
-
nsqadmin一个界面,起到实时查看集群状态的作用
在每个nsqd中每个topic有多个channel,每个channel有自己的消息副本(减少因为某些channel消费太慢而影响其它channel)
nsqlookup提供一个发现服务,consumer能够通过nsqdlookupd查询到自己订阅topic的nsqd实例的地址,通过nsqdlookup在producer和consumer做了个解耦,减少系统的复杂性和维护成本,对于nsqd来说,它和nsqlookupd之间有一个长连接的tcp协议,定时push 自己的状态,这样无论是重新开启一个nsqd还是重新开启一个consumer都只需要配置一个nsqdlookup的地址就可以了
-
-
Consumer
-
Consumer订阅channel之后,NSQD通过Tcp的Handle函数启动两个循环:
a. ⼀个监听consumer指令,处理consumer命令(SUB,NOP…);
b. 另一个向consumer发送数据(message, heartbeat…):每30s,nsqd向consumer发送⼼跳,consumer收到heart会返回NOP,超过1分钟未收到consumer回复,则关闭这个连接;
-
consumer连接NSQD之后会开启两个子进程readLoop/writeLoop,负责consumer的读写操作
-
consumer与channel连接之后会一直消费消息,直到断开连接;
-
与channel相连的所有consumer都会试图从channel的memoryMsgChan⾥面拿消息,一个消息只会被一个consumer拿到
-
如果消息超时未收到回复,需要重新排队的消息会从in-flight删除加入到deferred队列列,等待⼀段时间再加入到memoryMsgChan
-
-
Kafka vs NSQ
- Kafka 因为replication机制需要维护controller;有额外消耗;
- Kafka 能保证消息的局部有序(partition);nsq消息⽆序;
- Kafka 按照一定策略略将消息发到不同的broker;nsq产⽣多个worker随机发到不同nsqd;
4.Kafka 一个集群⼀一个ZooKeeper, nsq可以有多个nsqlookupd(现在的配置是一个集群4个nsqlookupd)
-
NSQ 优缺点
-
优点
(1). 部署⾮常简单,管理⽅便
(2). 扩展⽅便
(3). 去中心化,轻量级
(4). 避免单点故障(nsqd之间独立)
(5). 确保消息送达(At least Once)
(6). 生产者消费者自动发现
(7). 使用了简单的TCP协议且具有多种语言的客户端功能库
(8). ⽅便⼆次开发
-
缺点
(1). Nsqd挂了会丢内存数据(定时写磁盘/抛异常/双机备份)
(2). 无法满足消息强时序要求
-
-
消费者是如何从nsq中读取消息的?
(1). 消费者⾸首先需要发送SUB命令,告诉nsqd它想订阅哪个Channel,然后nsqd将该Client与Channel建⽴对应关系,channel将client加⼊到⾃己的client列表
(2). 消费者发送RDY命令,告诉服务端它准备好接受count个消息,服务端则向消费者发送count个消息,如果消费者想继续接受消息就需要不断发送RDY命令告诉服务端⾃己准备好接受消息
(3) 客户端回复消息处理结果FIN (finish) or REQ (re-queue),如果超时未收到客户端回复,消息会被重新加入到队尾;Nsqd挂了了,则内存中的数据会丢失
nsq总结
最新推荐文章于 2022-10-06 22:42:45 发布