【❧消息队列】消息队列面试题

为什么要使用消息队列?

主要有三点原因:解耦、异步、削峰

(1)解耦。比如,用户下单后,订单系统需要通知库存系统,假如库存系统无法访问,则订单减库存将失败,从而导致订单操作失败。订单系统与库存系统耦合,这个时候如果使用消息队列,可以返回给用户成功,先把消息持久化,等库存系统恢复后,就可以正常消费减去库存了。

(2)异步。将消息写入消息队列,非必要的业务逻辑以异步的方式运行,不影响主流程业务。

(3)削峰。消费端慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。比如秒杀活动,一般会因为流量过大,从而导致流量暴增,应用挂掉。这个时候加上消息队列,服务器接收到用户的请求后,首先写入消息队列,如果消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。

什么时候使用MQ

对于一些不需要立即生效的操作,可以拆分出来,异步执行,使用消息队列实现。

以常见的订单系统为例,用户点击下单按钮之后的业务逻辑可能包括:扣减库存、生成相应单据、发短信通知。这种场景下就可以用 MQ 。将短信通知放到 MQ 异步执行,在下单的主流程(比如扣减库存、生成相应单据)完成之后发送一条消息到 MQ, 让主流程快速完结,而由另外的线程消费MQ的消息。

使用了消息队列会有什么缺点

  • 系统可用性降低。引入消息队列之后,如果消息队列挂了,可能会影响到业务系统的可用性。
  • 系统复杂性增加。加入了消息队列,要多考虑很多方面的问题,比如:一致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等。

如何保证消息队列的高可用?

RabbitMQ:镜像集群模式

RabbitMQ 是基于主从做高可用性的,Rabbitmq有三种模式:单机模式、普通集群模式、镜像集群模式

  • 单机模式:一般只是本地测试用、生产环境不会用
  • 普通集群模式:在多台机器上启动多个rabbitmq实例,每个机器启动一个。但是你创建的queue,只会放在一个rabbtimq实例上,但是每个实例都同步queue的元数据,消费的时候会从queue所在实例上拉取数据过来。
  • 镜像集群模式:这种模式才是所谓的rabbitmq的高可用模式,跟普通集群模式不一样的是,你创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,然后每次你写消息到queue的时候,
    都会自动把消息到多个实例的queue里进行消息同步。这样一台机器挂了,别的机器可以用。RabbitMQ管理控制台新增一个策略,这个策略是镜像集群模式的策略,指定的时候可以要求数据同步到所有节点的,也可以要求就同步到指定数量的节点,然后你再次创建queue的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。

镜像集群模式的优缺点

优点:任何一个机器宕机不影响其他机器的使用。

缺点:

  • 性能开销太大:消息同步所有机器,导致网络带宽压力和消耗很重;
  • 扩展性差:如果某个 Queue 负载很重,即便加机器,新增的机器也包含了这个 Queue 的所有数据,并没有办法线性扩展

Kafka:partition 和 replica 机制

Kafka 基本架构是多个 broker 组成,每个 broker 是一个节点。创建一个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 就放一部分数据,这就是天然的分布式消息队列。就是说一个 topic 的数据,是分散放在多个机器上的,每个机器就放一部分数据。

Kafka 0.8 以前,是没有 HA 机制的,任何一个 broker 宕机了,它的 partition 就没法写也没法读了,没有什么高可用性可言。

Kafka 0.8 以后,提供了 HA 机制,就是 replica 副本机制每个 partition 的数据都会同步到其他机器上,形成自己的多个 replica 副本。然后所有 replica 会选举一个 leader 出来,生产和消费都跟这个 leader 打交道,然后其他 replica 就是 follower。写的时候,leader 会负责把数据同步到所有 follower 上去,读的时候就直接读 leader 上数据即可。Kafka 会均匀的将一个 partition 的所有 replica 分布在不同的机器上,这样才可以提高容错性。

如何保证消息的顺序性?

RabbitMQ

拆分多个 Queue,每个 Queue一个 Consumer;或者就一个 Queue 但是对应一个 Consumer,然后这个 Consumer 内部用内存队列做排队,然后分发给底层不同的 Worker 来处理。

