Java面试题(10)消息中间件MQ

一、简述RabbitMQ的架构设计

二、RabbitMQ如何确保消息发送?消息接收?

三、RabbitMq事务消息

四、RabbitMQ死信队列、延时队列

五、简述kafka架构设计

六、Kafka消息重复、丢失的场景及解决方案

七、Kafka是pull?push?优劣势分析

八、Kafka中zk的作用

九、Kafka中高性能的原因分析

十、简述Kafka的rebalance机制

 

一、简述RabbitMQ的架构设计

Broker:rabbitmq的服务节点

Queue: 队列,是RabbitMQ的内部对象,用于存储消息。Rabbitmq中消息只能存储在队列中。生产者投递消息到队列,消费者从队列中获取消息并消费。多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(轮询)给多个消费者进行消费,而不是每个消费者都收到所有的消息进行消费。(注:RabbirMQ不支持队列层面的广播消费,如果需要广播消费,可以采用一个交换器通过路由Key绑定多个队列,由多个消费者来订阅这些队列的方式。

Exchage:交换器。生产者将消息发送到Exchange,由交换器将消息路由到一个或多个队列中。如果路由不到,或返回给生产者,或直接丢弃,或做其他处理。

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

Binding:通过绑定将交换器和队列关联起来,在绑定的时候一般会指定一个绑定键,这样RabbitMQ就可以指定如何正确的路由到队列了。

交换器和队列实际上是多对多关系。就像关系数据库中的两张表。他们通过BindingKey做关联(多对多关系表)。在投递消息时,可以通过Exchage和RoutingKey(对应BindingKey)就可以找到对应的队列。

信道信道是建立在Connection 之上的虚拟连接。当应用程序与Rabbit Broker建立TCP连接的时候,客户端紧接着可以创建一个AMQP 信道(Channel) ,每个信道都会被指派一个唯一的D。RabbitMQ 处理的每条AMQP指令都是通过信道完成的。信道就像电缆里的光纤束。一条电缆内含有许多光纤束,允许所有的连接通过多条光线束进行传输和接收。

 

二、RabbitMQ如何确保消息发送?消息接收?

发送方确认模式:

将信道设置成confirm模式(发生方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。

一旦消息被投递到队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一ID)。

如果rabbitMQ发生内部错误从而导致消息丢失,会发送一条nack(notacknowledged,未确认)消息。

发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。

接收方确认机制:

消费者接受每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。这里并没有用到超时机制,RabbitMQ仅通过consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ给了Consumer足够长的时间来处理消息。保证数据的最终一致性。

下面罗列几种特殊情况

如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要去重)

如果消费者接收到消息却没有确认消息,连接也未断开,则RabbitMQ认为该消费者繁忙,将不会给消费者分发更多的消息。

 

三、RabbitMq事务消息

正常情况下,如果消息经过交换器进入队列就可以完成消息的持久化,但如果消息在没有到达broker之前出现意外,那就会造成消息丢失,解决方案如下:1、通过AMQP提供的事务机制实现;2、使用发送者确认模式实现

RabbitMQ事务的实现主要是对信道的设置,主要的方法有三个:

(1)channel.txSelect()声明启动事务模式;

(2)channel.txComment()提交事务;

(3)channel.txRollback()回滚事务;

Confirm批量确定和Confirm异步模式性能相差不大,Confirm模式要比事务快10倍左右。

 

四、RabbitMQ死信队列、延时队列

由于某些原因没有被消费的消息,就变成了死信,接收到死信消息的队列就叫死信队列。

一个消息满足以下条件之一,会进入到死信路由(注意这里是路由不是队列,一个路由可以对应多个队列)

  • 消息的TTL(消息的存活时间)到了,消息从进入队列开始,直到达到TTL(消息过期了)仍然没有被消费。
  • 消息被consumer拒收了(手动确认中调用basicReject或者basicNack),并且拒收方的requeue参数是false,也就是说不会重新入队被其他消费者使用
  • 队列的长度限制满了(在创建普通队列时会添加x-max-length参数,指定队列的最长长度),排在前面的消息会被丢弃或者进入死心路由。

延时队列:【例如】

  • 订单支付超时自动取消。
  • 拍卖成功后进入公示状态,公示10分钟后取消公示状态。
  • 用户注册成功后,如果超过24小时没有登录则进行短信提醒。

