NSQ(6)-nsq相关策略
1:nsq的流量控制 RDY
消息中间件的实现无非两种套路,一种让客户端pull,典型的比如kafka便是如此,而另一种则是push,也就是让客户端不需要做任何操作,只需要做好conn便可以源源不断收到服务端的推送,典型的代表就是我们今天介绍的nsq。
pull的优势在于客户端可以自己做流控,比如客户端想什么时候pull就什么时候pull,不会因为服务端的强迫而接受,但劣势也很明显,如果服务端的生产速度很慢,客户端需要不断的轮询会让cpu处于繁忙且无用的状态。
push的优势则在于能够不受限于客户端的速度,可以让服务端更快的、批量的把数据push给客户端,因此大部分push实现的消息中间件都是属于内存型,而nsq比较特殊,它实际上是内存+磁盘的一个消息中间件。上面也说了,pull流的优势在于可以让客户端自由控制消息的速度,但是push流不一样,push流不管客户端是否多繁忙都会推送消息,如果没有一个流控机制,很容易让客户端最终因为消费速度跟不上导致产生各种性能问题。nsq其实也考虑到这一点,于是采用了一个
RDY的状态字段来表示流控。简单来说,就是客户端连接上nsqd之后,会告诉nsqd它的可接受的消息数量是多少,每当nsqd给客户端推送一条消息这个RDY就会减一,而客户端消费完毕并且发送一个FIN之后,这个RDY又会加一(其实这个设计有点类似tcp中的用来控制流量的窗口机制)。
// ConnectToNSQD 部分代码
// pre-emptive signal to existing connections to lower their RDY count
r.connections[addr] = conn
for _, c := range r.conns() {
r.maybeUpdateRDY(c)
}
- 可以看到 go-nsq 的Consumer 结构体中有一个字段 connections,它是一个 map,key是nsqd addr,value是连接成功的Conn。
- 上面的代码表示会遍历这个Customer的所有 nsqd conn(customer可以同时连接多个nsqd),然后调用
maybeUpdateRDY方法。
func (r *Consumer) maybeUpdateRDY(conn *Conn) {
inBackoff := r.inBackoff()
inBackoffTimeout := r.inBackoffTimeout()
if inBackoff || inBackoffTimeout {
r.log(LogLevelDebug, "(%s) skip sending RDY inBackoff:%v || inBackoffTimeout:%v",
conn, inBackoff, inBackoffTimeout)
return
}
count := r.perConnMaxInFlight()
r.log(LogLevelDebug, "(%s) sending RDY %d", conn, count)
r.updateRDY(conn, count)
}
- 计算平均每一个连接的最大 InFlight 数量。
func (r *Consumer) updateRDY(c *Conn, count int64) error {
if c.IsClosing() {
return ErrClosing
}
// never exceed the nsqd's configured max RDY count
if count > c.MaxRDY() {
count = c.MaxRDY()
}
// stop any pending retry of an old RDY update
r.rdyRetryMtx.Lock()
if timer, ok := r.rdyRetryTimers[c.String()]; ok {
timer.Stop()
delete(r.rdyRetryTimers, c.String())
}
r.rdyRetryMtx.Unlock()
// never exceed our global max in flight. truncate if possible.
// this could help a new connection get partial max-in-flight
rdyCount := c.RDY()
maxPossibleRdy := int64(r.getMaxInFlight()) - atomic.LoadInt64(&r.totalRdyCount) + rdyCount
if maxPossibleRdy > 0 && maxPossibleRdy < count {
count = maxPossibleRdy
}
if maxPossibleRdy <= 0 && count > 0 {
if rdyCount == 0 {
// we wanted to exit a zero RDY count but we couldn't send it...
// in order to prevent eternal starvation we reschedule this attempt
// (if any other RDY update succeeds this timer will be stopped)
r.rdyRetryMtx.Lock()
r.rdyRetryTimers[c.String()] = time.AfterFunc(5*time.Second,
func() {
r.updateRDY(c, count)
})
r.rdyRetryMtx.Unlock()
}
return ErrOverMaxInFlight
}
return r.sendRDY(c, count)
}
- 此处主要是对 rdy计数count 进行了预处理。
func (r *Consumer) sendRDY(c *Conn, count int64) error {
if count == 0 && c.LastRDY() == 0 {
// no need to send. It's already that RDY count
return nil
}
atomic.AddInt64(&r.totalRdyCount, count-c.RDY())
c.SetRDY(count)
err := c.WriteCommand(Ready(int(count)))
if err != nil {
r.log(LogLevelError, "(%s) error sending RDY %d - %s", c.String(), count, err)
return err
}
return nil
}
- 组装成
RDY命令,通过当前连接的WriteCommand方法告诉给 nsqd。
// NewConsumer
func (r *Consumer) rdyLoop() {
redistributeTicker := time.NewTicker(r.config.RDYRedistributeInterval)
for {
select {
case <-redistributeTicker.C:
r.redistributeRDY()
case <-r.exitChan:
goto exit
}
}
exit:
redistributeTicker.Stop()
r.log(LogLevelInfo, "rdyLoop exiting")
r.wg.Done()
}
- 在连接成功之后,也会单独开启一个 goroutine 在后台不断去调整这个
rdycount。
2.nsq消息传输的可靠性与持久化
消息传输的可靠性
思考下面的问题:
- 网络传输的不确定性,比如超时。
- 客户端处理消息时崩溃,消息如何重传。
- 如何标识消息被客户端成功处理完毕。
- 消息的持久化,nsq服务端重新启动时消息不丢失。
func (p *protocolV2) messagePump(client *clientV2, startedChan chan bool)
- 客户端处理消息的逻辑:
- 在上述函数中,服务端会定时检查client端的连接状态,读取客户端发过来的各种命令,发送心跳等。
- 每一个连接最终的目的就是监听
channel的消息,发送给客户端进行消费。 - 在发送给客户端之前,把这个消息设置为在飞翔中。
- 对发送给客户端信息设置为在飞翔中,如果在如果处理成功就把这个消息从飞翔中的状态中去掉,如果在规定的时间内没有收到客户端的反馈,则认为这个消息超时,然后重新归队。
func (n *NSQD) queueScanLoop()
- 服务端处理消息的逻辑:
- 使用协程定时去扫描随机的
channel里是否有过期数据。 - 在扫描
channel的时候,如果发现有过期数据后,会重新放回到队列,进行重发操作。
- 使用协程定时去扫描随机的
客户端对消息的处理和响应
func (r *Consumer) handlerLoop(handler Handler)
- 在服务端发送消息给客户端后,如果在处理业务逻辑时,如果发生错误则给服务器发送
Requeue命令告诉服务器,重新发送消息进处理。 - 如果处理成功,则发送
Finish命令。 - 服务端收到命令后,对飞翔中的消息进行处理,如果成功则去掉,如果是
Requeue则执行归队和重发操作,或者进行defer队列处理。
消息的持久化
- 虽然系统支持消息持久化存储在磁盘中(通过
--mem-queue-size),不过默认情况下消息都在内存中。 - 如果将
--mem-queue-size(一个channel的容量) 设置为 0,所有的消息将会存储到磁盘。我们不用担心消息会丢失,nsq 内部机制保证在程序关闭时将队列中的数据持久化到硬盘,重启后就会恢复。 - NSQ 没有内置的复制机制,却有各种各样的方法管理这种权衡,比如部署拓扑结构和技术,在容错的时候从属并持久化内容到磁盘。
diskqueue是nsq自己实现的一个先进先出的消息文件队列,go-diskqueue是把消息爆出到本地文件内。
3.消息的负载处理
实际应用中,一部分服务集群可能会同时订阅同一个
topic,并且处于同一个channel下。当nsqd有消息需要发送给订阅客户端去处理时,发给哪个客户端是需要考虑的,也就是消息的负载。 如果不考虑负载情况,把随机的把消息发送到某一个客服端去处理消息,如果机器的性能不同,可能发生的情况就是某一个或几个客户端处理速度慢,但还有大量新的消息需要处理,其他的客户端处于空闲状态。理想的状态是,找到当前相对空闲的客户端去处理消息。
nsq的处理方式是客户端主动向nsqd报告自已的可处理消息数量(也就是RDY命令)。nsqd根据每个连接的客户端的可处理消息的状态来随机把消息发送到可用的客户端,来进行消息处理。MaxInFlight用来设置最大的处理中的消息数量,会根据这个变量计算是否需要更新RDY。- 初始化的时候 客户端会向连接的nsqd服务端来发送updateRDY来设置最大处理数。
func (r *Consumer) onConnMessage(c *Conn, msg *Message) {
atomic.AddInt64(&r.totalRdyCount, -1)
atomic.AddUint64(&r.messagesReceived, 1)
r.incomingMessages <- msg
r.maybeUpdateRDY(c)
}
当有消息从
nsqd发送过来后也会调用maybeUpdateRDY方法,计算是否需要发送RDY命令。上面就是主要的处理逻辑,但还有一些逻辑,就是当消息处理发生错误时,
nsq有自己的退避算法backoff也会更新RDY简单来说就是当发现有处理错误时,来进行重试和指数退避,在退避期间RDY会为0,重试时会先放尝试RDY为1看有没有错误,如果没有错误则全部放开,具体处理逻辑这里就不细说了。
参考资料:
https://studygolang.com/articles/21065?fr=sidebar
https://www.cnblogs.com/li-peng/p/11868123.html
https://github.com/nsqio/go-diskqueue
NSQ(6)-nsq相关策略的更多相关文章
- Redis-设置Key的过期时间及相关策略
Redis-设置Key的过期时间及相关策略 1.设置key的过期时间 1.1expire key second:设置key的过期时间(秒) 1.2ttl key:查看key的有效期 1.3persis ...
- Go操作NSQ
NSQ是目前比较流行的一个分布式的消息队列,本文主要介绍了NSQ及Go语言如何操作NSQ. NSQ NSQ介绍 NSQ是Go语言编写的一个开源的实时分布式内存消息队列,其性能十分优异. NSQ的优势有 ...
- Go之NSQ
文章引用自 NSQ NSQ是目前比较流行的一个分布式的消息队列,本文主要介绍了NSQ及Go语言如何操作NSQ. NSQ介绍 NSQ是Go语言编写的一个开源的实时分布式内存消息队列,其性能十分优异. N ...
- Go之NSQ简介,原理和使用
NSQ简介 NSQ是Go语言编写的一个开源的实时分布式内存消息队列,其性能十分优异. NSQ 是实时的分布式消息处理平台,其设计的目的是用来大规模地处理每天数以十亿计级别的消息.它具有分布式和去中心化 ...
- GO学习-(27) Go语言操作NSQ
Go语言操作NSQ NSQ是目前比较流行的一个分布式的消息队列,本文主要介绍了NSQ及Go语言如何操作NSQ. NSQ NSQ介绍 NSQ是Go语言编写的一个开源的实时分布式内存消息队列,其性能十分优 ...
- NSQ源码剖析之nsqd
NSQ简介 NSQ 是实时的分布式消息处理平台,其设计的目的是用来大规模地处理每天数以十亿计级别的消息.NSQ 具有分布式和去中心化拓扑结构,该结构具有无单点故障.故障容错.高可用性以及能够保证消息的 ...
- Golang入门教程(十七)Linux/Windows下快速搭建和配置NSQ
前言 NSQ是一个基于Go语言的分布式实时消息平台,它基于MIT开源协议发布,代码托管在GitHub,其当前最新版本是0.3.1版.NSQ可用于大规模系统中的实时消息服务,并且每天能够处理数亿级别的消 ...
- nsq多播分发和负载均衡实验
什么是nsq?请参考实时分布式消息平台nsq. 本地如何搭建nsq?请参考本地搭建nsq经验分享. 从NSQ的设计文档中得知,单个nsqd被设计为一次能够处理多个流数据,NSQ中的数据流模型是由str ...
- 12.1 Go nsq
12.1 Go nsq 1.nsq是Go语言编写的,开源的内存分布式消息队列中间件 2.可以大规模的处理每天数以十亿级别的消息 3.分布式和去中心化拓扑结构,无单点故障 4.地址https://git ...
- Spring之SpringMVC(源码)初始化DispatcherServlet策略配置
1.从上一篇文章中可以SpringMVC初始化的过程中完成的其中一件事就是DispatcherServlet的相关策略的配置,如下所示 protected void initStrategies(Ap ...
随机推荐
- IO相关了解
1.IO的概念 IO简单来讲就是对输入输出设备的简化表达形式 单片机中各种接口,进行数据流的传输 从磁盘中读取数据至内存,又或者从内存中写入磁盘 编程中的IO 此时的IO其应用程序的运行态,即进程-- ...
- RocketMQ - 消费者进度保存机制
RocketMQ设计了远程位点管理和本地位点管理两种位点管理方式.集群消费时,位点由客户端提交给Broker保存,具体实现代码在RemoteBrokerOffsetStore.java文件中:广播消费 ...
- LeetCode-911 在线选举
来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/online-election 题目描述 给你两个整数数组 persons 和 times .在选 ...
- Linux操作命令(四) 1.which命令 2.whereis命令 3.locate命令
1.which命令 which 命令的作用是,在 PATH 变量指定的路径中搜索可执行文件的所在位置.它一般用来确认系统中是否安装了指定的软件 确认是否安装了 gcc:which gcc 查看 cd ...
- WGCMS 奇迹网站系统 介绍[V2023.2.2]
智鹏网站系统,请勿用作非法用途 权利和义务: 程序仅限学习技术使用,未经官方许可不得用于商业! 程序售价500元一套,绑定域名,不限制端口.如绑定:xx.com,则www.xx.com.mu.xx.c ...
- Docker 架构演进之路
转载:https://developer.aliyun.com/article/673009 前言 Docker已经推出了5年,在这5年中它极大的改变了互联网产品的架构,推进了新的产品开发.测试和运维 ...
- vue项目引用vue-pdf打包多出worker.js文件
问题描述:项目要用到pdf预览功能,因为是vue项目就是直接导入了vue-pdf组件,但是在进行打包的时候在dist文件夹下面多个worker.js文件,导致项目部署后预览pdf直接报了404 方案一 ...
- 查电脑并修改IP地址,你晓得吗?
查电脑并修改IP地址,你晓得吗? 好记性不如烂笔头,古人的话,浅显却好有深意,越品越有味道. 每次都会忘记怎么查电脑IP,那么今天就写下来吧! 方法一:通过命令行查询IP地址 快捷键Win ...
- function的length,就是第一个具有默认值之前的参数个数。
一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法.常见的类数组对象有 arguments 和 DOM 方法的返回结果,还有一个函数也 ...
- 2022-05-26内部群每日三题-清辉PMP
1.在执行关键路径上的一项活动时,职能主管将涉及这个活动的两个项目资源调去支持解决某个应急情况,项目经理应该怎么做? A.实施应急计划 B.快速跟进关键路径 C.与职能经理协商分配替代资源 D.将该问 ...