Kafka

(1)一个 Topic,一个 Partition,一个 Consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。

(2)写 N 个内存 Queue,具有相同 key 的数据都到同一个内存 Queue;然后对于 N 个线程,每个线程分别消费一个内存 Queue 即可,这样就能保证顺序性。

如何避免消息重复消费?

在消息生产时,MQ内部针对每条生产者发送的消息生成一个唯一id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进入队列

在消息消费时,要求消息体中也要有一全局唯一id作为去重和幂等的依据,避免同一条消息被重复消费

大量消息在 MQ 里长时间积压,该如何解决?

一般这个时候,只能临时紧急扩容了,具体操作步骤如下:

(1)先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉;

(2)新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量;

(3)然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue;

(4)接着临时用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据;

(5)等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消息。

MQ 中的消息过期失效了怎么办?

如果使用的是RabbitMQ的话,RabbtiMQ 是可以设置过期时间的(TTL)。如果消息在 Queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。这时的问题就不是数据会大量积压在 MQ 里,而是大量的数据会直接搞丢。这个情况下,就不是说要增加 Consumer 消费积压的消息,因为实际上没啥积压,而是丢了大量的消息。

我们可以采取一个方案,就是批量重导。就是大量积压的时候,直接将数据写到数据库,然后等过了高峰期以后将这批数据一点一点的查出来,然后重新灌入 MQ 里面去,把丢的数据给补回来。

如何保证数据一致性,事务消息如何实现?

一条普通的MQ消息,从产生到被消费,大概流程如下:

  1. 生产者产生消息,发送带MQ服务器
  2. MQ收到消息后,将消息持久化到存储系统。
  3. MQ服务器返回ACk到生产者。
  4. MQ服务器把消息push给消费者
  5. 消费者消费完消息,响应ACK
  6. MQ服务器收到ACK,认为消息消费成功,即在存储中删除消息。
  1. 生产者产生消息,发送一条半事务消息到MQ服务器
  2. MQ收到消息后,将消息持久化到存储系统,这条消息的状态是待发送状态。
  3. MQ服务器返回ACK确认到生产者,此时MQ不会触发消息推送事件
  4. 生产者执行本地事务
  5. 如果本地事务执行成功,即commit执行结果到MQ服务器;如果执行失败,发送rollback。
  6. 如果是正常的commit,MQ服务器更新消息状态为可发送;如果是rollback,即删除消息。
  7. 如果消息状态更新为可发送,则MQ服务器会push消息给消费者。消费者消费完就回ACK。
  8. 如果MQ服务器长时间没有收到生产者的commit或者rollback,它会反查生产者,然后根据查询到的结果执行最终状态。

如何设计一个消息队列?

首先是消息队列的整体流程,producer发送消息给broker,broker存储好,broker再发送给consumer消费,consumer回复消费确认等。

producer发送消息给broker,broker发消息给consumer消费,那就需要两次RPC了,RPC如何设计呢?可以参考开源框架Dubbo,你可以说说服务发现、序列化协议等等。

broker考虑如何持久化呢,是放文件系统还是数据库呢,会不会消息堆积呢,消息堆积如何处理呢。

消费关系如何保存呢? 点对点还是广播方式呢?广播关系又是如何维护呢?zk还是config server

消息可靠性如何保证呢?如果消息重复了,如何幂等处理呢?

消息队列的高可用如何设计呢? 可以参考Kafka的高可用保障机制。多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。

消息事务特性,与本地业务同个事务,本地消息落库;消息投递到服务端,本地才删除;定时任务扫描本地消息库,补偿发送。

MQ得伸缩性和可扩展性,如果消息积压或者资源不够时,如何支持快速扩容,提高吞吐?可以参照一下 Kafka 的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了嘛。

多线程异步和MQ的区别

  • CPU消耗。多线程异步可能存在CPU竞争,而MQ不会消耗本机的CPU。
  • MQ 方式实现异步是完全解耦的,适合于大型互联网项目。
  • 削峰或者消息堆积能力。当业务系统处于高并发,MQ可以将消息堆积在Broker实例中,而多线程会创建大量线程,甚至触发拒绝策略。
  • 使用MQ引入了中间件,增加了项目复杂度和运维难度。

