rocketmq 面试

首先推荐一个java架构师修炼实战博客,还包含教你如何手写JVM:

Java架构师修炼

1.为什么要用到MQ?

解耦
有这么个场景,A系统要发数据到BCD三个系统,如果是接口调用,挂了一个系统,要不要重发,要不要把消息存起来。这些都要A系统来协助,假如B系统没有收到消息没关系,C系统必须要收到消息,就得让A重发,这些逻辑都堆到A系统来了,很不解耦。

如果让A先把消息发到MQ里面,让BCD自己选择是否要重试,新加E系统也只需接入MQ就行,跟A系统毫无关系了

异步
有些业务场景如后台导出,后台需要查库出数据然后生成excel然后存储到磁盘,过程相当漫长, 我们可以利用发出一个异步MQ,这样用户端立即返回,还可以做其他事情。

削峰
有些秒杀场景,系统的瞬间流量相当高,如果还操作mysql了那就GG了,MQ可以提供消息的堆积,据说单个队列可以达到上百万堆积量,然后消费者平滑消费,这样就达到了流量削峰,不会导致mysql等崩溃。

蓄流压测
线上有些链路不好压测,可以通过堆积一定量消息再放开来压测。

2.MQ整体架构

基本架构
在这里插入图片描述

NameSrv简介

  • namesrv的定位是作为注册中心,保存broker节点的路由信息,保存一些简单的k/v信息。
  • namesrv通过提供对外接口给访问broker的路由信息,底层通过netty来实现。
  • namesrv支持集群模式,但是每个namesrv之间相互独立不进行任何通信,它的多点容灾通过producer/consumer在访问namesrv的时候轮询获取信息(当前节点访问失败就转向下一个)。
  • namesrv作为注册中心,负责接收broker定期的注册信息并维持在内存当中,没错namesrv是没有持久化功能的,所有数据都保存在内存当中,broker的注册过程也是循环遍历所有namesrv进行注册。

Producer简介

消息生产者,用于向消息服务器发送消息;Producer与NameSrv集群中一台建立长链接,定期从NameSrv取Topic路由信息,并向提供Topic服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。

Broker简介
每个Broker与NameSrv集群中的所有节点建立长连接,定时注册Topic信息到所有NameSrv。brocker可主从部署。

  • Consumer:消息消费者,该流程图中未涉及;

Topic路由注册和剔除过程

  • Broker 每30s向 NameServer发送心跳包,心跳包中包含主题的路由信息(主题的读写队列数、操作权限等),NameServer 会通过 HashMap 更新 Topic的路由信息,并记录最后一次收到 Broker 的时间戳。
  • NameServer 以每10s的频率清除已宕机的 Broker,宕机的依据是如果当前系统时间戳减去最后一次收到 Broker 心跳包的时间戳大于120s。
  • 消息生产者以每30s的频率去拉取主题的路由信息,即消息生产者并不会立即感知 Broker 服务器的新增与删除。

由于消息生产者无法实时感知 Broker 服务器的宕机,那消息发送的高可用性如何保证呢?

消息发送高可用设计
在这里插入图片描述
消息发送队列负载默认采用轮询机制,消息发送时默认选择重试机制来保证消息发送的高可用。
当 Broker 宕机后,虽然消息发送者无法第一时间感知 Broker 宕机,但是当消息发送者向 Broker 发送消息返回异常后,生产者会在接下来一定时间内,例如5分钟内不会再次选择该 Broker上的队列,这样就规避了发生故障的 Broker,结合重试机制,巧妙实现消息发送的高可用。
以上也有可能导致消息没法送到brocker(重试次数达到),我们可以将失败的存储到DB,定时器轮询,直到发送到brocker成功。

消息文件存储结构
CommitLog
存储Producer端写入的消息主体内容,单个文件大小默认1G ,消息是顺序写入日志文件,当文件满了,写入下一个文件;
commitLog结构图:
在这里插入图片描述

