复盘女朋友面试4个月的RocketMQ面试题

发布于:2024-05-01 ⋅ 阅读:(27) ⋅ 点赞:(0)

又来了,这是复盘第六波女朋友面试4个月的面试题,本次是关于RocketMQ的专题

在分布式系统中,消息队列是一个使用场景非常丰富的技术。我们通常会用来作为异步通信,系统解耦,海量请求或者数据的削峰填谷,也可能使用延迟消息或者顺序消息完成特殊场景的业务功能。

消息队列产品有Kafka,RocketMQ, rabbitmq, 是市面上使用比较多的一个中间件, 也是Java后端面试中命中率较高的专题。

还是老套路,高频的面试题已经标星,有面试需要的同学可以先点星星收藏起来

image.png

image.png

架构相关题目

  • 说说RocketMQ架构组成有哪些?

哈哈,这个题目是不是就是传说中的送分题?我们如果使用过RocketMQ一般都可以回答出这个答案。

RocketMQ由生产者,消费者,broker,nameserver四个组件组成。前面三者都会和nameserver通信,nameserver可以理解成注册中心,他会存储所有生产者,消费者,broker的信息。broker是支持高可用的,他支持集群和主从模式。我们脑海里如果记住有下面这幅图就能解答这个问题了。

7691B76D-3970-448D-ACA3-802461CCB7D9.png

rocketMQ官网也有这部分的这部分更加详细的介绍。

使用姿势

  • 在集群消费模式下,16个消费者,8个写队列,有什么问题?

回答这个问题,需要知道Rocketmq负载均衡机制,默认使用平均分配策略,并且一个消费者最小消费的单元是队列。所以如果消费者数量比队列数大的时候,大于队列的消费者是无法消费到消息的,下面是RocketMQ官网给出的图:

image.png

因此,我们一定要合理规划topic下的队列数量。

功能原理相关题目

  • 顺序消息原理

这个问题需要从生产者和消费者,broker三端来解答,因为顺序消息需要三端配合。

  1. 生产者

生产者发送消息时候需要根据自己的业务规则,实现MessageQueueSelector接口的select方法,计算当前消息路由到哪个队列去。


//队列负载均衡
public interface MessageQueueSelector {
    MessageQueue select(final List<MessageQueue> mqs, final Message msg, final Object arg);
}
//发送消息
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
                    @Override
                    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                        //arg 业务id
                        Integer id = (Integer) arg;
                        //生成订单 , 支付订单 保持顺序
                        System.out.println("订单id:"+id);
                        int index = id % mqs.size();
                        return mqs.get(index);
                    }
                }, orderId);

  1. 消费者

消费端消费的时候,consumer首先会向broker申请加锁,发送topic,queueId,clientId给broker,broker只会让这个消费者拉取对应队列的消息。

for (MessageQueue mq : mqs) {

if (this.isLocked(group, mq, clientId)) {
    lockedMqs.add(mq);
} else {
    notLockedMqs.add(mq);
}
}

真正消费的地方,consumer还会加锁。

先对MessageQueue加锁

public Object fetchLockObject(final MessageQueue mq) {
        Object objLock = this.mqLockTable.get(mq);
        if (null == objLock) {
            objLock = new Object();
            Object prevLock = this.mqLockTable.putIfAbsent(mq, objLock);
            if (prevLock != null) {
                objLock = prevLock;
            }
        }

        return objLock;
    }

真正处理消息前对processQueue加锁,保证当前消息队列Queue只被1个线程处理

try {
      //对处理队列加锁
      this.processQueue.getLockConsume().lock();
      //调用业务逻辑处理消息
      status = messageListener.consumeMessage(Collections.unmodifiableList(msgs), context);
  } catch (Throwable e) {
      hasException = true;
  } finally {
      //处理队列解锁
      this.processQueue.getLockConsume().unlock();
  }

顺序消息的实现是比较复杂的,因为涉及到锁和线程并发控制,源码需要多读几次才能理解。面试时我们要注重讲解核心要点。

  • 事务消息原理

生产者发送事务消息(这是半消息,无法被消费者消费的)之后,开始执行本地事务,根据本地事务执行情况,告诉broker本地事务结果,要么成功,要么失败,如果是成功,那么broker会将事务消息还原到真实的topic和队列,如果是失败,broker会将事务消息(半消息)删除。

这里如果生产者没有告诉broker本地事务的执行结果,broker有一个兜底的定时任务,broker启用一个线程,扫描事务消息topic里的队列里面的消息,判断是否需要检查事务状态(最大检查15次)。

面试的时候回答还是要简洁一些,首先要把主流程讲解出来,其实这个细节原理在RocketMQ的实现内部也是比较复杂的。

高级特性

  • RocketMQ为什么这么快?

这个问题好像似曾相识,因为很多时候面试官喜欢问一些归类总结性的问题,这个问题就是这样,可以判断候选者对技术有没有自己分析总结的能力,这种题目其实没有标准答案,需要了解技术实现的情况下,进行分析总结。

我们还是需要从生产者,消费者,broker来分析

首先是生产者,生产者发送消息支持同步,支持异步,还支持oneway,oneway效率最高,因为他不用等broker返回。

在broker端,消息由索引和消息内容数据两部分组成,消息内容先需要写到commitlog,先写到pagecache,再刷新到磁盘,由于commitlog是顺序写的,而刷盘支持异步刷,这样性能是极高的。

如果是消费者从broker拉取消息, 先查询索引数据consumerQueue,这些索引数据占用空间小,通过页缓存读取,并且本地有缓存机制,读取性能也非常高,读取文件使用到零拷贝mmap技术提高性能,而且消费者和broker保持长轮训机制,使新消息到达可以快速投递到消费端。

最后在消费者这边支持并发多线程消费,将topic和队列信息会进行本地缓存,同时和broker保持长链接,能够保证及时接收到最新的消息。

总结

RocketMQ专题的知识其实很多,底层涉及的一些技术,比如rpc通信,编码解码,零拷贝,刷盘机制,主从复制,负载均衡算法等是大部分中间件通用的一些技术点,如果问这个专题的面试题,有很多题可能被问到。因此我们需要掌握实现机制,才能从容面对面试官的灵魂发问。

如果大家对其他专题的面试题感兴趣,可以关注我的,里面已经有5个高频面试专题,后续会陆续更新成体系,成专题的面试题。


网站公告

今日签到

点亮在社区的每一天
去签到