总的来说,规模比较小的项目可以使用多线程实现异步,大项目建议使用MQ实现异步。

RabbitMQ

什么是RabbitMQ?

RabbitMQ是一个由erlang开发的消息队列。消息队列用于应用间的异步协作。

RabbitMQ的组件

(1)Message:由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key、priority、delivery-mode(是否持久性存储)等。

(2)Publisher:消息的生产者。

(3)Exchange:接收消息并将消息路由到一个或多个Queue。default exchange 是默认的直连交换机,名字为空字符串,每个新建队列都会自动绑定到默认交换机上,绑定的路由键名称与队列名称相同。

(4)Binding:通过Binding将Exchange和Queue关联,这样Exchange就知道将消息路由到哪个Queue中。

(5)Queue:存储消息,队列的特性是先进先出。一个消息可分发到一个或多个队列。

(6)Virtual host:每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。当多个不同的用户使用同一个RabbitMQ server提供的服务时,可以划分出多个vhost,每个用户在自己的vhost创建exchange和queue。

(7)Broker:消息队列服务器实体。

RabbitMQ的优缺点

优点:有管理界面,方便使用;可靠性高;功能丰富,支持消息持久化、消息确认机制、多种消息分发机制。

缺点:使用erlang实现,不利于二次开发和维护;性能较kafka差,持久化消息和ACK确认的情况下生产和消费消息单机吞吐量大约在1-2万左右,kafka单机吞吐量在十万级别。

RabbitMQ 有哪些重要的角色?

RabbitMQ 中重要的角色有:生产者、消费者和代理。

  • 生产者:消息的创建者,负责创建和推送数据到消息服务器
  • 消费者:消息的接收方,用于处理数据和确认消息;
  • 代理:就是 RabbitMQ 本身,用于扮演“快递”的角色,本身不生产消息,只是扮演“快递”的角色。

Exchange 类型

Exchange分发消息时根据类型的不同分发策略不同,目前共四种类型:direct、fanout、topic、headers 。headers 模式根据消息的headers进行路由,此外 headers 交换器和 direct 交换器完全一致,但性能差很多。

direct

direct交换机会将消息路由到binding key 和 routing key完全匹配的队列中。它是完全匹配、单播的模式。

fanout

所有发到 fanout 类型交换机的消息都会路由到所有与该交换机绑定的队列上去。fanout 类型转发消息是最快的。

topic

topic交换机使用routing key和binding key进行模糊匹配,匹配成功则将消息发送到相应的队列。routing key和binding key都是句点号“. ”分隔的字符串,binding key中可以存在两种特殊字符“*”与“##”,用于做模糊匹配,其中“*”用于匹配一个单词,“##”用于匹配多个单词。

headers

headers交换机是根据发送的消息内容中的headers属性进行路由的。在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。

RabbitMQ 如何确保消息不丢失?

消息丢失场景:生产者生产消息到RabbitMQ Server消息丢失RabbitMQ Server存储的消息丢失RabbitMQ Server到消费者消息丢失

消息丢失从三个方面来解决:生产者确认机制、消费者手动确认消息和持久化。

生产者确认机制

生产者发送消息到队列,无法确保发送的消息成功的到达server。

解决方法:

1)事务机制。在一条消息发送之后会使发送端阻塞,等待RabbitMQ的回应,之后才能继续发送下一条消息,性能差。

2)开启生产者确认机制,只要消息成功发送到交换机之后,RabbitMQ就会发送一个ack给生产者(即使消息没有Queue接收,也会发送ack)。如果消息没有成功发送到交换机,就会发送一条nack消息,提示发送失败。

路由不可达消息

生产者确认机制只确保消息正确到达交换机,对于从交换机路由到Queue失败的消息,会被丢弃掉,导致消息丢失。

对于不可路由的消息,有两种处理方式:Return消息机制和备份交换机。

Return消息机制

Return消息机制提供了回调函数 ReturnCallback,当消息从交换机路由到Queue失败才会回调这个方法。

