(RocketMQ)面试题解析

543 阅读7分钟

一、RocketMQ为什么自己实现一个注册中心而不用zk

先看看NameServer

1.Broker 向 NameServer 注册时,会注册到所有的 NameServer 服务器, NameServer 并不是分布式存储,NameServer 集群是去中心化的。
2.NameServer 会有定时任务(每 10s 一次)检查 Broker 是否下线了,判断依据是 120s 内有没有收到心跳,如果没有收到,则关闭 channel,把 Broker 信息从本地缓存移除。代码见 RouteInfoManager 类 scanNotActiveBroker 方法。
3.Broker 启动时,同时会启动定时任务,每 30s 向 NameServer 发送注册消息,NameServer 收到注册消息后更新心跳时间(BrokerLiveInfo.lastUpdateTimestamp)。

为什么放弃zk

RocketMQ本身是参考kafka设计的,在他早期也是引用的zk

轻量级

RocketMQ本身需要注册的数据比较少,zk是一个比较重量级的框架。

一致性

  • NameServer是去中心化的,相互之间不会建立通信连接,这样确实存在不一致的情况。
  • zk使用的zab协议来保证节点之间的数据强一致性,这要求在每一个写请求都需要在节点上写事务日志,同时需要将内存数据持久化到磁盘以保证一致性和持久性。
  • 放弃强一致性这样让NameServer设计更加简单化。

拓展性

NameServer 集群各节点是对等的,当集群有压力时,横向扩展非常容易。而 zk 在写扩展方面非常不灵活。

运维

如果使用zk,又增加了运维人员的学习成本,增加了系统复杂度。

二、生产者组和消费者组分别有什么用

生产者组

一个生产者组,代表着一群topic相同的Producer。即一个生产者组是同一类Producer的组合。主要作用在TransactionMQProducer(事务消息)。
假如现在有Producer_1,Producer_2,Producer_3组成一个生产者组, Producer_1发送事务消息,消息存储到broker的Half Message Queue中但还未存储到Topic的queue中,Producer_1发生宕机情况,则可以通过同一个组下的Producer_2进行二段式提交或者回溯。

消费者组

一个消费者组,代表着一群topic相同,tag相同(即逻辑相同)的Consumer。通过一个消费者组,则可容易的进行负载均衡以及容错。
使用时,一个节点下,一个topic加一个tag可以对应一个consumer。一个消费者组就是横向上多个节点的相同consumer为一个消费组。

三、RocketMQ如何保证集群高可用

Dledger是RocketMQ自4.5版本引入的实现高可用集群的一项技术。他基于Raft算法进行构建,在RocketMQ的主从集群基础上,增加了自动选举的功能。当master节点挂了之后,会在集群内自动选举出一个新的master节点。

四、RocketMQ如何保证消息不丢失

先考虑消息丢失场景

rocketMQ (1).png 在发消息,主从复制,写入缓存刷盘,收消息都存在丢失消息的可能性。

生产者发消息

为RocketMQ的事务消息机制就是为了保证零丢失来设计的,并且经过阿里的验证,肯定是非常靠谱的。 先看看事务消息的流程图

事务消息.png

1.为什么要发送half消息

half消息对下游服务的消费者是不可见的。那这个消息的作用更多的体现在确认RocketMQ的服务是否正常。相当于嗅探下RocketMQ服务是否正常,并且通知RocketMQ,即将有重要的消息到来。

2.half消息写入失败怎么办

假设当前业务是下单操作,下单之后通知下游服务进行后续处理。如果没有half消息,进行了下单操作之后去发送MQ。如果MQ发送失败就尴尬了。而half消息写入失败就可以任务mq当前状态是不可用的,这时,就不能通知下游服务了。我们可以在下单时给订单一个状态标记,然后等待MQ服务正常后再进行补偿操作,等MQ服务正常后重新下单通知下游服务。

3.执行本地事务失败怎么办

这个问题我们同样比较下没有使用事务消息机制时会怎么办?如果没有使用事务消息,我们只能抛出异常,那就不往MQ发消息了,这样至少保证不会对下游服务进行错误的通知。但是这样的话,如果过一段时间数据库恢复过来了,这个消息就无法再次发送了。
如果是写数据库失败(可能是数据库崩了,需要等一段时间才能恢复)。那我们可以另外找个地方把消息先缓存起来(Redis、文本或者其他方式),然后给RocketMQ返回一个UNKNOWN状态。这样RocketMQ就会过一段时间来回查事务状态。我们就可以在回查事务状态时再尝试把数据写入数据库,如果数据库这时候已经恢复了,那就能再继续后面的业务。这样这个消息就不会因为数据库临时崩了而丢失。

4.half消息写入后RocketMQ挂了怎么办

在事务消息的处理机制中,未知状态的事务状态回查是由RocketMQ的Broker主动发起的。也就是说如果出现了这种情况,那RocketMQ就不会回调到事务消息中回查事务状态的服务。而等RocketMQ恢复后,只要存储的消息没有丢失,RocketMQ就会再次继续状态回查的流程。

主从复制

在使用Dledger技术搭建的RocketMQ集群中,Dledger会通过两阶段提交的方式保证文件在主从之间成功同步。

刷盘问题

采用同步刷盘就可以保证消息在刷盘过程中不会丢失了。

同步刷盘

在返回写成功状态时,消息已经被写入磁盘。具体流程是,消息写入内存的PAGECACHE后,立刻通知刷盘线程刷盘,然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写成功的状态。

异步刷盘

在返回写成功状态时,消息只是被写入了内存的PAGECACHE,写操作的返回快,吞吐量大;当内存里的消息量积累到一定程度时,统一触发写磁盘操作,快速写入。

消费者接收消息

消费者端不要使用异步消费机制。

五、RocketMQ消息积压怎么处理

  1. 增加消费者数量到所对应的topic队列数量。
  2. 创建新的topic配置足够多的队列,然后把所有消费者节点的目标Topic转向新的Topic,并紧急上线一组新的消费者,只负责消费旧Topic中的消息,并转储到新的Topic中,这个速度是可以很快的。然后在新的Topic上,就可以通过增加消费者个数来提高消费速度了。之后再根据情况恢复成正常情况。

六、如果保证幂等性

做标记方案有很多:使用redis、mysql缓存一下消息的唯一id,每次消费的时候去判断是否被消费过。

七、单机版本中如何增加RocketMQ的吞吐量

增加队列和消费者

八、RocketMQ负载均衡策略

生产者

从MessageQueue列表中随机选择一个(默认策略),通过自增随机数对列表大小取余获取位置信息,但获得的MessageQueue所在的集群不能是上次的失败集群。
集群超时容忍策略,先随机选择一个MessageQueue,如果因为超时等异常发送失败,会优先选择该broker集群下其他的messeagequeue进行发送。如果没有找到则从之前发送失败broker集群中选择一个MessageQueue进行发送,如果还没有找到则使用默认策略。

消费者

1)平均分配策略(默认)(AllocateMessageQueueAveragely)

2)环形分配策略(AllocateMessageQueueAveragelyByCircle)

3)手动配置分配策略(AllocateMessageQueueByConfig)

4)机房分配策略(AllocateMessageQueueByMachineRoom)

5)一致性哈希分配策略(AllocateMessageQueueConsistentHash)

6)靠近机房策略(AllocateMachineRoomNearby)