ConsumeQueue
磁盘路径:{alibaba-rocketmq}/store/consumequeue/{topic} 可以通过topic来定位。

消息消费队列,引入的目的主要是提高消息消费的性能,如果没有ConsumeQueue, 那么消费时就要遍历commitlog文件来根据topic检索消息,这是非常低效的。

ConsumeQueue(逻辑消费队列)作为消费消息的索引,保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量offset,消息大小size和消息Tag的HashCode值。

与 CommitLog 不同,ConsumeQueue 的存储条目采用定长存储结构,如下图所示。为了实现定长存储,ConsumeQueue 存储了消息 Tag 的 Hash Code,在进行 Broker 端消息过滤时,通过比较 Consumer 订阅 Tag 的 HashCode 和存储条目中的 Tag Hash Code 是否一致来决定是否消费消息。

ConsumeQueue 结构图:
在这里插入图片描述
消息存储,接收过程

  • Producer 将消息发送到 Broker 后,Broker 会采用同步或者异步的方式把消息写入到 CommitLog,对 CommitLog 写之前会加锁,同时也可以使得消息能够被顺序写入到 CommitLog。
  • CommitLog 持久化后,会把里面的消息 Dispatch 到对应的 Consume Queue 上,Consume Queue存储了这个 Queue 在 CommitLog 中的起始 Offset,log 大小和 MessageTag 的 hashCode。
  • 当消费者进行消息消费时,会先读取 ConsumerQueue
    得到在CommitLog的偏移量,然后读取CommitLog 。

读写消息高效原因
rocketmq写入消息时,并不是写到磁盘才成功,而是 写入一个叫PageCache 的缓存中,然后由定时器去fush到磁盘。而且rocketmq采用mmap技术,不需要将文件中的数据先拷贝至OS的内核IO缓冲区,而是直接将用户进程私有地址空间中的一块区域与文件对象建立映射关系,这样程序就好像可以直接从内存中完成对文件读/写操作一样。
一般来说,一次只能映射1.5~2G 的文件至用户态的虚拟内存空间,这也是为何RocketMQ默认设置单个CommitLog日志数据文件为1G的原因了;

Producer消息路由
一个topic下,我们可以设置多个MessageQueue,当一条消息发送时,Producer 会根据topic获取所有的MessageQueue,然后根据一个路由的object类型参数的hash code取模MessageQueue的个数得到要发送到的MessageQueue。之所以不在消息服务器(brocker)上做是因为这样可以让用户自己决定该如何路由消息,具有更大的灵活性。
如果没有这个object参数,默认会轮询所有的Message Queue发送,以实现消息平均落在不同的Broker上。如下图:
在这里插入图片描述

Consumer Rebalance
消息队列系统中,经常会出现 Broker 实例的增删、Topic 的增减、Topic 下 MessageQueue 数目的增减、消费组实例数目的增减等情况,它们都会触发消费关系的重新分配,这个过程称之为 Rebalance。

RocketMQ 的 Rebalance 机制有主动和被动之分,主动意为消费者实例每隔 20s 会定时计算自己的消费拓扑并和内存中的对比,一旦发现部分 MessageQueue 不再是自己负责消费,则停止对它的消息拉取任务;如果有新的 MessageQueue 变为自己负责,则创建对它的消息拉取任务。

被动意为,Broker 可以主动通知某个消费组下的所有实例,要求它们立即开始一次 Rebalance,常用于新的消费者实例加入、或者 Broker 检测到有消费者实例心跳失联等情况,下面是一个消费者实例新加入的场景。
在这里插入图片描述
以上是集群模式时的Rebalance, 且这个模式只对于一个ConsumerGroup才起作用,如果是广播模式则应该是:
在这里插入图片描述
每台consumer机器都订阅到相关topic的所有queue,这样就会让每台consumer机器都消费到。

3.MQ几个题目