当消息从交换机路由到Queue失败时,会返回 return exchange: , routingKey: MAIL, replyCode: 312, replyText: NO_ROUTE。

备份交换机

备份交换机alternate-exchange 是一个普通的exchange,当你发送消息到对应的exchange时,没有匹配到queue,就会自动转移到备份交换机对应的queue,这样消息就不会丢失。

消费者手动消息确认

有可能消费者收到消息还没来得及处理MQ服务就宕机了,导致消息丢失。因为消息者默认采用自动ack,一旦消费者收到消息后会通知MQ Server这条消息已经处理好了,MQ 就会移除这条消息。

解决方法

消费者设置为手动确认消息。消费者处理完逻辑之后再给broker回复ack,表示消息已经成功消费,可以从broker中删除。当消息者消费失败的时候,给broker回复nack,根据配置决定重新入队还是从broker移除,或者进入死信队列。只要没收到消费者的 acknowledgment,broker 就会一直保存着这条消息,但不会 requeue,也不会分配给其他 消费者。

持久化

如果RabbitMQ服务异常导致重启,将会导致消息丢失。RabbitMQ提供了持久化的机制,将内存中的消息持久化到硬盘上,即使重启RabbitMQ,消息也不会丢失。

消息持久化需要满足以下条件:

  • 消息设置持久化。发布消息前,设置投递模式delivery mode为2,表示消息需要持久化。
  • Queue设置持久化。
  • 交换机设置持久化。

当发布一条消息到交换机上时,RabbitMQ会先把消息写入持久化日志,然后才向生产者发送响应。一旦从队列中消费了一条消息的话并且做了确认,RabbitMQ会在持久化日志中移除这条消息。在消费消息前,如果RabbitMQ重启的话,服务器会自动重建交换机和队列,加载持久化日志中的消息到相应的队列或者交换机上,保证消息不会丢失。

镜像队列

当MQ发生故障时,会导致服务不可用。引入RabbitMQ的镜像队列机制,将queue镜像到集群中其他的节点之上。如果集群中的一个节点失效了,能自动地切换到镜像中的另一个节点以保证服务的可用性。

通常每一个镜像队列都包含一个master和多个slave,分别对应于不同的节点。发送到镜像队列的所有消息总是被直接发送到master和所有的slave之上。除了publish外所有动作都只会向master发送,然后由master将命令执行的结果广播给slave,从镜像队列中的消费操作实际上是在master上执行的。

消息重复消费怎么处理?

消息重复的原因有两个:

  • 生产时消息重复
  • 消费时消息重复

生产者发送消息给MQ,在MQ确认的时候出现了网络波动,生产者没有收到确认,这时候生产者就会重新发送这条消息,导致MQ会接收到重复消息。

消费者消费成功后,给MQ确认的时候出现了网络波动,MQ没有接收到确认,为了保证消息不丢失,MQ就会继续给消费者投递之前的消息。这时候消费者就接收到了两条一样的消息。由于重复消息是由于网络原因造成的,无法避免。

解决方法:发送消息时让每个消息携带一个全局的唯一ID,在消费消息时先判断消息是否已经被消费过,保证消息消费逻辑的幂等性。具体消费过程为:

  1. 消费者获取到消息后先根据id去查询redis/db是否存在该消息
  2. 如果不存在,则正常消费,消费完毕后写入redis/db
  3. 如果存在,则证明消息被消费过,直接丢弃

消费端怎么进行限流?

当 RabbitMQ 服务器积压大量消息时,队列里的消息会大量涌入消费端,可能导致消费端服务器奔溃。这种情况下需要对消费端限流。

Spring RabbitMQ 提供参数 prefetch 可以设置单个请求处理的消息个数。如果消费者同时处理的消息到达最大值的时候,则该消费者会阻塞,不会消费新的消息,直到有消息 ack 才会消费新的消息。

什么是死信队列?

消费失败的消息存放的队列。

消息消费失败的原因:

  • 消息被拒绝并且消息没有重新入队(requeue=false)
  • 消息超时未消费
  • 达到最大队列长度

怎么设置消息的过期时间?

