RocketMQ 概述
架构
选型
消息队列 | Kafka | RocketMQ |
---|---|---|
适用场景 | 大量消息快速消费如流式计算 | 高性能、稳定、高可靠 |
热度 | 与周边生态系统的兼容性最好 | 有活跃中文社区 |
消息可靠传递 | √ | √ |
延迟 | 毫秒级 | 毫秒级 |
性能 | 每秒几十万 | 每秒几十万 |
消息丢失 | 参数优化配置后0丢失 | 参数优化配置后0丢失 |
消费模式 | Pull | Pull + Push(原理都是Pull) |
可用性 | 非常高(分布式) | 非常高(主从) |
topic数量对吞吐量的影响 | topic达到几十,几百个时,吞吐量会大幅度下降 | topic达到几百,几千个时,吞吐量会有较小幅度的下降 |
缺点:
- Kafka:同步收发消息的响应时延比较高,因为当客户端发送一条消息的时候,Kafka 并不会立即发送出去,而是要等一会儿攒一批再发送,在它的 Broker 中,很多地方都会使用这种“先攒一波再一起处理”的设计。当业务场景中,每秒钟消息数量没有那么多的时候,Kafka 的时延反而会比较高。所以,Kafka 不太适合在线业务场景。
- RocketMQ:没有太明显的缺点
部署结构
- 启动 NameServer,NameServer 起来后监听端口,等待 Broker、Producer、Consumer 连上来,相当于一个路由控制中心。
- Broker 启动,跟所有的 NameServer 保持长连接,定时发送心跳包。心跳包中包含当前 Broker 信息(IP+端口等)以及存储所有 Topic 信息。注册成功后,NameServer 集群中就有 Topic 跟 Broker 的映射关系。
- 收发消息前,先创建 Topic,创建 Topic 时需要指定该 Topic 要存储在哪些 Broker 上,也可以在发送消息时自动创建 Topic。
- Producer 发送消息,启动时先跟 NameServer 集群中的其中一台建立长连接,并从 NameServer 中获取当前发送的 Topic 存在哪些 Broker 上,轮询从队列列表中选择一个队列,然后与队列所在的 Broker 建立长连接从而向 Broker 发消息。
- Consumer 跟 Producer 类似,跟其中一台 NameServer 建立长连接,获取当前订阅 Topic 存在哪些 Broker 上,然后直接跟 Broker 建立连接通道,开始消费消息。
下图来自GitHub
推拉模型
推模式优缺点
实时性高
推送速率难以适应消费速率
不同消费者的消费速率很有可能不一样,Broker难以平衡每个消费者的推送速率,如果要实现自适应就会大大增加Broker自身的复杂度
因此推模式适用于消息量不大、消费能力强要求实时性高的情况下。
拉模式优缺点
消费者可以根据自己能力拉取消息处理,灵活稳定
可以更合适地进行消息的批量发送,基于推模式可以来一个消息就推送,也可以缓存一些消息之后再推送,但是推送的时候其实不知道消费者能不能一次性处理这么多消息。而拉模式可以根据消费者缓存能力决定拉取多少消息。
会造成消息的延迟消费,如果长时间没有消息,消费端不断轮询拉取,会造成一定时间的忙等,如果轮询时间过长,又会导致消息的延迟加大。
QA
消息队列可以做什么?
异步处理耗时任务
解耦上下游系统
削峰填谷
哪些消息队列可以做到在消息生产、消费过程中不重、不丢(Exactly once)?
Kafka、RocketMQ、RabbitMQ都没有实现这个需求,因为要实现Exactly once,除了重发外还需要做幂等,实现比较复杂,而且对性能影响比较大。
RocketMQ中的Consumer是推还是拉?
RocketMQ支持推和拉,但这两种方式实际上都是通过pull实现的,只是拉是同步的,而推是传个回调函数,当RocketMQ客户端接收到消息后再调用这个回调函数。
RocketMQ发送、存储、接收的流程?
当发现消费者不消费时,如何诊断问题?
- 检查连接状态,看消费者是否正常连接Broker;
- 看消费者是否有分配到ConsumeQueue,因为一个ConsumeQueue只能被一个消费者消费,所以消费者数量超过ConsumeQueue时,就会出现部分消费者没有ConsumeQueue可消费的情况;
- 生产者是否有正常消费,从控制台就可以看;
- 如果检查完以上步骤后仍然没有发现问题,则需要查看消费者的客户端日志再进一步分析。
怎么实现消息发送的严格顺序性?
RMQ中的分区算法指的就是把消息发到固定的某些队列上,因为同一队列只能被一个消费者消费,因此可以保证这个队列中消息的顺序性。
可选的分区算法如:
- 在表中存储key和分区的对应关系,通过查表确定分区号;
- 取模
RocketMQ能否做到单队列的并行消费?
RocketMQ 在消费的时候,为了保证消息的不丢失和严格顺序,每个队列只能串行消费(一个消费者可以消费多个队列),无法做到并发,否则会出现消费空洞的问题。那如果放宽一下限制,不要求严格顺序,能否做到单个队列的并行消费呢?
怎么实现负载均衡?
RocketMQ如何保证消息不丢(消息一定能被消费)?
- Producer端重试
默认push重试3次。 - Broker端只有在复制半数以上副本之后才会返回发送成功。
和MySQL里的semisync有点像。
- Consumer端重复消费
DefaultMQPushConsumer默认超时重试无限次,默认异常重试16次,过期或重试不成功则进入死信队列、默认凌晨 3 点会清除死信队列,为了确保重试不会出现重复消费,业务逻辑一般都需要保证幂等(幂等key可以使用业务oid或uniqId)。
Consumer有两种返回值,CONSUME_SUCCESS和CONSUME_LATER,后者令Broker将消息转移到另一个Retry队列中供重试使用。
RocketMQ如何实现消息去重?
RocketMQ本身没有实现消息的去重功能,因为RocketMQ是At-Least-Once的。
所以,很多时候我们需要自己通过Redis等来实现消息去重,但是要注意的是不要用错了msgId:
MessageExt.msgId
:重试时这个msgId是会变的,因此不适合当作幂等key;MessageExt.properties["PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX"]
:相对上面那个msgId来说,这个UNIQ_KEY就算重试多次值还是一样的,因此更适合当作幂等key。
消费失败怎么重发的?
怎么判断消息堆积了?
刷盘的原理?
- CommitLog
- ConsumeLog
怎么实现消息复制(Broker主从之间)?
RocketMQ 如何保证消息的高可用?
- NameServer 集群
NameServer集群节点没有Master、Slave之分,即使挂掉其中几台,其他的仍可提供服务。 - Broker多主多从
Broker支持多主多从集群,即使其中某台Master挂掉了,其他Master照样可以提供服务,而且挂掉的Master,其从节点照样可以通过选举得到一个新的Master。
broker集群的master宕机,slave是怎么提供服务的?master是怎么切换回来的?
为什么 RocketMQ 使用 NameServer 而不是 ZooKeeper 作为服务注册表
NameServer 具有高可用性,就算其中某台挂掉,其他服务器仍然能提供服务注册和查询功能。
ZooKeeper 的设计目标是高一致性,其中某台服务器挂掉,整个 ZooKeeper 集群就无法提供服务了——直到下一个Leader被选举出来为止。
NameServer是怎么感知Broker的变化的?
RocketMQ的事务消息是否完整实现了事务的ACID特性?
为什么要有Half Message?
- 可以先确认Broker服务器是否正常,如果半消息都发送失败了,就说明Broker挂了。
- 可以通过半消息来回查事务状态,如果半消息发出后一直没有被二次确认,就会回查事务状态。
事务回查有两种情况:1、由于网络等原因一直没有执行事务的commit和rollback;2、本地事务执行成功了,但是返回commit的时候服务挂了,Broker最终也没有收到消息,因此还是半消息状态,因此仍会进行重试。
为什么说RocketMQ只能保证最终一致性?
比如以一个转帐功能为例,A账户扣减、发MQ消息通知另一个服务增加B账户的余额,扣减和增加是在两个事务中执行的,MQ虽然能保证两个事务最终一定都能执行上,但是并不能保证中间状态不会出现,比如某个时刻A账户扣减了、但是B账户仍为原状。
RocketMQ使用某个消息序号messageID消费某个队列的消息,时间复杂度是多少?(假设消息文件commitLog数量为m,每个消息文件中消息条数是k,索引文件consumerQueue的数量是n,队列中共有j条消息)
复杂度是O(1),因为消息序号中包含了消息在commitLog中的偏移量,因此可以直接通过偏移量来拿到消息。