消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]

写在开始 :
本文合计2万多字, 500多行, 阅读可能需要花费一点时间;
主要包括消息队列和 常用MQ(比如RabbitMQ, RocketMQ 和 Kafka)的部分高频面题, 可供复习参考使用

导读

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第1张图片

一. RabbitMQ

如何保证消息不丢失

日常应用场景: 异步发送(验证码、短信、邮件==),MySQL和Redis、ES之间的数据同步、分布式事务、削峰填谷等等
消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第2张图片

生产者确认机制

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第3张图片

消息持久化

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第4张图片

消费者确认

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第5张图片

小结

  • 开启生产者确认机制,确保生产者的消息能到达队列
  • 开启持久化功能,确保消息被消费前在队列不会丢失
  • 开启消费者确认机制为 auto,由spring确认消息处理成功后完成ack
  • 开启消费者失败重试机制,多次重试失败后将消息投递到异常交换机,交由人工处理

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第6张图片

发送⽅确认机制:信道需要设置为 confirm 模式,则所有在信道上发布的消息都会分配⼀个唯⼀ ID。⼀旦消息被投递到queue(可持久化的消息需要写⼊磁盘),信道会发送⼀个确认给⽣产者(包含消息唯⼀ ID)。如果 RabbitMQ 发⽣内部错误从⽽导致消息丢失,会发送⼀条 nack(未确认)消息给⽣产者。所有被发送的消息都将被 confirm(即 ack) 或者被nack⼀次。但是没有对消息被 confirm 的快慢做任何保证,并且同⼀条消息不会既被 confirm⼜被nack 发送⽅确认模式是异步的,⽣产者应⽤程序在等待确认的同时,可以继续发送消息。当确认消息到达⽣产者,⽣产者的回调⽅法会被触发。
ConfirmCallback接⼝:只确认是否正确到达 Exchange 中,成功到达则回调
ReturnCallback接⼝:消息失败返回时回调
接收⽅确认机制:消费者在声明队列时,可以指定noAck参数,当noAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存(或者磁盘,持久化消息)中移去消息。否则,消息被消费后会被⽴即删除。
消费者接收每⼀条消息后都必须进⾏确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ 才能安全地把消息从队列中删除。
RabbitMQ不会为未ack的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯⼀依据是消费该消息的消费者连接是否已经断开。这么设计的原因是RabbitMQ允许消费者消费⼀条消息的时间可以很⻓。保证数据的最终⼀致性;
如果消费者返回ack之前断开了链接,RabbitMQ 会重新分发给下⼀个订阅的消费者。(可能存在消息重复消费的隐患,需要去重)

消息的重复消费问题如何解决

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第7张图片

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第8张图片

死信交换机(延迟队列)

延迟队列 : 进入队列的消息会被延迟消费的队列
场景 : 超时订单 限时优惠 定时发布。。。
延迟队列 = 死信交换机 + TTL(生存时间)
消息超时未消费就会变成死信(死信的其他情况:拒绝被消费,队列满了)
消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第9张图片
消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第10张图片
消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第11张图片
延迟队列插件实现延迟队列 DelayExchange

  • 声明一个交换机,添加 delayed 属性 为 true
  • 发送消息时候, 添加 x-delay 头,值为超时时间

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第12张图片

1. 消息被消费⽅否定确认,使⽤ channel.basicNack 或 channel.basicReject ,并且此时
requeue 属性被设置为 false2. 消息在队列的存活时间超过设置的TTL时间。
3. 消息队列的消息数量已经超过最⼤队列⻓度。
    
那么该消息将成为“死信”。“死信”消息会被RabbitMQ进⾏特殊处理,如果配置了死信队列信息,那么
该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃
    
为每个需要使⽤死信的业务队列配置⼀个死信交换机,这⾥同⼀个项⽬的死信交换机可以共⽤⼀个,然
后为每个业务队列分配⼀个单独的路由key,死信队列只不过是绑定在死信交换机上的队列,死信交换
机也不是什么特殊的交换机,只不过是⽤来接受死信的交换机,所以可以为任何类型【DirectFanoutTopic】
    