在生产端发送消息的时候可以给消息设置过期时间,单位为毫秒(ms)

Kafka

Kafka 都有哪些特点?

  • 高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒,每个topic可以分多个partition, consumer group 对partition进行consume操作。
  • 可扩展性:kafka集群支持热扩展
  • 持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失
  • 容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)
  • 高并发:支持数千个客户端同时读写

请简述下你在哪些场景下会选择 Kafka?

  • 日志收集:一个公司可以用Kafka可以收集各种服务的log,通过kafka以统一接口服务的方式开放给各种consumer,例如hadoop、HBase、Solr等。
  • 消息系统:解耦、生产者和消费者、缓存消息等。
  • 用户活动跟踪:Kafka经常被用来记录web用户或者app用户的各种活动,如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到kafka的topic中,然后订阅者通过订阅这些topic来做实时的监控分析,或者装载到hadoop、数据仓库中做离线分析和挖掘。
  • 运营指标:Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告。
  • 流式处理:比如spark streaming和 Flink

Kafka 的设计架构你知道吗?

Kafka 架构分为以下几个部分:

  • Producer :消息生产者,就是向 kafka broker 发消息的客户端。
  • Consumer :消息消费者,向 kafka broker 取消息的客户端。
  • Topic :可以理解为一个队列,一个 Topic 又分为一个或多个分区,
  • Consumer Group:这是 kafka 用来实现一个 topic 消息的广播(发给所有的 consumer)和单播(发给任意一个 consumer)的手段。一个 topic 可以有多个 Consumer Group。
  • Broker :一台 kafka 服务器就是一个 broker。一个集群由多个 broker 组成。一个 broker 可以容纳多个 topic。
  • Partition:为了实现扩展性,一个非常大的 topic 可以分布到多个 broker上,每个 partition 是一个有序的队列。partition 中的每条消息都会被分配一个有序的id(offset)。将消息发给 consumer,kafka 只保证按一个 partition 中的消息的顺序,不保证一个 topic 的整体(多个 partition 间)的顺序。
  • Offset:kafka 的存储文件都是按照 offset.kafka 来命名,用 offset 做名字的好处是方便查找。例如你想找位于 2049 的位置,只要找到 2048.kafka 的文件即可。当然 the first offset 就是 00000000000.kafka。

Kafka 分区的目的?

分区对于 Kafka 集群的好处是:实现负载均衡

分区对于消费者来说,可以提高并发度,提高效率

你知道 Kafka 是如何做到消息的有序性?

kafka 中的每个 partition 中的消息在写入时都是有序的,而且单独一个 partition 只能由一个消费者去消费,可以在里面保证消息的顺序性。但是分区之间的消息是不保证有序的

Kafka Producer 的执行过程?

  1. Producer生产消息
  2. 从Zookeeper找到Partition的Leader
  3. 推送消息
  4. 通过ISR列表通知给Follower
  5. Follower从Leader拉取消息,并发送ack
  6. Leader收到所有副本的ack,更新Offset,并向Producer发送ack,表示消息写入成功。

讲一下你使用 Kafka Consumer 消费消息时的线程模型,为何如此设计?

一个消费者一个线程。这种多线程模型是利用Kafka的topic分多个partition的机制来实现并行:每个线程都有自己的consumer实例,负责消费若干个partition。各个线程之间是完全独立的,不涉及任何线程同步和通信,所以实现起来非常简单。

请谈一谈 Kafka 数据一致性原理

一致性就是说不论是老的 Leader 还是新选举的 Leader,Consumer 都能读到一样的数据。

假设分区的副本为3,其中副本0是 Leader,副本1和副本2是 follower,并且在 ISR 列表里面。虽然副本0已经写入了 Message4,但是 Consumer 只能读取到 Message2。因为所有的 ISR 都同步了 Message2,只有 High Water Mark 以上的消息才支持 Consumer 读取,而 High Water Mark 取决于 ISR 列表里面偏移量最小的分区,对应于上图的副本2,这个很类似于木桶原理。

