RocketMQ常见问题总结(非常好的总结性的文章)
初识RocketMQ | RocketMQ(RocketMQ官网!!)
万字长文讲透 RocketMQ 的消费逻辑(这篇文章偏底层,全面介绍了rocketMQ)
千锋教育RocketMQ全套视频教程,快速掌握MQ消息中间件_哔哩哔哩_bilibili
📎rocketmq.pdf(千峰教育配套课件)
为什么用消息队列?
重点理解:削峰、异步和解耦!!!!!!!
从零到一学习RocketMQ | 拿个offer - 开源&项目实战
消息队列有三个应用场景,异步、解耦和削峰,其中削峰是企业项目运行中保障应用稳定性较为重要的特点。
异步: 系统中的不同部分不需要同步执行,可以独立地进行操作, 使得快速响应请求 。
-
- 常见的一个场景是用户注册后,需要发送注册邮件和短信通知,以告知用户注册成功。如果注册、发送邮件、短信各 50ms,原有逻辑需要 150ms。而使用消息队列异步后,只需执行用户注册 50ms,以及发送队列消息 5ms,共 55ms 即可返回,极大提升了系统接口响应时间。
解耦:消息队列将系统中的不同模块或服务解耦。消息队列充当中介角色,发送者和接收者之间没有直接的依赖关系(解耦不同模块/服务) 。
-
- 比如说用户支付成功后,发送一条消息队列可以完成几件事情,订单系统修改订单状态为已支付,库存系统扣减库存,用户系统加积分等。如果没有消息队列,只能支付系统一个一个去调用,耦合性太大。
- 具体如何实现上面这种解耦,比如创建一个
payTopic,用户支付后往这个topic发消息。然后订单、积分、库存分别为三个不同的 消费者组(订单消费组、积分消费组、库存消费组) 去监听这个payTopic,这样订单、积分、库存三个系统都能拿到全部的支付消息互不干扰。
-
削峰: 削峰的目的是平滑系统的请求流量 ,将瞬时的大量请求分散到较长的时间内处理 ,让消费者按照自己的能力消费 。 -
- 在秒杀或团队抢购活动中,由于用户请求量较大,导致流量暴增,秒杀的应用在处理如此大量的访问流量后,下游的通知系统无法承载海量的调用量,甚至会导致系统崩溃等问题而发生漏通知的情况。所以我们需要加一层消息队列进行缓冲。主流程将消息发送消息队列,然后让下游服务通过消息消费者匀速消费。
为什么要用消息队列?
【分钟背八股】500:为什么要使用消息队列?_哔哩哔哩_bilibili
从削峰、异步、解耦来说
-
削峰:平滑系统的请求流量 ,将瞬时的大量请求分散到较长的时间内处理 ,让消费者按照自己的能力消费
-
- 首先假如某个接口本来只能处理2000的qps(请求),加MQ之后能处理2W,因为异步执行了所以接口响应快,所以能处理峰值请求,然后消费者慢慢消费。(异步和削峰是息息相关的)
- 比如秒杀的应用在处理如此大量的访问流量后,下游的通知系统无法承载海量的调用量。所以需要加一层消息队列进行缓冲。主流程将消息发送消息队列,然后让下游服务通过消息消费者匀速消费。
- 异步:系统中的不同部分不需要同步执行,可以独立地进行操作, 使得快速响应请求 。
- 解耦 :消息队列将 系统中的不同模块或服务解耦 。 消息队列充当中介角色,发送者和接收者之间没有直接的依赖关系(解耦不同模块/服务)
-
- 下游接口改动影响上游
- 下游接口宕机或响应慢,影响上游
- 为什么使用RocketMQ而不用Kafka或者RabbitMQ?(重要)
先说kafka优点,再说kafka相对于rocket的缺点,最后说学习成本。s
- 首先kafka的吞吐量确实是比rocketmq要高很多。
- 但是kafka不支持延迟消息以及事务消息,
- kafka在分区较多的情况下,会出现性能严重下降的情况。
主观因素:目前国内公司和开源项目也大量用RocketMQ,它相当于也是基于kafka设计的比kafka架构更简单,基于学习成本所以先学RocketMQ,后续如果有需要学kafka也比较方便。
- Kafka在数据吞吐上是远超rocketmq的,但是它的topic很多的情况下,性能又远低于rocketmq。基于这种情况kafka多用于处理海量的日志,历史数据等体量庞大的数据集合体,这样在个体数据庞大的情况下使用的topic点更少;rocketmq有着更加严谨的检查和规则,所以它更适合分散式的短消息和小数据,这也得于它的topic算法和规划,即使有成千上万个topic点,性能下降并不多。
Kafka和RocketMQ的极限性能大概是多少 ?
kafka17w/s
Rocket10w/s
拓展:为什么kafka比RocketMQ性能高,吞吐量高?
kafka为什么这么快?RocketMQ哪里不如Kafka?_哔哩哔哩_bilibili (详细解答)
在零拷贝broker发送给consumer数据时。
kafka采用的sendfile零拷贝,rocketMQ采用的mmap零拷贝。而sendfile比mmap少了一次系统调用,也少了一次DMA的拷贝,同时还少两次内核态与用户态之间的切换(一次系统调用就是两次切换)。
所以kafka性能更高。
拓展:为什么RocketMQ采用mmap而不采用更高效的sendifle呢?
这和mmap与sendfile的零拷贝的系统调用特点和RocketMQ的功能设计有关。
- mmap发送消息时,应用可以获取消息到消息的内容。
- sendfile发送消息时,返回值只是发送了多少字节,具体发送了什么内容用户态并不知道。因为根本没经过用户态,也没有像mmap那样做映射,所以肯定不知道呀。
而RocketMQ中的某些功能用户态应用层需要拿到消息内容,方便后续投递。比如消息重试将消息投到延迟队列中,或者重试16次失败后将消息投到死信队列中。
为什么不用线程池呢?
两个点
- 线程池不满足可靠性,机器宕机消息丢失。
- 线程池不能处理高并发,可能会OOM。高并发来之后消息会排队,到阻塞队列中,而线程池中的任务是在内存中的,高并发可能造成OOM内存溢出。
为什么用消息队列和不是调RPC接口?
具体情况具体分析(从消费异步解耦分析)
- RPC不能异步,不能减少接口响应时间。
- RPC不能业务解耦,两个模块强依赖
-
- 比如我下游接口信息改了那我这边也要改
- 或者下游系统宕机导致这边调用接口失败,影响上游业务的执行。
- RPC不能削峰,假如本服务能抗住这个并发,下游业务可能扛不住这个并发,通过消息队列下游消费者可以按照自己的频率消费。
- 不同业务/服务职责分离(出了问题自己背自己的锅) ,业务A只要保证消息送到了就行,下游业务自己保证可靠消费。
MQ异步和削峰是息息相关的,可以通过异步快速响应请求的方式扛住并发量,同时对于异步执行的下游,MQ将下游的流量平滑(削峰) ,下游业务可以按照自己的速率消费。
RPC调用和消息队列的优缺点?
一个是削峰、异步、解耦。一个是实时性和简单。
RPC优点:1. 实时性比较好,2. 实现简单不需要考虑消息可靠顺序重复这种机制。
RPC缺点:
- 代码耦合,直接调用接口(如果接口改了或者下游服务挂了) ,如果有50个下游系统岂不是要调50个接口?。(MQ解耦)
- 不满足高性能,不能及时返回响应。(MQ异步)
- 扩展性低,如果这时又有另一个下游系统需要这份信息,那么需要改代码,在代码中加调用逻辑。(如果我是MQ只需要让新下游系统监听MQ就行了)
- 无法处理高并发调用,下游系统可能扛不住(MQ削峰)
没有消息队列削峰会导致什么,或者高并发最终会导致什么?
整个过程分析
- 不削峰不异步的话,首先接口响应请求就会慢。
- 因为每个请求在tomcat web服务器中都是一个线程,tomcat将请求用线程取处理,tomcat线程池最大线程数是200,也就是同一时间能处理200请求。
- 如果请求响应慢,前面的请求不释放线程,导致后面的请求需要去tomcat线程池中排队,排队任务太多最终导致OOM或者一直卡着用户体验差。
补充:tomcat线程池是先创建最大线程数后加入阻塞队列的。
补充:假设现在接口响应200ms,tomcat最大线程数为200,那么QPS是多少?200ms能处理200个请求,那么1s能处理1000个请求,QPS是1000。
可靠
保证可靠,消息不丢?
4 张图,9 个维度告诉你怎么做能确保 RocketMQ 不丢失消息
三个方面分析,结合相关代码理解
1、生产者可靠:
- 同步发送 和 异步发送都能保证可靠,可以
retryTimesWhenSendFailed设置发送失败的重试次数。 - 如果重试也失败,同步发送producer.send(msg)方法会抛异常,异步发送会回调onException()方法。那么同步发送就捕获异常并兜底处理,异步发送就在onException兜底处理。
-
- 如何进行兜底处理?重试发送失败可以通过MySQL消息失败表或日志记录消息兜底处理。
-
- 同步发送,如果发送失败mq会自动进行消息重试,默认两次, 可以通过 retryTimesWhenSendFailed 设置。
- 异步发送,如果发送失败mq会自动进行消息重试,默认两次, 可以通过 retryTimesWhenSendAsyncFailed设置。
2、broker消息存储的可靠:
- 同步刷盘 保证强可靠性,异步刷盘 可能会有丢失。
- 主从方面:有同步复制和异步复制,采用同步复制的方式可以保证可用性。(这是可用性方面,不太算可靠性方面,异步复制提升性能 但 降低可用性)
3、消费者可靠消费
- 消费成功返回ack提交offset,如果 Consumer 消费成功,返回 CONSUME_SUCCESS,提交 offset 并从 Broker 拉取下一批消息。
- 消费失败重试机制,如果消费逻辑中出现以下三种情况会触发消费重试,RocketMQ会在重试间隔时间后,将消息重新投递给Consumer消费,若达到最大重试次数(默认16)次后消息还没有成功被消费,则消息将被投递至死信队列, 由人工处理。
- 消费函数中抛异常
- return ConsumeConcurrentlyStatus.RECONSUME_LATER;
- return null;
注意:
- 重试16次后进入死信队列,进入死信队列后人工处理。
- 消费者要做好幂等处理
如果生产者一直发送失败怎么处理,重试之后还发送失败?
总共就两种方案,记录Mysql消息失败表或者打日志
- 方案一:消息发送失败表:发送失败后将消息保存到 MySQL 消息发送失败表中,通过定时任务等 RocketMQ 好过来后再进行重复消费。
如果高并发的消息发送 进行mysql消息表兜底,mysql可能会扛不住。
- 方案二:如果高并发场景下MySQL扛不住,采用打印日志的方式记录,后续读取日志恢复消息。
如果主broker挂了怎么办?(高可用相关)
设置主从broker,保证高可用,并且可以设置同步复制的保存机制(但是会降低吞吐量),确保主从broker消息一致性。
如果broker全都挂了怎么办?(高可用相关)
首先不太可能,一般集群都是异地多活
- 异地多活部署,保证集群高可用。
- 中间件/消息队列降级(redis Stream作为降级队列)。在生产者这边加一个熔断器,监听发送消息情况,如果xxx就打开熔断进行降级发送到redis,然后熔断器会有半开启状态,以及后续关闭或者开启状态。
重复消费,幂等
为什么会出现重复消息,重复消费?
为了保证可靠性,生产者只有在收到broker的ack后才会确保消息可靠投递,broker只有在收到消费者的offset的确认后才会确保消息可靠消费。
由于这个机制,那么可能出现
- 生产者重复生产,由于网络原因没有收到broker的ack,重复生产消息。
- 消费者重复消费,broker由于网络原因没有收到消费者的ack(提交offset),重复投递消息。
- 主从同步offset失败,主节点为来得及同步就挂了,导致从节点的offset未更新,重复消费。
如何防止重复消费?
短链接项目解决重复消费方案:知识星球 | 深度连接铁杆粉丝,运营高品质社群,知识变现的工具
RocketMQ会重复消费吗? - 古道轻风 - 博客园(这个文章很详细,讲了为什么会重复消费)
1)首先说下什么情况下会重复消费,参考下边学习文章。RocketMQ会重复消费吗? - 古道轻风 - 博客园
2)如何防止重复消费-马哥 其次说下如何保障消息队列不被重复消费,比如通过 Redis 和 MySQL 做消息表。另外,这个点会有很多细节,刚开始不要把细节说太多,面试官容易绕进去。保障一个大体逻辑,然后很多点当做问题一嘴带过即可。然后面试官感兴趣,自然会问你相关的细节。
首先如果要保证消息可靠,重复消息/幂等消息 其实是不可避免的(就算概率很低),因为要保证可靠就会有重试 (那可能消费者返回的ACK由于网络问题broker没有收到,那broker不就会重试吗,那这时候就出现了重复消息嘛) 。
如何防止消息重复消费?
设置一个消息 消费标识(类似于状态机)。如,即将消费set key 0,消费完成后 set key 1,消费失败删除key并xxx,类似于一个状态机。
- (这里细节很多,涉及未消费,消费中,已消费三个状态。)
- 未消费就是没有值,消费中置0,消费完成置0。
- 线程进来如果判断消费中需要抛异常重试,判断未消费可以抢占锁进行消费,判断已消费返回ack则不会重试和消费。
- 消费过程中,消费完成将状态置1,消费失败 删除标识 并抛异常后续重试。
那上述利用的消息表,消息如何选型?
消息表技术选型
- redis,优点快,缺点占内存(因为万一在指定过期时间内消息特别多,非常占内存)
- MySQL,用的磁盘,不担心存储。 缺点:不能自动过期(海量消息幂等问题,唯一索引解决幂等)
- 布隆过滤器。(误判怎么办?消费失败无法设置删除标识?)
幂等键的设计?
这个具体场景具体分析。也可选择消息id作为幂等键,在rocketMQ中可以自己设置消息的keys。
拓展:假如一个消息有五个步骤,执行前三个步骤成功,后两个步骤失败。如果解决这种消息重复消费幂等的问题?(米哈游)
- 最容易想到的事务,如果是同一个库的操作,采用mysql事务进行回滚。如果是分布式场景,采用分布式事务。
- 状态机。每个消息的消费步骤都是一个状态(通过Redis维护),每执行一个步骤就更新状态。如果消费失败,重新消费时可以接着上次的状态往后执行,跳过已经执行过的步骤。(类似于key是幂等键,value就是状态)
- 幂等套幂等,确保每个步骤的幂等性,可能的话采用数据库唯一索引进行兜底。
- 采用全局的思想,例如分布式事务这种思想。
顺序消费&并发消费
如何保证消息的顺序性?
两个维度,一个维度是全局有序 和 局部有序,第二个维度是从生产者消费者broker层面设置如何保证。
- 首先要看是全局有序还是局部有序,如果是全局有序那就只设置一个mq队列(同步发送消息并设置orderly消费),这样吞吐量会很低,一般不会全局有序。
- 如果是局部有序,那么在生产者同步发送 和 消费者orderly消费的基础上,可以有多个队列,生产者通过messageSelector将同一类型的消息放在同一队列保证局部有序。
如何保证有序,通过两个方面
- producer顺序同步发送(通过selector选择指定队列)
- consumer顺序消费(orderly)
局部有序
生产者
- 顺序投递消息,同步发送。(为什么要同步发送,因为只有同步发送才能保证先发的消息先到,如果异步,那么先发的消息可能后到broker)
- 同一类型的消息在同一mq中(例如同一订单的消息在一个队列中,如果监听binlog变化那么同一主键的binlog消息在同一mq中,即可以满足。)
消费者:
- 顺序消费消息,消费者 通过设置
new MessageListenerOrderly()进行顺序消费。
如何顺序投递消息呢?
- 同步发送。
- 保证同一类型消息在同一mq中。(根据key,hash取模)
拓展:new MessageListenerOrderly()进行顺序消费的原理,如何实现的?进阶源码
在消费同一个messagequeue时,保证消费顺序
RocketMQ快速入门:如何保证消息顺序消费,附带源码分析(八)_rocketmq怎么保证顺序消费-CSDN博客
大概就是通过对mq队列进 行加锁 ,保证同一时间,一个队列只有一个线程处理消息。
- 顺序消费也是通过线程池消费的,synchronized 锁用来保证同一时刻对于同一个队列只有一个线程去消费它
- 为什么也是线程池呢?因为同一消费者可能消费多个队列呀,这样线程池中的不同线程可以分别消费不同队列。
总结起来有三把锁
- 启动时,消费者和Messagequeue绑定加锁,一个consumer对应一个Messagequeue。
- 消费时,对Messagequeue加锁,同一时间只有一个线程消费。
- 保证不重复消费,还对ProcesQueue加锁。
拓展:MessageListenerConcurrently()并发消费底层原理?
简单讲讲
- 多线程消费同一个队列(processQueue) ,这个processQueue我理解为是messageQueue的本地副本,因为要消费的话肯定客户端本地也要存messagequeue的。
- 具体来说是线程池消费,知道是线程池就行
-
- 通讯框架回调线程会将数据存储在消费快照里,然后将消息列表 msgList 提交到消费消息服务
- 消息列表 msgList 组装成消费对象
- 将消费对象提交到消费线程池
消息积压
积压消息怎么办
分析消息挤压产生的原因:消费者太慢,生产者太快。
-
限流,让生产者慢一点。
-
增加队列和消费者数量,让消费者快消费,提高吞吐量。(注意是增加队列和消费者数量,如果只增加消费者可能出现空闲的现象,因为一个队列对应一个消费者实例)
- 如果行不通,那么需要解决消费者消费慢的根本问题
消息积压了,增加消费者一定有用吗?
阿里二面:RocketMQ 消息积压了,增加消费者有用吗?(这篇文章特别详细!!!1)
看情况:
- 如果消费者的数量小于 MessageQueue 的数量,增加消费者可以加快消息消费速度,减少消息积压。
- 还有一种情况,如果是广播消息,加消费者也没有用。因为每个消费者都收到全量的信息。
消息积压,且消费者数量小于队列数量,加消费者一定有用?
大部分情况有用,如果原因是消费者就是消费得很慢,那加了实例数量也不一定有用。(应该解决消费者消费慢的根本原因)
如何解决消费慢的根本问题?(在可以改代码的情况下)
- 使用多线程线程池消费。
- 如果是调用接口慢,不需要返回值的情况采用异步调用。
- 如果是访问数据库慢,那么解决sql问题,使用缓存数据库。
- 或者是网络等问题,解决网络问题。
如何解决消费慢的根本问题?(在不能改代码的情况下)
54-RocketMQ最佳实践-解决消息积压问题_哔哩哔哩_bilibili
另外创建一个消费者组其中只有一两个消费者,这两个消费者不做业务处理只转发,将消息转发到一个tempTopic,有更多的mq,并且创建有更多的consumer消费tempTopic。
假设现在有10个messageQueue10个consumer,还是积压
再创建一个临时topic叫tempTopic(10个mq),替换调5个consumer,在新建的consumer中将消息转发到tempTopic,这样再建10个consumer监听temTopic,这样总共就有15个consumer可以消费消息。
推/拉模式,Push/Pull
万字长文讲透 RocketMQ 的消费逻辑(这篇文章很全面,全面介绍了rocketMQ)
消费者 采用 推模式 还是 拉模式
- 推Push: 实时性好,但如果客户端没有做好流控,一旦服务端推送大量消息到客户端时,就会导致客户端消息堆积 甚至崩溃(推模式的消息积压主要积压在客户端,客户端积压会导致broker积压)。
- 拉Pull: 客户端按照自己消费速率在拉消息,但拉取的频率也需要用户自己控制,拉取频繁容易造成服务端和客户端的压力,拉取间隔长又容易造成消费不及时。
- 推常用的实现方式是:长连接/注册回调函数。
- 拉常用的实现方式是:轮询,长轮询。
- 优化: 但常用的实现中会采用长轮询的方式(长连接+轮询)。结合了长连接+轮询的优缺点。客户端先发一个长轮询请求拉,如果有消息直接返回,如果没有消息也不会立刻断开当前连接,然后等会,期间有消息会直接返回,期间没有消息时间到了就会断开连接。结束等待下次长轮询。
拉模式为什么要长轮询?
-
虽然拉模式的主动权在消费者这一侧,但是缺点很明显。
-
因为消费者并不知晓 Broker 端什么时候有新的消息 ,所以会不停地去 Broker 端拉取消息,但拉取频率过高, Broker 端压力就会很大,频率过低则会导致消息延迟。
-
长轮询可以减少拉消息的频率,同时降低消息延迟也比较低。
所以要想消费消息的延迟低,服务端的推送必不可少(长轮询底层也是服务端推?)。
有没有了解过为什么push的方式会多一点?
push使用起来更简单,pull使用起来复杂一些,但好处是相对灵活一些
消息模式
在同一个消费者组中有广播消息和集群消息。
-
广播模式下,队列中一条消息会发送给 同一个消费组中的所有消费者。 消费组内的每个消费者都会收到订阅Topic的全量消息,因此即使扩缩消费者数量也无法提升或降低消费能力。
-
集群模式下,队列中一条消息只会发送给(同一个消费者中的)一个消费者实例。整个消费组会Topic收到全量的消息,而消费组内的消费实例分担消费这些消息, 因此可以通过扩缩消费者数量,来提升或降低消费能力。
广播消息和集群消息是怎么实现的?(字节)
我知道的
广播消息的offset消费点位 保存在客户端(消费者)!!!!。
集群消息的offset消费点位 保存在服务端(broker)!!!!。
为什么?猜的
集群消息下,broker中的mq只需要为每个消费者组 维护offset。
但是广播消息,mq不可能为每个消费实例维护offset把?太多了,所以就在消费者方 保存了消费每个队列的offset。
扩展/其他
队列 和 消费者之间的关系是什么样的?
消费模式
- 广播消费,队列中一条消息会发送给 同一个消费组中的所有消费者
- 集群消费,队列中一条消息 只会 被 同一个消费组中的一个消费者消费。
消费者组的概念
一个topic被消费者组消费,一个队列对应 消费者中的一个消费实例。不同的消费者组 可以消费同一个topic,或者说不同消费者组的mq消费可以重复同一个队列。每个消费者组(实例)在每个队列上都维护自己的offset(消费偏移量) 。
不同的消费者实例可以消费同一个队列里面的消息吗?
在同一消费者组的情况下:
如果是集群模式,那么在同一个消费者组中不行,一个消费实例对应一至多个队列。
如果是广播模式,在同一个消费者组中可以。
不同消费者组
不同消费者组 中的实例 可以消费同一个队列,互不干扰。
那假如有两个消费实例,只有一个队列怎么办?一个实例消费,另外一个实例会空闲。
那假如有一个实例,但有两个队列呢?一个消费实例 会把两个队列都消费了。
拓展:现在有一个消息队列,只有一个messageQueue队列,然后上游发布消息的时候,消息里面有个user_id字段,为了提升性能,下游是多实例并发消费。设计一个方案,使得消息队列中,同一个user_id的消息是串行且顺序处理的,不同uid之间的消息可以不保证顺序,怎么设计和实现呢?(百度,难)
正常情况下,我们要保证同一user_id的消息顺序,肯定是弄多个队列,同一uid的消息在同一队列中,然后消费者顺序消费。
这个题是反着来的,只有一个队列,多实例也是并发地消费。
这种难的场景题 大致讲讲自己的几种解决思路,可以天马行空,不一定需要具体
- 想法一,加一层,让这个单队列消息推送到多队列MQ去,然后同一uid哈希到在同一队列中,然后多实例去消费中转的那个MQ。
- 想法二天马行空,考虑同一uid这些消息之间有没有一些前后联系,或者发送的时候给消息编号,多实例消费的时候引入redis中间件进行判断,消费的时候key为消息uid,value为消息序号。判断当前uid的消息消费到哪一条了,如果消费到3,这时候来的消息是5,那么我不应该消费这条消息。