TTL:⼀条消息或者该队列中的所有消息的最⼤存活时间
    
如果⼀条消息设置了TTL属性或者进⼊了设置TTL属性的队列,那么这条消息如果在TTL设置的时间内
没有被消费,则会成为“死信”。如果同时配置了队列的TTL和消息的TTL,那么较⼩的那个值将会被使
⽤。
    
只需要消费者⼀直消费死信队列⾥的消息

消息堆积怎么解决

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第13张图片
消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第14张图片

三种解决思路:

  • 增加消费者,提高消费速度
  • 消费者开启线程池加快消息处理速度
  • 扩大队列容积,提高堆积上限,采用惰性队列
    • 在声明队列时设置属性 x-queue-mode 为 lazy,即惰性队列
    • 基于磁盘存储,消息上限高
    • 性能比较稳定,但基于磁盘存储,受限于磁盘IO,时效性降低;

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第15张图片

高可用机制

生产环境下,使用集群保证高可用性
普通集群、镜像集群、仲裁队列
消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第16张图片
消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第17张图片
消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第18张图片

高可用机制

生产环境下,采用的镜像模式搭建的集群,共有3个节点
镜像队列结构是一主多从(从就是镜像),所有操作都是主节点完成,然后同步给镜像节点;
主宕机后,镜像节点会替代成新的主(如果主从同步前,主机已经宕机,可能出现数据丢失)

出现数据丢失怎么解决

采用仲裁队列, 也是主从模式,支持主从数据同步,基于 Raft 协议,强一致.并且使用也简单,不需要额外配置,在声明队列的时候只要指定这个是仲裁队列即可
消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第19张图片

镜像queue有master节点和slave节点。master和slave是针对⼀个queue⽽⾔的,⽽不是⼀个node作为
所有queue的master,其它node作为slave。⼀个queue第⼀次创建的node为它的master节点,其它
node为slave节点。
    
⽆论客户端的请求打到master还是slave最终数据都是从master节点获取。当请求打到master节点时,
master节点直接将消息返回给client,同时master节点会通过GM(Guaranteed Multicast)协议将
queue的最新状态⼴播到slave节点。GM保证了⼴播消息的原⼦性,即要么都更新要么都不更新。
    
当请求打到slave节点时,slave节点需要将请求先重定向到master节点,master节点将将消息返回给
client,同时master节点会通过GM协议将queue的最新状态⼴播到slave节点。
    
    如果有新节点加⼊,RabbitMQ不会同步之前的历史数据,新节点只会复制该节点加⼊到集群之后新增
的消息。

常见消息模型

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第20张图片
消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第21张图片
消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第22张图片
消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第23张图片

RabbitMQ的架构设计

  • Broker:rabbitmq的服务节点
  • Queue:队列,是RabbitMQ的内部对象,⽤于存储消息。RabbitMQ中消息只能存储在队列中。⽣产者