这样做的原因是还没有被足够多副本复制的消息被认为是“不安全”的,如果 Leader 发生崩溃,另一个副本成为新 Leader,那么这些消息很可能丢失了。如果我们允许消费者读取这些消息,可能就会破坏一致性。试想,一个消费者从当前 Leader(副本0) 读取并处理了 Message4,这个时候 Leader 挂掉了,选举了副本1为新的 Leader,这时候另一个消费者再去从新的 Leader 读取消息,发现这个消息其实并不存在,这就导致了数据不一致性问题。

当然,引入了 High Water Mark 机制,会导致 Broker 间的消息复制因为某些原因变慢,那么消息到达消费者的时间也会随之变长(因为我们会先等待消息复制完毕)。延迟时间可以通过参数  replica.lag.time.max.ms参数配置,它指定了副本在复制消息时可被允许的最大延迟时间。

ISR、OSR、AR 是什么?

Kafka分区引入了多副本机制(Replica),通过增加副本数量提升容灾能力,副本处于不同的Broker上,副本之间是一主多从的关系,Leader副本负责处理读写请求,Follower副本只负责与Leader副本的消息同步。当Leader副本出现故障时,从Follower副本中重新选举新的Leader对外服务。

ISR:In-Sync Replicas,由与Leader副本保持一定程度同步的副本(包括Leader副本)组成。

OSR:Out-of-Sync Replicas 与Leader副本同步滞后太多的副本。

AR:Assigned Replicas 分区中所有副本。AR=ISR+OSR

在消息同步期间,Follower副本相对于Leader副本具有一定程度的滞后,Leader副本负责维护和跟踪ISR集合中所有Follower副本的滞后状态。当Follower副本落后太多或失效时,Leader副本会将它从ISR集合中剔除。只有在ISR集合中的副本才有资格被选举为新的Leader。

LEO、HW、LSO、LW等分别代表什么

LEO:是 LogEndOffset 的简称,代表当前日志文件中下一条

HW:水位或水印(watermark)一词,也可称为高水位(high watermark),通常被用在流式处理领域(比如Apache Flink、Apache Spark等),以表征元素或事件在基于时间层面上的进度。在Kafka中,水位的概念反而与时间无关,而是与位置信息相关。严格来说,它表示的就是位置信息,即位移(offset)。取 partition 对应的 ISR中 最小的 LEO 作为 HW,consumer 最多只能消费到 HW 所在的位置上一条信息

LSO:是 LastStableOffset 的简称,对未完成的事务而言,LSO 的值等于事务中第一条消息的位置(firstUnstableOffset),对已完成的事务而言,它的值同 HW 相同

LW:Low Watermark 低水位, 代表 AR 集合中最小的 logStartOffset 值

数据传输的事务有几种?

数据传输的事务定义通常有以下三种级别:

  • 最多一次:消息不会被重复发送,最多被传输一次,但也有可能一次都不传输
  • 最少一次:消息不会被漏发送,最少被传输一次,但也有可能被重复传输
  • 精确的一次(Exactly once):不会漏传输也不会重复传输,每个消息都传输被消费

Kafka 消费者是否可以消费指定分区消息?

Kafa consumer消费消息时,向broker发出fetch请求去消费特定分区的消息,consumer指定消息在日志中的偏移量(offset),就可以消费从这个位置开始的消息,customer拥有了offset的控制权,可以向后回滚去重新消费之前的消息,这是很有意义的。

Kafka消息是采用Pull模式,还是Push模式?

Kafka最初考虑的问题是,customer应该从brokers拉取消息还是brokers将消息推送到consumer,也就是pull还push。在这方面,Kafka遵循了一种大部分消息系统共同的传统的设计:producer将消息推送到broker,consumer从broker拉取消息

一些消息系统比如Apache Flume采用了push模式,将消息推送到下游的consumer。这样做有好处也有坏处:由broker决定消息推送的速率,对于不同消费速率的consumer就不太好处理了。消息系统都致力于让consumer以最大的速率最快速的消费消息,但不幸的是,push模式下,当broker推送的速率远大于consumer消费的速率时,consumer恐怕就要崩溃了。最终Kafka还是选取了传统的pull模式。

