Kafka面试题
Kafka面试题
1、kafka为什么这么快?(高吞吐量、低延迟或者高性能原因)
消息生产方面 | 消息存储方面 | 消息消费方面 |
---|---|---|
批量发送:Kafka通过将多个消息打包成一个批次,减少了网络传输和磁盘写入的次数,从而提高了消息的吞吐量和传输效率。 | 零拷贝技术:Kafka 使用零拷贝技术来避免了数据的拷贝操作,隆低了内存和CPU 的使用率,提高了系统的件能 | 消费者群组:通过消费者群组可以实现消息的负载均衡和容错处理 |
异步发送:生产者可以异步发送消息,不必等待每个消息的确认,这大大提高了消息发送的效率 | 磁盘顺序写入:Kafka把消息存储在磁盘上,且以顺序的方式写入数据。顺序写入比随机写入速度快很多,因为它减少了磁头寻道时间。避免了随机读写带来的性能损耗,提高了磁盘的使用效率 | 并行消费:不同的消费者可以独立地消费不同的分区,实现消费的并行处理 |
消息压缩:支持对消息进行压缩,减少网络传输的数据量 | 页缓存:Kafka将其数据存储在磁盘中,但在访问数据时,它会先将数据加载到统的页缓存中,并在页缓存中保留一份副本,从而实现快速的数据访问 | 批量拉取:Kafka支持批量拉取消息,可以一次性拉取多个消息进行消费。减少网络消耗,提升性能 |
并行发送:通过将数据分布在不同的分区(Partitions)中,生产者可以并行发送消息,从而提高了吞吐量 | 稀疏索引:Kaka 存储消息是通过分段的日志文件,每个分段都有自己的索引文件。这些索引文件中的条目不是对分段中的每条消息都建立索引,而是每隔一定数量的消息建立一个索引点,这就构成了稀疏索引。稀疏索引减少了索引大小,使得加载到内存中的索引更小,提高了查找特定消息的效率 | |
分区和副本:Kafka采用分区和副本的机制,可以将数据分散到多个节点上讲行处理,从而实现了分布式的高可用件和负载均衡 |
2、kafka如何解决消息丢失问题
保证消息不丢失,可以从以下三个方面考虑:
(1)生产者不丢消息,即:确保生产的消息能到达存储端
针对kafka而言,使用调用回调的 producer.send(msg,callback)方法,保证消息同步发送到存储端,
同时可以根据回调方法返回的异常信息针对消息发送失败问题进行处理,
如果是因为那些瞬时错误,Producer重试就可以了;如果是消息不合规造成的,那么调整消息格式后再次发送。
(2)Broker存储端不丢数据:确保消息持久化到磁盘中
例如:Kafka 某个 broker 宕机,然后重新选举 partition 的 leader。如果此时其他的 follower 刚好有些数据没有同步,
结果此时 leader 挂了,然后选举某个 follower 成 leader 之后,便会导致数据丢失
解决方案主要有两种:
1> 持久化存储:Kafka使用持久化存储来存储消息。这意味着消息在写入Kafka时将被写入磁盘,这种方式可以防止消息因为节点容机而丢失
2> ISR复制机制:Kafka使用ISR机制来确保消息不会丢失,Kafka使用复制机制来保证数据的可靠性。每个分区都有多个副本,副本可以分布在不同的节点上。
当一个节点宕机时,其他节点上的副本仍然可以提供服务,保证消息不丢失
服务端也可以设置如下参数:
1> topic 设置 replication.factor 参数值大于 1,要求每个 partition 必须有至少 2 个副本
2> 在 Kafka 服务端设置 min.insync.replicas 参数值大于 1,这个是要求一个 leader 至少有一个 follower 还跟自己保持联系
3> 在 producer 端设置 acks=all:这个是要求每条数据,必须是写入所有 replica 之后,才能认为是写成功了
4> 在 producer 端设置 retries=MAX(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试
(3)消费者不丢数据:确保消息消费完成再提交
也就是说一个线程消费到了这个消息,然后消费者那边自动提交了 offset,会让 Kafka 以为此线程已经消费好了这个消息,
但实际情况是该线程准备处理此消息时,就挂掉了,结果就导致这条消息丢失了。
解决方案:
针对此种情况,只需要关闭kafka的自动提交 offset设置,即:将enable.auto.commit参数值设置为false,然后在处理完消息之后再手动提交 offset
3、kafka如何保证消息的顺序性
Kafka的消息是存储在指定的topic中的某个partition中的。并且一个topic是可以有多个partition的。同一个partition中的消息是有序的,
但是跨partition,或者跨topic的消息就是无序的了。
对于如何保证消息的顺序性,在同一个partition中可以有如下方法:
(1)在1个Topic中只创建1个Partition(分区),这样生产者的所有数据都发送到了一个Partition(分区),保证了消息的消费顺序。
但是此种方式单线程的吞吐量太低,一般只能保证局部有序
(2)生产者在发送消息时,可以通过指定Partition的方式,将需要保证顺序的消息发送到同一个Partition中,从而做到顺序消费。
关于如何将消息发送到同一个Partition中,有以下方法:
1> 指定partition:在发送消息的时候,可以直接在ProducerRecord中指定partition
2> 指定key:如果没有指明partition,也就是partition值为null,这种情况下,如果有key,Kafka 对其进行 Hash 计算,根据计算结果决定放入哪个Partition中。
这样 Partition Key 相同的消息也会放在同一个 Partition中。针对key为null的情况,kafka默认采用轮询策略处理。
4、Kafka如何避免消息重复消费(保证消息的幂等性)
关于此问题,可以从以下方面回答:
(1)在Kafka中,每个消费者都必须加入至少一个消费者组。同一个消费者组内的消费者可以共享消费者的负载。
因此,如果一个消息被消费组中的任何一个消费者消费了,那么其他消费者就不会再收到这个消息了
(2)消费者可以通过手动提交消费位移来控制消息的消费情况。通过手动提交位移,消费者可以跟踪自己已经消费的消息,确保不会重复消费同一消息
(3)客户端自己可以做一些幂等机制,防止消息的重复消费。
针对重复消费的问题,一般可以根据业务情况处理:
1> 可以建立一个业务相关的本地表,使用主键或者唯一索引,针对每次消费之后已提交的数据记录消息的唯一id,等下次消息消费时判断是否消费过
2> 或者使用Redis缓存针对已经消费提交的消息进行标记,等下次消息消费进行校验
5、Kafka消息的发送过程简单说明
当我们使用Kafka发送消息时,一般有两种方式,分别是同步发送(producer.send(msg).get())及异步发送(producer.send(msg,cal1back))。
同步发送是在发送消息后,通过get方法获取消息结果:producer.send(record)get();,这种情况下拿到消息最终的发送结果,要么是成功,要么是失败。
而异步发送,是采用了callback的方式进行回调的,可以大大的提升消息的吞吐量,也可以根据回调来判断消息是否发送成功。
不管是同步发送还是异步发送,最终都需要在Producer端把消息发送到Broker中。
Kafka的Producer在发送消息时通常涉及两个线程,主线程(Main)和发送线程(Sender)和一个消息累加器(RecordAccumulator)
> Main线程是Producer的入口,负责初始化 Producer 的配置、创建 KafkaProducer 实例并执行发送逻辑。
它会按照用户定义的发送方式(同步或异步)发送消息,然后等待消息发送完成。
> RecordAccumulator在 Kafka Producer 中起到了消息积累和批量发送的作用,当Producer 发送消息时,不会立即将每条消息发送到Broker,
而是将消息添加到RecordAccumulator维护的内部缓冲区中,RecordAccumulator根据配置的条件(如batch.size、linger.ms)对发送的消息进行批量处理。
当满足指定条件时,RecordAccumulator 将缓冲区中的消息组织成一个批次(batch)一次性的发送给Broker。
若发送失败或发生错误,RecordAccumulator可以将消息重新分配到新的批次中进行重试。
> Send线程是负责实际的消息发送和处理的。发送线程会定期从待发送队列中取出消息,并将其发送到对应Partition的Leader Broker上。
6、Kafka的ISR机制
ISR,是In-Sync Replicas,同步副本的意思 在Kafka中,每个主题分区可以有多个副本(replica)。
ISR是与主副本(Leader Replica)保持同步的副本集合。
ISR机制就是用于确保数据的可靠性和一致性的。 当消息被写入Kafka的分区时,它首先会被写入Leader,然后Leader将消息复制给ISR中的所有副本。
只有当ISR中的所有副本都成功地接收到并确认了消息后,主副本才会认为消息已成功提交。这种机制确保了数据的可靠性和一致性。
7、如何处理消息积压问题
消息积压有如下几种情况:
(1)消息堆积在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 机器来消费消息。
(2)优化一下消费的逻辑,比如之前是一条一条消息消费处理的话,我们可以确认是不是可以优化为批量处理消息。
如果还是慢,我们可以考虑水平扩容,增加 Topic 的队列数和消费组机器的数量,提升整体消费能力。
8、如何保证消息的高可用
消息中间件如何保证高可用呢? 单机是没有高可用可言的,高可用都是对集群来说的。
Kafka 的基础集群架构:由多个 broker组成,每个 broker都是一个节点。当你创建一个 topic时,它可以划分为多个 partition,
而每个 partition放一部分数据,分别存在于不同的 broker 上。
也就是说,一个 topic 的数据,是分散放在多个机器上的,每个机器就放一部分数据。
Kafka 0.8 以前,是没有 HA 机制的,Kafka 0.8 之后,提供了复制品副本机制来保证高可用,即每个 partition 的数据都会同步到其它机器上,形成多个副本。
然后所有的副本会选举一个 leader 出来,让 leader 去跟生产和消费者打交道,其他副本都是 follower。
写数据时,leader 负责把数据同步给所有的 follower,读消息时, 直接读 leader 上的数据即可。
如何保证高可用的?就是假设某个 broker 宕机,这个broker 上的 partition 在其他机器上都有副本的。
如果挂的是 leader 的broker 呢?其他 follower 会重新选一个 leader 出来。
9、如何保证消息的一致性,使用事务实现
(1)生产者产生消息,发送一条事务消息到 MQ 服务器。
(2)MQ 收到消息后,将消息持久化到存储系统,这条消息的状态是待发送状态。
(3)MQ 服务器返回 ACK 确认到生产者,此时 MQ 不会触发消息推送事件。
(4)生产者执行本地事务,如果本地事务执行成功,即 commit 执行结果到 MQ 服务器;如果执行失败,发送 rollback。
(5)如果是正常的 commit,MQ 服务器更新消息状态为可发送;如果是 rollback,即删除消息。
(6)如果消息状态更新为可发送,则 MQ 服务器会 push 消息给消费者。消费者消费完就回 ACK。
(7)如果 MQ 服务器长时间没有收到生产者的 commit 或者 rollback,它会反查生产者,然后根据查询到的结果执行最终状态。
10、如何设计一个消息队列
(1)首先是消息队列的整体流程,producer 发送消息给 broker,broker 存储好,broker 再发送给 consumer 消费,consumer 回复消费确认等。
(2)producer 发送消息给 broker,broker 发消息给 consumer 消费,那就需要两次RPC 了,RPC 如何设计呢?你可以说说服务发现、序列化协议等等
(3)broker 考虑如何持久化呢,是放文件系统还是数据库呢,会不会消息堆积呢,消息堆积如何处理呢。
(4)消费关系如何保存呢? 点对点还是广播方式呢?广播关系又是如何维护呢?zk 还是 config server?
(5)消息可靠性如何保证呢?如果消息重复了,如何幂等处理呢?
(6)消息队列的高可用如何设计呢? 可以参考 Kafka 的高可用保障机制。多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。
(7)消息事务特性,与本地业务同个事务,本地消息落库;消息投递到服务端,本地才删除;定时任务扫描本地消息库,补偿发送。
(8)MQ 得伸缩性和可扩展性,如果消息积压或者资源不够时,如何支持快速扩容,提高吞吐量?
可以参照一下 Kafka 的设计理念,broker -> topic -> partition,每个partition 放一个机器,就存一部分数据。
如果现在资源不够了,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了
11、Kafka、RocketMQ、RabbitMQ之间的区别
吞吐量 | 优先级队列 | 延迟队列 | 死信队列 | 消息消费模式 | 事务消息 | |
Kafka | 百万级 | 不支持 | 不支持 | 不支持 | 支持拉模式 | 仅支持生产消息事务 |
RocketMQ | 十万级至百万级 | 支持 | 支持 | 支持 | 支持推、拉模式 | 支持事务消息 |
RabbitMQ | 万级至十万级 | 支持 | 支持 | 支持 | 支持推、拉模式 | 支持基本的事务消息 |