这个功能主要是需要一个延时队列,那通过RabbitMQ实现延时队列只是一种方式,还可以通过其他方式实现。比如java的DelayQueue,Quartz定时任务,Redis的zset、时间轮等都可以实现,具体的结合业务的需求来考虑采用哪种解决方案。比如RabbitMQ可以实现延迟队列,那具体选择插件方式还是死信队列方式,这个还需要看项目中对该功能的灵活程度来选择。

 

五、简述kafka架构设计

应付Kafka面试,看这篇就够了 (qq.com)

1、为什么要使用kafka?

  • 缓冲和肖峰:上游数据时有突发流量,下游可能扛不住,或者下游么有足够多的机器来保证冗余,kafka在中间可以起到一个缓冲的作用,把消息暂存在kafka中,下游服务就可以按照自己的节奏进行慢慢处理。
  • 解耦和扩展性:项目开始的时候,并不能确定具体需求。消息队列可以作为一个接口层,解耦重要的业务流程。只需要遵守约定,针对数据编程即可获取扩展能力。
  • 冗余:可以采用一对多的方式,一个生产者发布消息,可以被多个订阅topic的服务消费到,供多个毫无关联的业务使用。
  • 健壮性:消息队列可以堆积请求,所以消费端业务即使短时间死掉,也不会影响主要业务的正常进行。
  • 异步通信:很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
  • 高吞吐低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒,每个topic可以分多个partition,consumer group 对partition进行consumer操作。
  • 持久性可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失。
  • 容错性:允许集群中节点失败(若副本数量为n,则运行n-1个节点失败)
  • 高并发:支持书数千个客户端同时读写

几个概念:

Consumer Group: 消费者组,消费者组内每个消费者负责消费不同分区的数据,提高消费能力。逻辑上的一个订阅者。

Topic:可以理解为一个队列,Topic 将消费分类,生产者和消费者面向的是同一个Topic.

Partition:为了实现扩展性,提高并发能力,一个Topic以多个Partition的方式分不到多个broker上,每个Partition是一个有序的队列。一个Topic的每个partition都有若干个副本(Replica),一个Leader和若干个Follower。生产者发送数据的对象,以及消费者消费数据的对象,都是Leader。Follower负责实时从Leader中同步数据,保持和Leader数据的同步。Leader发生故障时,某个Follower还会成为新的Leader。

Offset:消费者消费的位置信息,监控数据消费到什么位置,当消费者挂掉再重新恢复的时候,可以从消费位置继续消费。

Zookeeper:Kafka集群能够正常工作,需要依赖于Zookeeper,Zookeeper帮助Kafka存储和管理集群信息。Broker和Topic注册到Zookeeper,生成Znode的临时节点。Broker、Topic、Offset和Partition的关系,包括Consumer的关联关系,都是存储到zk的,Leader节点的选举模式也是使用Zookeeper的选举机制。

 

2、kafka的数据可靠性怎么保证?

为了保证producer发送的数据,能可靠的发送到指定的topic,topic的每个partition收到producer发送的数据后,都需要向produer发送ack(acknowledgement确认收到),如果producer收到ack,就会进行下一轮的发送,否则重新发送数据。所以引出ack机制

ack应答机制kafka为用户提供了三种可靠性级别,用户根据对可靠性和延迟的要去进行权衡,选择以下的配置。ack参数配置:

0:producer不等待broker的ack,这一操作提供了一个最低的延迟,broker一接收到还没写入磁盘就已经返回,当broker故障时有可能丢失数据。

1:producer等待broker的ack,partition的leader落盘成功后返回ack,如果在follower同步成功之前leader故障,那么将会丢失数据。

-1(all):producer等待broker的ack,partition的leader和follower全部落盘成功后才返回ack。但是如果在follower同步完成后,broker发送ack之前,leader发生故障,那么会造成数据重复。

 

六、Kafka消息重复、丢失的场景及解决方案

发生在三个阶段:生产阶段、broker阶段、消费者阶段

1、生产阶段:

1.1、生产者丢失消息

(1)、丢失场景:

  配置文件里面,ack设置为0,也就是生产者发送之后,不管分区副本(leader和follower)是否收到都不管了,如果发送失败,消息就会丢失。

  配置文件里面,ack设置为1,也就是生产者发送之后,只要Leader接收到了,就会返回成功,follower没来得及同步的时候,此时Leader挂掉,就会丢失消息。

(2)、解决方案:

设置ack=all 或 -1 保证leader和follower分区都收到之后,再返回给生产者成功。如果其中有一个步骤异常,都会触发kafka的重试机制。