投递消息到队列,消费者从队列中获取消息并消费。多个消费者可以订阅同⼀个队列,这时队列中的消
息会被平均分摊(轮询)给多个消费者进⾏消费,⽽不是每个消费者都收到所有的消息进⾏消费。(注意:RabbitMQ不⽀持队列层⾯的⼴播消费,如果需要⼴播消费,可以采⽤⼀个交换器通过路由Key绑定多个
队列,由多个消费者来订阅这些队列的⽅式。

  • Exchange:交换器。⽣产者将消息发送到Exchange,由交换器将消息路由到⼀个或多个队列中。如果

路由不到,或返回给⽣产者,或直接丢弃,或做其它处理。

  • RoutingKey:路由Key。⽣产者将消息发送给交换器的时候,⼀般会指定⼀个RoutingKey,⽤来指定

这个消息的路由规则。这个路由Key需要与交换器类型和绑定键(BindingKey)联合使⽤才能最终⽣效。
在交换器类型和绑定键固定的情况下,⽣产者可以在发送消息给交换器时通过指定RoutingKey来决定消
息流向哪⾥。

  • Binding:通过绑定将交换器和队列关联起来,在绑定的时候⼀般会指定⼀个绑定键,这样RabbitMQ就

可以指定如何正确的路由到队列了。
交换器和队列实际上是多对多关系。就像关系数据库中的两张表。他们通过BindingKey做关联(多对多
关系表)。在投递消息时,可以通过Exchange和RoutingKey(对应BindingKey)就可以找到相对应的队
列。

  • 信道:信道是建⽴在Connection 之上的虚拟连接。当应⽤程序与Rabbit Broker建⽴TCP连接的时候,

客户端紧接着可以创建⼀个AMQP 信道(Channel) ,每个信道都会被指派⼀个唯⼀的D。RabbitMQ 处
理的每条AMQP 指令都是通过信道完成的。信道就像电缆⾥的光纤束。⼀条电缆内含有许多光纤束,允
许所有的连接通过多条光线束进⾏传输和接收。

RabbitMQ事务消息

通过对信道的设置实现

  1. channel.txSelect();通知服务器开启事务模式;服务端会返回Tx.Select-Ok
  2. channel.basicPublish;发送消息,可以是多条,可以是消费消息提交ack
  3. channel.txCommit()提交事务;
  4. channel.txRollback()回滚事务;
    消费者使⽤事务:
  5. autoAck=false,⼿动提交ack,以事务提交或回滚为准;
  6. autoAck=true,不⽀持事务的,也就是说你即使在收到消息之后在回滚事务也是于事⽆补的,队列已经把消息移除了
    如果其中任意⼀个环节出现问题,就会抛出IoException异常,⽤户可以拦截异常进⾏事务回滚,或决定
    要不要重复消息。
    事务消息会降低rabbitmq的性能

二. Kafka

Kafka 的消息丢失

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第24张图片
Q ; 发送消息失败?
A : 设置异步发送, 发送失败使用回调进行记录或重发

// 同步发送
RecordMetadata recordMetadata = kafkaProducer.send(record).get();
// 异步发送
kafkaProducer.send(record,new Callback(){
    @Override
    public void onCompletion(RecordMetadata recordMetadata,Exception e){
        if(e != null){System.out.print("消息发送失败日志记录");}
        long offset = recordMetadata.offset();
        int partition = recordMetadata.partition();
        String topic = recordMetadata.topic();
    }
});

消息重试 : 失败重试,参数配置,可设置重试次数

// 设置重试次数
prop.put(ProducerConfig.RETRIES_CONFIG,10);

Q : 消息在 Brocker 存储中丢失
发送确认机制 acks , 选择 all,让所有副本参与保存数据后确认
消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第25张图片

消费者从 Brocker 接收消息丢失

  • 关闭自动提交偏移量,开启手动提交偏移量
  • 提交方式,最好是同步+异步提交

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第26张图片

消息的重复消费问题如何解决

  • 关闭自动提交偏移量,开启手动提交偏移量
  • 提交方式,最好是同步 +异步提交
  • 幂等方案

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第27张图片

保证消息的顺序性

场景:
即时消息中的单对单聊天和群聊,保证发送方消息发送顺序和接收方的顺序一致
充值转账两个渠道在同一时间进行余额变更,短信通知必须有序

原因 :
一个 topic 数据可能存储在不同分区,每个分区都有一个按照顺序的存储的偏移量,如果消费者关联了多个分区不能保证顺序性
解决方案:
发送消息时候指定分区号
发送消息按照相同业务设置相同的 key

Kafka 默认存储和消费消息,不能保证顺序性,因为一个 topic 数据肯存在不同的分区,每个分区都有一个
按照顺序存储的偏移量,如果消费者关联多个分区不能保证顺序性;

如果有这样需求,可以把消息存储同一个分区下,
第一个是发送消息时候指定分区号,第二个是发送消息按照相同业务设置相同 key ,因为默认情况分区是
    通过 key 的 hashcode 值来选择分区, hash 值如果一样,分区肯定也一样

高可用机制

集群模式
一个Kafka集群由多个broker实例组成,即使某一台宕机,也不耽误其他 broker 继续对外提供服务
分区备份机制(复制机制)
一个 topic 有多个分区,每个分区有多个副本,一个leader,其余是 follower, 副本存储在不同的 broker
所有分区副本内容相同,如果 leader 故障,自动将其中一个 follower 提升为 leader,保证系统容错性高可用

解释一下复制机制的 ISR
ISR (in-sync replica) 需要同步复制保存的 follower
分区副本分为两类,一个是 ISR ,和 leader 副本同步保存数据,另一个普通副本, 异步同步数据,当 leader挂掉,优先从 ISR 副本列表选取一个作为 leader;
消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第28张图片

数据清理机制

文件存储机制(存储结构)
Kafka 里面 topic 数据存储在分区上,分区如果文件过大会分段存储 segment
每个分段都在磁盘以索引 (.index) 和日志文件 (.log) 形式存储
分段好处是,第一能减少单个文件内容大小,查找数据方便,第二方便 Kafka 进行日志清理

日志清理策略有两个:
根据消息保留时间,当消息保存时间超过指定时间,触发清理,默认168h (7天)
根据 topic 存储的数据大小,当 topic 所占日志文件大小超过阈值,开始删除最久的消息(默认关闭)
这两个策略都是通过 Kafka 的 broker 的配置文件进行设置

高性能设计

  1. **消息分区: ** 不受单台服务器限制,不受限的处理更多数据
  2. **顺序读写: ** 磁盘,提升读写效率
  3. **页缓存: ** 磁盘的 data 缓存到内存,变成对内存的访问
  4. **0拷贝 : ** 减少上下文切换以及数据拷贝
  5. 消息压缩 : 减少磁盘IO 和 网络IO
  6. 分批发送 : 消息打包批量发送,减少网络开销

总而言之 : 多方协同结果,包括宏观架构,分布式储存, ISR数据同步,以及高效利用磁盘,操作系统特性等等,主要有以上六方面;

零拷贝

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第29张图片
消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第30张图片

Kafka是什么

Kafka 是⼀种⾼吞吐量、分布式、基于发布/订阅的消息系统,最初由 LinkedIn 公司开发,使⽤Scala
语⾔编写,⽬前是 Apache 的开源项⽬。
    broker:Kafka 服务器,负责消息存储和转发
    topic:消息类别, Kafka 按照 topic 来分类消息
    partition:topic 的分区,⼀个 topic 可以包含多个 partition,
topic 消息保存在各个partition 上
    offset:消息在⽇志中的位置,可以理解是消息在 partition 上的偏移
量,也是代表该消息的唯⼀序号
    Producer:消息⽣产者
    Consumer:消息消费者
    Consumer Group:消费者分组,每个 Consumer 必须属于⼀个 group
    Zookeeper:保存着集群 broker、 topic、 partition等 meta 数据;另外,还负责 broker 故障
    发现, partition leader 选举,负载均衡等功能

Kafka为什么吞吐量⾼

Kafka的⽣产者采⽤的是异步发送消息机制,当发送⼀条消息时,消息并没有发送到Broker⽽是缓存起
来,然后直接向业务返回成功,当缓存的消息达到⼀定数量时再批量发送给Broker。这种做法减少了⽹
络io,从⽽提⾼了消息发送的吞吐量,但是如果消息⽣产者宕机,会导致消息丢失,业务出错,所以理
论上kafka利⽤此机制提⾼了性能却降低了可靠性。

Kafka的Pull和Push分别有什么优缺点

  1. pull表示消费者主动拉取,可以批量拉取,也可以单条拉取,所以pull可以由消费者⾃⼰控制,根据
    ⾃⼰的消息处理能⼒来进⾏控制,但是消费者不能及时知道是否有消息,可能会拉到的消息为空
  2. push表示Broker主动给消费者推送消息,所以肯定是有消息时才会推送,但是消费者不能按⾃⼰的
    能⼒来消费消息,推过来多少消息,消费者就得消费多少消息,所以可能会造成⽹络堵塞,消费者
    压⼒⼤等问题

为什么要使⽤ kafka,为什么要使⽤消息队列?

**缓冲和削峰 **:上游数据时有突发流量,下游可能扛不住,或者下游没有⾜够多的机器来保证冗余,
kafka在中间可以起到⼀个缓冲的作⽤,把消息暂存在kafka中,下游服务就可以按照⾃⼰的节奏进⾏慢
慢处理。
解耦和扩展性 :项⽬开始的时候,并不能确定具体需求。消息队列可以作为⼀个接⼝层,解耦重要的业
务流程。只需要遵守约定,针对数据编程即可获取扩展能⼒。
冗余 :可以采⽤⼀对多的⽅式,⼀个⽣产者发布消息,可以被多个订阅topic的服务消费到,供多个毫⽆关联的业务使⽤。
健壮性 :消息队列可以堆积请求,所以消费端业务即使短时间死掉,也不会影响主要业务的正常进⾏。
**异步通信 **:很多时候,⽤户不想也不需要⽴即处理消息。消息队列提供了异步处理机制,允许⽤户把⼀个消息放⼊队列,但并不⽴即处理它。想向队列中放⼊多少消息就放多少,然后在需要的时候再去处理它们。

Kafka中的ISR、AR⼜代表什么?ISR的伸缩⼜指什么

ISR:In-Sync Replicas 副本同步队列
AR:Assigned Replicas 所有副本ISR是由leader维护,follower从leader同步数据有⼀些延迟(包括延
迟时间replica.lag.time.max.ms和延迟条数replica.lag.max.messages两个维度, 当前最新的版本0.10.x
中只⽀持replica.lag.time.max.ms这个维度),任意⼀个超过阈值都会把follower剔除出ISR, 存⼊
OSR(Outof-Sync Replicas)列表,新加⼊的follower也会先存放在OSR中。AR=ISR+OSR。

Kafka⾼效⽂件存储设计特点:

  1. Kafka 把 topic 中⼀个 parition ⼤⽂件分成多个⼩⽂件段,通过多个⼩⽂件段,就容易定期清除或
    删除已经消费完⽂件,减少磁盘占⽤。
  2. 通过索引信息可以快速定位 message 和确定 response 的最⼤⼤⼩。
  3. 通过 index 元数据全部映射到 memory,可以避免 segment file 的 IO 磁盘操作。
  4. 通过索引⽂件稀疏存储,可以⼤幅降低 index ⽂件元数据占⽤空间⼤⼩

Kafka与传统消息系统之间有三个关键区别

  1. Kafka 持久化⽇志,这些⽇志可以被重复读取和⽆限期保留
  2. Kafka 是⼀个分布式系统:它以集群的⽅式运⾏,可以灵活伸缩,在内部通过复制数据提升容错能
    ⼒和⾼可⽤性
  3. Kafka ⽀持实时的流式处理

Kafka创建 Topic 时如何将分区放置到不同的 Broker 中

  1. 副本因⼦不能⼤于 Broker 的个数;
  2. 第⼀个分区(编号为 0)的第⼀个副本放置位置是随机从 brokerList 选择的;
  3. 其他分区的第⼀个副本放置位置相对于第 0 个分区依次往后移。也就是如果我们有 5 个Broker,
    5 个分区,假设第⼀个分区放在第四个 Broker 上,那么第⼆个分区将会放在第五个 Broker 上;第
    三个分区将会放在第⼀个 Broker 上;第四个分区将会放在第⼆个Broker 上,依次类推;
  4. 剩余的副本相对于第⼀个副本放置位置其实是由 nextReplicaShift 决定的,⽽这个数也是随机产⽣的
Q : Kafka的消费者如何消费数据
    消费者每次消费数据的时候,消费者都会记录消费的物理偏移量( offset)的位置等到下次消费时,他
会接着上次位置继续消费

Q : Kafka消费者负载均衡策略
   ⼀个消费者组中的⼀个分⽚对应⼀个消费者成员,他能保证每个消费者成员都能访问,如果组中成员太
多会有空闲的成员

Q : kafaka⽣产数据时数据的分组策略
    ⽣产者决定数据产⽣到集群的哪个 partition 中每⼀条消息都是以( key, value)格式 Key是由⽣产者
发送数据传⼊所以⽣产者( key)决定了数据产⽣到集群的哪个 partition

Q : Kafka中是怎么体现消息顺序性的?
    kafka每个partition中的消息在写⼊时都是有序的,消费时,每个partition只能被每⼀个group中的⼀个
消费者消费,保证了消费时也是有序的。整个topic不保证有序。如果为了保证topic整个有序,那么将
partition调整为1.

    

Kafka如何实现延迟队列?

Kafka并没有使⽤JDK⾃带的Timer或者DelayQueue来实现延迟的功能,⽽是基于时间轮⾃定义了⼀个
⽤于实现延迟功能的定时器(SystemTimer)。JDK的Timer和DelayQueue插⼊和删除操作的平均时间
复杂度为O(nlog(n)),并不能满⾜Kafka的⾼性能要求,⽽基于时间轮可以将插⼊和删除操作的时间复杂
度都降为O(1)。时间轮的应⽤并⾮Kafka独有,其应⽤场景还有很多,在Netty、Akka、Quartz、
Zookeeper等组件中都存在时间轮的踪影。底层使⽤数组实现,数组中的每个元素可以存放⼀个
TimerTaskList对象。TimerTaskList是⼀个环形双向链表,在其中的链表项TimerTaskEntry中封装了
真正的定时任务TimerTask.Kafka中到底是怎么推进时间的呢?Kafka中的定时器借助了JDK中的
DelayQueue来协助推进时间轮。具体做法是对于每个使⽤到的TimerTaskList都会加⼊到DelayQueue
中。Kafka中的TimingWheel专⻔⽤来执⾏插⼊和删除TimerTaskEntry的操作,⽽DelayQueue专⻔负
责时间推进的任务。再试想⼀下,DelayQueue中的第⼀个超时任务列表的expiration为200ms,第⼆个
超时任务为840ms,这⾥获取DelayQueue的队头只需要O(1)的时间复杂度。如果采⽤每秒定时推进,
那么获取到第⼀个超时的任务列表时执⾏的200次推进中有199次属于“空推进”,⽽获取到第⼆个超时
任务时有需要执⾏639次“空推进”,这样会⽆故空耗机器的性能资源,这⾥采⽤DelayQueue来辅助以少
量空间换时间,从⽽做到了“精准推进”。Kafka中的定时器真可谓是“知⼈善⽤”,⽤TimingWheel做最
擅⻓的任务添加和删除操作,⽽⽤DelayQueue做最擅⻓的时间推进⼯作,相辅相成。

三. RocketMQ

RocketMQ事务消息是如何实现

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第31张图片
a. ⽣产者订单系统先发送⼀条half消息到Broker,half消息对消费者⽽⾔是不可⻅的
b. 再创建订单,根据创建订单成功与否,向Broker发送commit或rollback
c. 并且⽣产者订单系统还可以提供Broker回调接⼝,当Broker发现⼀段时间half消息没有收到任
何操作命令,则会主动调此接⼝来查询订单是否创建成功
d. ⼀旦half消息commit了,消费者库存系统就会来消费,如果消费成功,则消息销毁,分布式事务成功结束
e. 如果消费失败,则根据重试策略进⾏重试,最后还失败则进⼊死信队列,等待进⼀步处理

为什么RocketMQ不使⽤Zookeeper作为注册中⼼呢

根据CAP理论,同时最多只能满⾜两个点,⽽zookeeper满⾜的是CP,也就是说zookeeper并不能保证
服务的可⽤性,zookeeper在进⾏选举的时候,整个选举的时间太⻓,期间整个集群都处于不可⽤的状
态,⽽这对于⼀个注册中⼼来说肯定是不能接受的,作为服务发现来说就应该是为可⽤性⽽设计。
基于性能的考虑,NameServer本身的实现⾮常轻量,⽽且可以通过增加机器的⽅式⽔平扩展,增加集
群的抗压能⼒,⽽zookeeper的写是不可扩展的,⽽zookeeper要解决这个问题只能通过划分领域,划
分多个zookeeper集群来解决,⾸先操作起来太复杂,其次这样还是⼜违反了CAP中的A的设计,导致
服务之间是不连通的。
持久化的机制来带的问题,ZooKeeper 的 ZAB 协议对每⼀个写请求,会在每个 ZooKeeper 节点上保
持写⼀个事务⽇志,同时再加上定期的将内存数据镜像(Snapshot)到磁盘来保证数据的⼀致性和持久
性,⽽对于⼀个简单的服务发现的场景来说,这其实没有太⼤的必要,这个实现⽅案太重了。⽽且本身
存储的数据应该是⾼度定制化的。
消息发送应该弱依赖注册中⼼,⽽RocketMQ的设计理念也正是基于此,⽣产者在第⼀次发送消息的时
候从NameServer获取到Broker地址后缓存到本地,如果NameServer整个集群不可⽤,短时间内对于⽣
产者和消费者并不会产⽣太⼤影响。

RocketMQ的实现原理

RocketMQ由NameServer注册中⼼集群、Producer⽣产者集群、Consumer消费者集群和若⼲
Broker(RocketMQ进程)组成,它的架构原理是这样的:
Broker在启动的时候去向所有的NameServer注册,并保持⻓连接,每30s发送⼀次⼼跳
Producer在发送消息时候从NameServer获取Broker服务器地址,根据负载均衡算法选择⼀台服务器来发送消息
Conusmer消费消息的时候同样从NameServer获取Broker地址,然后主动拉取消息来消费

RocketMQ为什么速度快

因为使⽤了顺序存储、Page Cache和异步刷盘。我们在写⼊commitlog的时候是顺序写⼊的,这样⽐
随机写⼊的性能就会提⾼很多,写⼊commitlog的时候并不是直接写⼊磁盘,⽽是先写⼊操作系统的
PageCache,最后由操作系统异步将缓存中的数据刷到磁盘

Q : 消息队列如何保证消息可靠传输

A : 消息可靠传输代表了两层意思,既不能多也不能少。

  1. 为了保证消息不多,也就是消息不能重复,也就是⽣产者不能重复⽣产消息,或者消费者不能重复消费消息
  2. ⾸先要确保消息不多发,这个不常出现,也⽐较难控制,因为如果出现了多发,很⼤的原因是⽣产者⾃⼰的原因,如果要避免出现问题,就需要在消费端做控制
  3. 要避免不重复消费,最保险的机制就是消费者实现幂等性,保证就算重复消费,也不会有问题,通过幂等性,也能解决⽣产者重复发送消息的问题
  4. 消息不能少,意思就是消息不能丢失,⽣产者发送的消息,消费者⼀定要能消费到,对于这个问题,就要考虑两个⽅⾯
  5. ⽣产者发送消息时,要确认broker确实收到并持久化了这条消息,⽐如RabbitMQ的confirm机制,
    Kafka的ack机制都可以保证⽣产者能正确的将消息发送给broker
  6. broker要等待消费者真正确认消费到了消息时才删除掉消息,这⾥通常就是消费端ack机制,消费者接收到⼀条消息后,如果确认没问题了,就可以给broker发送⼀个ack,broker接收到ack后才会删除消息
消息队列有哪些作⽤
  1. 解耦:使⽤消息队列来作为两个系统之间的通讯⽅式,两个系统不需要相互依赖了
  2. 异步:系统A给消息队列发送完消息之后,就可以继续做其他事情了
  3. 流量削峰:如果使⽤消息队列的⽅式来调⽤某个系统,那么消息将在队列中排队,由消费者⾃⼰控制消费速度
死信队列是什么?延时队列是什么?
  1. 死信队列也是⼀个消息队列,它是⽤来存放那些没有成功消费的消息的,通常可以⽤来作为消息重试
  2. 延时队列就是⽤来存放需要在指定时间被处理的元素的队列,通常可以⽤来处理⼀些具有过期性操作的业务,⽐如⼗分钟内未⽀付则取消订单
如何保证消息的⾼效读写?

零拷⻉: kafka和RocketMQ都是通过零拷⻉技术来优化⽂件读写。
传统⽂件复制⽅式: 需要对⽂件在内存中进⾏四次拷⻉。
零拷⻉: 有两种⽅式, mmap和transfile,Java当中对零拷⻉进⾏了封装, Mmap⽅式通过
MappedByteBuffer对象进⾏操作,⽽transfile通过FileChannel来进⾏操作。Mmap 适合⽐较⼩的⽂件,通常⽂件⼤⼩不要超过1.5G ~2G 之间。Transfile没有⽂件⼤⼩限制。RocketMQ当中使⽤Mmap⽅式来对他的⽂件进⾏读写。
在kafka当中,他的index⽇志⽂件也是通过mmap的⽅式来读写的。在其他⽇志⽂件当中,并没有使⽤零拷⻉的⽅式。Kafka使⽤transfile⽅式将硬盘数据加载到⽹卡。

如何设计MQ

大体思路 :

  1. 从整体到细节,从业务场景到技术实现。
  2. 以现有产品为基础。

MQ作⽤、项⽬⼤概的样⼦。

  1. 实现⼀个单机的队列数据结构。 ⾼效、可扩展。
  2. 将单机队列扩展成为分布式队列。- 分布式集群管理
  3. 基于Topic定制消息路由策略。- 发送者路由策略,消费者与队列对应关系,消费者路由策略
  4. 实现⾼效的⽹络通信。- Netty Http
  5. 规划⽇志⽂件,实现⽂件⾼效读写。- 零拷⻉,顺序写。 服务重启后,快速还原运⾏现场。
  6. 定制⾼级功能,死信队列、延迟队列、事务消息等等。 - 贴合实际,随意发挥。

四. 消息队列对比(如何进行产品选型)

消息队列高频面试题[2023版本(包括RabbitMQ和RocketMQ 和 Kafka)]_第32张图片

类别 Kafka RocketMQ RabbitMQ
单机吞吐量 17w/s 11w/s 2.6w/s(消息持久化)
开发语言 Scala/java java Erlang
维护者 Apache Alibaba Mozilla/Spring
订阅形式 基于topic,按照t进行正则匹配
发布订阅模式 基于topic/messageTag,按照消息类型,属性进行正则匹配,发布订阅 提供四种,direct,topic,Headers
fanout(就是广播模式)
持久化 支持大量堆积 支持大量堆积 支持少量堆积
顺序消息 支持 支持 不支持
集群方式 Leader-Slave,无状态集群,每台服务器既Master也Slave 多对M-S模式,开源版本需手动切换S变Master 支持简单集群,复制模式,高级集群模式支持不好
性能稳定性 较差 一般

综上所述 :
Kafka:
优点: 吞吐量⾮常⼤,性能⾮常好,集群⾼可⽤。
缺点:会丢数据,功能⽐较单⼀。
使⽤场景:⽇志分析、⼤数据采集
RabbitMQ:
优点: 消息可靠性⾼,功能全⾯。
缺点:吞吐量⽐较低,消息积累会严重影响性能。erlang语⾔不好定制。
使⽤场景:⼩规模场景。
RocketMQ:
优点:⾼吞吐、⾼性能、⾼可⽤,功能⾮常全⾯。
缺点:开源版功能不如云上商业版。官⽅⽂档和周边⽣态还不够成熟。客户端只⽀持java。
使⽤场景:⼏乎是全场景。

写在最后:

码字不易,麻烦大家小手一点 , 点赞或关注 , 感谢大家的支持!!

你可能感兴趣的:(JAVA,Java面试题,消息队列,java-rabbitmq,java-rocketmq,rabbitmq)