Pull模式的一个好处是consumer可以自主决定是否批量的从broker拉取数据。Push模式必须在不知道下游consumer消费能力和消费策略的情况下决定是立即推送每条消息还是缓存之后批量推送。如果为了避免consumer崩溃而采用较低的推送速率,将可能导致一次只推送较少的消息而造成浪费。Pull模式下,consumer就可以根据自己的消费能力去决定这些策略

Pull有个缺点是,如果broker没有可供消费的消息,将导致consumer不断在循环中轮询,直到新消息到达。为了避免这点,Kafka有个参数可以让consumer阻塞知道新消息到达(当然也可以阻塞知道消息的数量达到某个特定的量这样就可以批量发。

Kafka 高效文件存储设计特点

Kafka把topic中一个parition大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删除已经消费完文件,减少磁盘占用

通过索引信息可以快速定位message和确定response的最大大小。

通过index元数据全部映射到memory,可以避免segment file的IO磁盘操作。

通过索引文件稀疏存储,可以大幅降低index文件元数据占用空间大小。

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

副本因子不能大于 Broker 的个数;

第一个分区(编号为0)的第一个副本放置位置是随机从 brokerList 选择的;

其他分区的第一个副本放置位置相对于第0个分区依次往后移。也就是如果我们有5个 Broker,5个分区,假设第一个分区放在第四个 Broker 上,那么第二个分区将会放在第五个 Broker 上;第三个分区将会放在第一个 Broker 上;第四个分区将会放在第二个 Broker 上,依次类推;

剩余的副本相对于第一个副本放置位置其实是由 nextReplicaShift 决定的,而这个数也是随机产生的。

谈一谈 Kafka 的再均衡

在Kafka中,当有新消费者加入或者订阅的topic数发生变化时,会触发Rebalance(再均衡:在同一个消费者组当中,分区的所有权从一个消费者转移到另外一个消费者)机制,Rebalance顾名思义就是重新均衡消费者消费

Rebalance的过程如下:

第一步:所有成员都向coordinator发送请求,请求入组。一旦所有成员都发送了请求,coordinator会从中选择一个consumer担任leader的角色,并把组成员信息以及订阅信息发给leader。

第二步:leader开始分配消费方案,指明具体哪个consumer负责消费哪些topic的哪些partition。一旦完成分配,leader会将这个方案发给coordinator。coordinator接收到分配方案之后会把方案发给各个consumer,这样组内的所有成员就都知道自己应该消费哪些分区了。

所以对于Rebalance来说,Coordinator起着至关重要的作用。

Kafka 是如何实现高吞吐率的?

Kafka是分布式消息系统,需要处理海量的消息,Kafka的设计是把所有的消息都写入速度低容量大的硬盘,以此来换取更强的存储能力,但实际上,使用硬盘并没有带来过多的性能损失。kafka主要使用了以下几个方式实现了超高的吞吐率:

  • 顺序读写
  • 零拷贝
  • 文件分段
  • 批量发送
  • 数据压缩

Kafka 缺点

  • 由于是批量发送,数据并非真正的实时;
  • 对于mqtt协议不支持;
  • 不支持物联网传感数据直接接入;
  • 仅支持统一分区内消息有序,无法实现全局消息有序;
  • 监控不完善,需要安装插件;
  • 依赖zookeeper进行元数据管理;(从 Kafka 2.8 开始,在没有 Zookeeper 的情况下也可以运行 Kafka 集群。)

Kafka 分区数可以增加或减少吗?为什么?

我们可以使用 bin/kafka-topics.sh 命令对 Kafka 增加 Kafka 的分区数据,但是Kafka 不支持减少分区数

Kafka 分区数据不支持减少是有很多原因的,比如减少的分区其数据放到哪里去?是删除,还是保留?删除的话,那么这些没消费的消息不就丢了。如果保留这些消息如何放到其他分区里面?追加到其他分区后面的话那么就破坏了 Kafka 单个分区的有序性。如果要保证删除分区数据插入到其他分区保证有序性,那么实现起来逻辑就会非常复杂。

 

posted @ 2023-04-05 12:17  残城碎梦  阅读(254)  评论(0编辑  收藏  举报