1.Rocketmq怎么保证消息的有序性?

生产者端处理: Producer端发送时,指定MessageSelector,算出将消息放到哪个queue
在这里插入图片描述
消费端处理: 如何保证消费端 顺序消费? 如果是使用MessageListenerOrderly(**顺序消费)**则自带顺序消费实现,如果是使用MessageListenerConcurrently(并发消费),则需要把线程池改为单线程模式。

MessageListenerOrderly(顺序消费):有序消费,同一队列的消息同一时刻只能一个线程消费,可保证消息在同一队列严格有序消费
MessageListenerConcurrently(并发消费):

2.Rocketmq怎么保证消息一定可达,不丢失?
  1. 怎么保证Producer发送消息阶段不丢失?
    rocketmq提供了三种方式发送消息
    在这里插入图片描述
    同步发送: Producer 向 broker 发送消息,阻塞当前线程等待 broker 响应 发送结果。
    异步发送: Producer 首先构建一个向 broker 发送消息的任务,把该任务提交给线程池,等执行完该任务时,回调用户自定义的回调函数,执行处理结果。
    Oneway发送: Oneway 方式只负责发送请求,不等待应答,Producer 只负责把请求发出去,而不处理响应结果。
    我们使用同步发送返送,并且捕获返回结果进行重试,可以减小消息发送丢失。

  2. 怎么保证Conusmer消费消息不丢失?
    PushConsumer为了保证消息肯定消费成功,只有使用方明确表示消费成功,RocketMQ才会认为消息消费成功。中途断电,抛出异常等都不会认为成功——即都会重新投递。
    ConsumeConcurrentlyStatus.CONSUME_SUCCESS

  3. 怎么保证brocker存储消息不丢失?
    采用同步刷盘模式,当刷盘成功后才返回producer投递消息成功。

3.怎么保证最终一致性?

事务消息

  1. 发送方向 MQ 服务端发送消息。
  2. MQ Server 将消息持久化成功之后,向发送方 ACK 确认消息已经发送成功,此时消息为半消息。
  3. 发送方开始执行本地事务逻辑。
  4. 发送方根据本地事务执行结果向 MQ Server 提交二次确认(Commit 或是 Rollback),MQ Server 收到Commit 状态则将半消息标记为可投递,订阅方最终将收到该消息;MQ Server 收到 Rollback 状态则删除半消息,订阅方将不会接受该消息。
  5. 在断网或者是应用重启的特殊情况下,上述步骤4提交的二次确认最终未到达 MQ Server,经过固定时间后MQ Server 将对该消息发起消息回查。
  6. 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
  7. 发送方根据检查得到的本地事务的最终状态再次提交二次确认,MQ Server 仍按照步骤4对半消息进行操作。

Producer Group
标识发送同一类消息的Producer,通常发送逻辑一致。发送普通消息的时候,仅标识使用,并无特别用处。若事务消息,如果某条发送某条消息的producer-A宕机,使得事务消息一直处于PREPARED状态并超时,则broker会回查同一个group的其 他producer,确认这条消息应该commit还是rollback。但开源版本并不支持事务消息。

4.consumer的消费模式?
  1. 广播消费
    广播消费是指一个consumer只要订阅了某个topic的消息,那它就会收到该topic下的所有queue里的消息,而不管这个consumer的group是什么。所以对于广播消费来说,consumer group没什么实际意义
consumer.setMessageModel(MessageModel.BROADCASTING)
  1. 集群消费
    集群消费是指,一个consumer group下的consumer,平均消费topic下的queue。假如一个topic下有4个queue,然后当前有一个consumer group,该分组下有4个consumer,那每个consumer就被分配到该topic下的一个queue,这样就达到了平均消费topic下的queue的目的。
 consumer.setMessageModel(MessageModel.CLUSTERING);

推荐一个java架构师修炼实战博客,还包含教你如何手写JVM:

Java架构师修炼

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值