消息队列底层原理总结

发布者: xiaozhimn

消息队列应用的场景

业务解耦:消息队列要解决的最本质问题,实现设计的单一性原则,不耦合其他模块的业务。
最终一致性:用来处理延迟不那么敏感的“分布式事务”场景或者不重要的业务。
广播:下游有很多系统关心你的系统发出的通知的时候。

为什么消息队列需要broker(消息队列服务端)

消息的转储,在更合适的时间点投递,或者通过一系列手段辅助消息最终能送达消费机。
规范一种范式和通用的模式,以满足解耦、最终一致性、错峰等需求。其实简单理解就是一个消息转发器,把一次RPC做成两次RPC。发送者把消息投递到broker,broker再将消息转发一手到接收端。
总结起来就是两次RPC加一次转储,如果要做消费确认,则是三次RPC

消息队列服务端broker的消息存储系统选型

主要的存储方案有:文件系统、分布式KV(持久化)、分布式文件系统、数据库。速度从左到右递减,可靠性相反。
用来支持支付、交易等对可靠性要求非常高,但对性能和量的要求没有这么高,DB是最好的选择。但是DB受制于IOPS,如果要求单broker5位数以上的QPS性能,基于文件的存储是比较好的解决方案。
很多消息对于投递性能的要求大于可靠性的要求,且数量极大(如日志)。这时候消息直接暂存内存,尝试几次failover,最终投递出去也可以。

最终一致性的设计思路

主要是用“记录”和“补偿”的方式。
本地事务维护业务变化和通知消息,一起落地,然后RPC到达broker,在broker成功落地后,RPC返回成功,本地消息可以删除。否则本地消息一直靠定时任务轮询不断重发,这样就保证了消息可靠落地broker。
broker往consumer发送消息的过程类似,一直发送消息,直到consumer发送消费成功确认。
我们先不理会重复消息的问题,通过两次消息落地加补偿,下游是一定可以收到消息的。然后依赖状态机版本号等方式做判重,更新自己的业务,就实现了最终一致性。
如果出现消费方处理过慢消费不过来,要允许消费方主动ack error,并可以与broker约定下次投递的时间。
对于broker投递到consumer的消息,由于不确定丢失是在业务处理过程中还是消息发送丢失的情况下,有必要记录下投递的IP地址。决定重发之前询问这个IP,消息处理成功了吗?如果询问无果,再重发。
事务:本地事务,本地落地,补偿发送。本地事务做的,是业务落地和消息落地的事务,而不是业务落地和RPC成功的事务。消息只要成功落地,很大程度上就没有丢失的风险。

如何鉴别消息重复,并幂等的处理重复消息

鉴别消息重复:利用存储系统的唯一键,给每个消息加上一个MessageId
幂等处理重复消息的方法:
跟鉴别消息重复一样,利用MessageId,重复即不处理
版本号,每个消息都带一个版本号,只处理比当前存储版本号高的消息
状态机,跟业务耦合比较严重,根据具体业务类型判断

为什么网络请求小包合并成大包会提高性能

减少无谓的请求头,如果你每个请求只有几字节,而头却有几十字节,无疑效率非常低下。
减少回复的ack包个数。把请求合并后,ack包数量必然减少,确认和重发的成本就会降低。

消息队列的两种模型push和pull的对比

push的缺点:慢消费。指broker给consumer推送一堆consumer无法处理的消息,consumer不是reject就是error,然后来回踢皮球。
pull的缺点:消息延迟与盲等。pull是消费方主动定时去拉去消息,由于消息的不确定性,所以会造成会多无用的请求。
RocketMQ的设计方案:长轮询,来平衡推拉模型各自的缺点。基本思路是:消费者如果尝试拉取失败,不是直接return,而是把连接挂在那里wait,服务端如果有新的消息到来,把连接notify起来。但海量的长连接block对系统的开销还是不容小觑的,还是要合理的评估时间间隔,给wait加一个时间上限。
0赞