又来了,这是复盘第六波
女朋友面试4个月的面试题,本次是关于RocketMQ
的专题
在分布式系统中,消息队列是一个使用场景非常丰富的技术。我们通常会用来作为异步通信,系统解耦,海量请求或者数据的削峰填谷
,也可能使用延迟消息或者顺序消息
完成特殊场景的业务功能。
消息队列产品有Kafka,RocketMQ, rabbitmq, 是市面上使用比较多的一个中间件, 也是Java后端面试中命中率较高的专题。
还是老套路,高频的面试题已经标星,有面试需要的同学可以先点星星收藏起来
。
架构相关题目
- 说说RocketMQ架构组成有哪些?
哈哈,这个题目是不是就是传说中的送分题?我们如果使用过RocketMQ一般都可以回答出这个答案。
RocketMQ由生产者,消费者,broker,nameserver
四个组件组成。前面三者都会和nameserver通信,nameserver可以理解成注册中心,他会存储所有生产者,消费者,broker的信息。broker是支持高可用的,他支持集群和主从模式。我们脑海里如果记住有下面这幅图就能解答这个问题了。
rocketMQ官网也有这部分的这部分更加详细的介绍。认识RocketMQ
使用姿势
- 在集群消费模式下,16个消费者,8个写队列,有什么问题?
回答这个问题,需要知道Rocketmq负载均衡机制,默认使用平均分配策略,并且一个消费者最小消费的单元是队列
。所以如果消费者数量比队列数大的时候,大于队列的消费者是无法消费到消息的,下面是RocketMQ官网给出的图:
因此,我们一定要合理规划topic下的队列数量。
功能原理相关题目
- 顺序消息原理
这个问题需要从生产者和消费者,broker三端来解答,因为顺序消息需要三端配合。
- 生产者
生产者发送消息时候需要根据自己的业务规则,实现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);
- 消费者
消费端消费的时候,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个高频面试专题,后续会陆续更新成体系,成专题的面试题。