1.2、生产者重复消费

(1)、重复消费场景:

 生产发送的消息没有收到正确的broker响应,导致producer重试。producer发出一条消息,broker落盘以后因为网络等种种原因发送端得到一个发送失败的响应或   者网络中断,然后producer收到一个可恢复的Exception重试消息导致消息重复。

(2)、解决方案:

  启动kafka的幂等性 enable.idempotence=true 同时要求 ack=all 且 retries > 1

  幂等性原理:每个生产者producer都有一个唯一的id,producer每发送一条数据,都会带上一个sequence,当消息落盘,sequence就会递增1.

  那么只需要判单当前消息的sequence是否大于当前最大sequence,大于就代表此条数据没有落盘过,可以正常消费。不大于就代表落盘过,这个时候重发的消息    会被服务端拒掉从而避免消息重复。

2、broker阶段

2.1、broker丢失数据

(1)、丢失场景:

   ack=1,follower没来得及同步的时候leader挂掉了也不好重试,当follower被选举为新的leader时,这部分没同步的数据就丢失了。

   分区副本数小于2,导致没有足够数量的副本参与新leader选举,无法保证数据的高可用,当原Leader挂了之后,么有Follower被选举为leader。

(2)、解决方案:

   ack=-1,保证leader和follower分区的数据可以落盘。

   保证分区副本数大于2,保证数据的高可用性

   设置重试次数等。

3、消费阶段

3.1、消费者丢失消息

(1)、丢失场景

   设置的自动提交offset,当消费者已经消费到了消息,也记录了新的偏移量offset,但是后面的业务失败了或者没来得及处理就挂了。这时候因为offset已经更新了,这条消息也再消费不到了。

(2)、解决方案

   设置为手动提交成功,当业务代码都执行完成之后,再进行手动提交,确保消息被真正处理到。

3.2、消费者消息重复

(1)丢失场景

  数据消费完没有及时提交offset到broker。

  消息消费端在消费过程中挂掉么有及时提交offset到broker,另外一个消费端启动拿之前记录的offset开始消费,由于offset的滞后性可能会导致新启动的客户端有少量重复消费。

(2)解决方案

  设置手动提交成功

  在下游程序里面做幂等,幂等就两种方式:

  • 将唯一键存入第三方介质,要操作数据时候先判断第三方介质(数据库或者缓存)有没有这个唯一键。
  • 将版本号(offset)存入到数据里面,然后再操作数据的时候用这个版本号做乐观锁,当版本号大于原先的才能操作。

 

七、Kafka是pull?push?优劣势分析

kafka一般包含多个生产者producer,多个Broker(kafka支持水平扩展,一般Broker越多,吞吐量越高),多个消费者(Consumer),以及一个Zookeeper集群,kafka通过zookeeper来管理集群配置,选举机制以及消费者发生变化时进行Rebalance。

生产者使用push模式将消息发布到Broker,消费者使用pull模式从Broker订阅消息

push模式很难适应消费速率不同的消费者,如果push的速度太快,容易造成消费者拒绝服务或网络阻塞如果push的速度太慢,容易造成消费者性能浪费。但是采用pull的方式也有一个缺点,就是当Broker没有消息时,消费者会陷入不断地轮询中,为了避免这点,kafka有个参数可以让消费则阻塞知道是否有新消息到达。

kafka与传统消息系统之间的三大关键区别

  • Kafka持久化日志,这些日志可以被重复读取和无限期保留
  • 分布式系统:可灵活伸缩,在内部通过复制数据提高容错能力和高可用性
  • 支持实时流式处理。

消费者每次消费数据时,消费者都会记录消费的物理偏移量(offset),等下次消费时,会接着上次位置继续消费。

 

八、Kafka中zk的作用

zk在kafka中的作用 - luckyna - 博客园 (cnblogs.com)

 

九、Kafka中高性能的原因分析

kafka作为一款基于磁盘存储的高吞吐消息中间件,常作为log,event等流式数据的通道,在流式计算领域应用丰富。

  • 零拷贝
  • 顺序写+page cache
  • 分区(partition)
  • reactor 网络模型

 

十、简述Kafka的rebalance机制

 (64条消息) 消息队列重要机制讲解以及MQ设计思路(kafka、rabbitmq、rocketmq)_拾牙慧者的博客-CSDN博客

posted @ 2022-01-27 15:23  娜梓  阅读(400)  评论(0编辑  收藏  举报