师兄大厂面试遇到面试官的 Kafka 暴击三连问,快面哭了!

共 5591字,需浏览 12分钟

 ·

2020-09-10 23:22

一、背景

前段时间,师兄一直在四处奔波面试中,和我分享了很多面试题和经验,我都一一拿小本本记下来 ( 超认真 !!)

其实在今天的很多面试里,都会要求能够熟练运用Apache Kafka等至少一种消息队列,Apache Kafka也是我们 面试里的常客。

但在大多数人的印象中,写磁盘都是比较慢的,可是,为什么Apache Kafka在各大MQ性能的评测中,还能够击败众多对手,取得不错的成绩呢?

接下来就让我们通过师兄遇到的关于 Kafka 问题的暴击三连问 ,走进他受伤的心 ? ....

二、带着疑问思考

  1. 「Kafka 为什么快?」
  2. 「Kafka 和其他消息队列的区别?」
  3. 「Kafka 这么快,它是如何保证不丢失消息?」

三、我的回答思路

其实,在大数据开发岗的面试中,都避免不了 面试官问你 Kafka ,毕竟 Kafka 和 Kafka Streams 耍起来确实很香啊!

那我们就先从 Kafka 的基础知识了解起吧,还不太了解的小伙伴可以先看看这几篇博客,先构建一个技术栈的知识框架,这样在面试时就能做到无懈可击啦!?

Kafka
【Kafka】(一)kafka 简介与设计、实现分析
【Kafka】(十)Kafka 如何实现高吞吐量
【Kafka】(十一)Kafka 的备份机制
【Kafka】(十四)Kafka 架构深入

四、关于 Kafka 为什么这么快

Kafka 的消息是保存或缓存在磁盘上的,一般认为在磁盘上读写数据是会降低性能的,因为寻址会比较消耗时间,但

是实际上,Kafka 的特性之一就是高吞吐率。Kafka 之所以能这么快,无非是:「顺序写磁盘、大量使用内存页 、零拷贝技术的使用」...

下面我就从数据写入和读取两方面分析,为大家分析下为什么 Kafka 速度这么快。

数据写入 Kafka 会把收到的消息都写入到硬盘中,它绝对不会丢失数据。为了优化写入速度 Kafka 采用了两个技术, 顺序写入和 Memory Mapped File 。

4.1 顺序写入

Kafka 会把收到的消息都写入到硬盘中,它绝对不会丢失数据。为了优化写入速度 Kafka 采用了两个技术, 顺序写入和 MMFile(Memory Mapped File)。

磁盘读写的快慢取决于你怎么使用它,也就是顺序读写或者随机读写。在顺序读写的情况下,磁盘的顺序读写速度和内存持平。因为硬盘是机械结构,每次读写都会寻址->写入,其中寻址是一个“机械动作”,它是最耗时的。「所以硬盘最讨厌随机 I/O,最喜欢顺序 I/O」。为了提高读写硬盘的速度,Kafka 就是使用顺序 I/O。

而且 Linux 对于磁盘的读写优化也比较多,包括 read-ahead 和 write-behind,磁盘缓存等。

如果在内存做这些操作的时候,一个是 Java 对象的内存开销很大,另一个是随着堆内存数据的增多,Java 的 GC 时间会变得很长。

使用磁盘操作有以下几个好处:

  • 磁盘顺序读写速度超过内存随机读写。

  • JVM 的 GC 效率低,内存占用大。使用磁盘可以避免这一问题。

  • 系统冷启动后,磁盘缓存依然可用。

下图就展示了 Kafka 是如何写入数据的, 每一个 Partition 其实都是一个文件 ,收到消息后 Kafka 会把数据插入到文件末尾(虚框部分):这种方法有一个缺陷——没有办法删除数据 ,所以 Kafka 是不会删除数据的,它会把所有的数据都保留下来,每个 消费者(Consumer)对每个 Topic 都有一个 Offset 用来表示读取到了第几条数据 。一般情况下 Offset 由客户端 SDK 负责保存 ,会保存到 Zookeeper 里面 。关于存在硬盘中的消息,Kafka 也有它的解决方法,可以基于时间和 Partition 文件的大小,正常 Kafka 是默认七天的保存,也可以通过命令来修改,以 users topic 为例。

修改kafka 7天 默认保存周期kafka-topics.sh --zookeeper 6 --alter --topic users --config retention.ms=100000

所以,为了避免磁盘被撑满的情况,Kakfa 提供了两种策略来删除数据:

  1. 「基于时间」 (默认七天)
  2. 「基于 Partition 文件大小」

4.2 Memory Mapped Files

这个和Java NIO中的内存映射基本相同,在大学的计算机原理里我们学过(划重点),mmf (Memory Mapped Files)直接利用操作系统的Page来实现文件到物理内存的映射,完成之后对物理内存的操作会直接同步到硬盘。mmf 通过内存映射的方式大大提高了IO速率,省去了用户空间到内核空间的复制。它的缺点显而易见--不可靠,当发生宕机而数据未同步到硬盘时,数据会丢失,Kafka 提供了produce.type参数来控制是否主动的进行刷新,如果 Kafka 写入到 mmf 后立即flush再返回给生产者则为同步模式,反之为异步模式。

Kafka 提供了一个参数 producer.type 来控制是不是主动 Flush:

  • 如果 Kafka 写入到 mmf 之后就立即 Flush,然后再返回 Producer 叫同步 (Sync)。

  • 如果 Kafka 写入 mmf 之后立即返回 Producer 不调用 Flush 叫异步 (Async)。

数据读取 Kafka 在读取磁盘时做了哪些优化?

4.3 基于 Sendfile 实现零拷贝(Zero Copy)

作为一个消息系统,不可避免的便是消息的拷贝,常规的操作,一条消息,需要从创建者的socket到应用,再到操作系统内核,然后才能落盘。同样,一条消息发送给消费者也要从磁盘到内核到应用再到接收者的socket,中间经过了多次不是很有必要的拷贝。

传统 Read/Write 方式进行网络文件传输,在传输过程中,文件数据实际上是经过了四次 Copy 操作,其具体流程细节如下:

  • 调用 Read 函数,文件数据被 Copy 到内核缓冲区。

  • Read 函数返回,文件数据从内核缓冲区 Copy 到用户缓冲区

  • Write 函数调用,将文件数据从用户缓冲区 Copy 到内核与 Socket 相关的缓冲区。

  • 数据从 Socket 缓冲区 Copy 到相关协议引擎。

硬盘—>内核 buf—>用户 buf—>Socket 相关缓冲区—>协议引擎

而 Sendfile 系统调用则提供了一种减少以上多次 Copy,提升文件传输性能的方法。在内核版本 2.1 中,引入了 Sendfile 系统调用,以简化网络上和两个本地文件之间的数据传输。Sendfile 的引入不仅减少了数据复制,还减少了上下文切换。相较传统 Read/Write 方式,2.1 版本内核引进的 Sendfile 已经减少了内核缓冲区到 User 缓冲区,再由 User 缓冲区到 Socket 相关缓冲区的文件 Copy。而在内核版本 2.4 之后,文件描述符结果被改变,Sendfile 实现了更简单的方式,再次减少了一次 Copy 操作。

Kafka 把所有的消息都存放在一个一个的文件中,当消费者需要数据的时候 Kafka 直接把文件发送给消费者,配合 mmap 作为文件读写方式,直接把它传给 Sendfile。

4.4 批量发送

Kafka允许进行批量发送消息,producter发送消息的时候,可以将消息缓存在本地,等到了固定条件发送到 Kafka 。

  • 等消息条数到固定条数。
  • 一段时间发送一次。

4.5 数据压缩

Kafka还支持对消息集合进行压缩,Producer可以通过GZIP或Snappy格式对消息集合进行压缩。压缩的好处就是减少传输的数据量,减轻对网络传输的压力。

Producer压缩之后,在Consumer需进行解压,虽然增加了CPU的工作,但在对大数据处理上,瓶颈在网络上而不是CPU,所以这个成本很值得。

注意:「批量发送」「数据压缩」一起使用,单条做数据压缩的话,效果不明显 ❗

4.6 总结

以上,便是Apache Kafka虽然使用了硬盘存储,但是仍然可以速度很快的原因。它把所有的消息都变成一个批量的文件,并且进行合理的批量压缩,减少网络 IO 损耗,通过 mmap 提高 I/O 速度。写入数据的时候由于单个 Partion 是末尾添加,所以速度最优;读取数据的时候配合 Sendfile 直接暴力输出。

五、关于 Kafka 和其他消息队列的区别

Kafka的设计目标是高吞吐量,那它与其它消息队列的区别就显而易见了:

1、Kafka操作的是序列文件I / O(序列文件的特征是按顺序写,按顺序读),为保证顺序,Kafka强制点对点的按顺序传递消息,这意味着,一个consumer在消息流(或分区)中只有一个位置。

2、Kafka不保存消息的状态,即消息是否被“消费”。一般的消息系统需要保存消息的状态,并且还需要以随机访问的形式更新消息的状态。而Kafka 的做法是保存Consumer在Topic分区中的位置offset,在offset之前的消息是已被“消费”的,在offset之后则为未“消费”的,并且offset是可以任意移动的,这样就消除了大部分的随机IO。

3、Kafka支持点对点的批量消息传递。

4、Kafka的消息存储在OS pagecache(页缓存,page cache的大小为一页,通常为4K,在Linux读写文件时,它用于缓存文件的逻辑内容,从而加快对磁盘上映像和数据的访问)。

RabbitMQ:分布式,支持多种MQ协议,重量级 ActiveMQ:与RabbitMQ类似 ZeroMQ:以库的形式提供,使用复杂,无持久化 Redis:单机、纯内存性好,持久化较差 Kafka:分布式,消息不是使用完就丢失【较长时间持久化】,吞吐量高【高性能】,轻量灵活

六、Kafka 如何保证消息队列不丢失

我想,如果小伙伴直接遇到面试官问:Kafka 为什么这么快,又能保证不丢失消息?肯定是蒙蔽的,但如果前面回答的仅仅有条,这个时候再回答消息不丢失,就很容易啦 !Σ(っ °Д °;)っ

6.1 关于 ACK 机制

关于 ACK 机制 ,不了解的小伙伴,可以看这里:Kafka 架构深入 ,通过 ACK 机制保证消息送达。Kafka 采用的是至少一次(At least once),消息不会丢,但是可能会重复传输。

acks 的默认值即为1,代表我们的消息被leader副本接收之后就算被成功发送。我们可以配置 acks = all ,代表则所有副本都要接收到该消息之后该消息才算真正成功被发送。

6.2 关于设置分区

为了保证 leader 副本能有 follower 副本能同步消息,我们一般会为 topic 设置 replication.factor >= 3。这样就可以保证每个 分区(partition) 至少有 3 个副本,以确保消息队列的安全性。

6.3 关闭 unclean leader 选举

我们最开始也说了我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。多个 follower 副本之间的消息同步情况不一样,当我们配置了 unclean.leader.election.enable = false 的话,当 leader 副本发生故障时就不会从 follower 副本中和 leader 同步程度达不到要求的副本中选择出 leader ,这样降低了消息丢失的可能性。

6.4 关于发送消息

为了得到更好的性能,Kafka 支持在生产者一侧进行本地buffer,也就是累积到一定的条数才发送,如果这里设置不当是会丢消息的。

生产者端设置:producer.type=async, sync,默认是 sync。

当设置为 async,会大幅提升性能,因为生产者会在本地缓冲消息,并适时批量发送。

如果对可靠性要求高,那么这里可以设置为 sync 同步发送。

一般时候我们还需要设置:min.insync.replicas> 1 ,消息至少要被写入到这么多副本才算成功,也是提升数据持久性的一个参数,与acks配合使用。

但如果出现两者相等,我们还需要设置 replication.factor = min.insync.replicas + 1 ,避免在一个副本挂掉,整个分区无法工作的情况!

6.5 Consumer 端丢失消息

consumer端丢失消息的情形比较简单:

如果在消息处理完成前就提交了offset,那么就有可能造成数据的丢失。

由于Kafka consumer默认是自动提交位移的,所以在后台提交位移前一定要保证消息被正常处理了,因此不建议采用很重的处理逻辑,如果处理耗时很长,则建议把逻辑放到另一个线程中去做。

为了避免数据丢失,现给出几点建议:设置 enable.auto.commit=false

关闭自动提交位移,在消息被完整处理之后再手动提交位移。

以上,便是本篇博客的所有内容,希望师兄大厂面试顺利 !

— 【 THE END 】—
本公众号全部博文已整理成一个目录,请在公众号里回复「m」获取!


3T技术资源大放送!包括但不限于:Java、C/C++,Linux,Python,大数据,人工智能等等。在公众号内回复「1024」,即可免费获取!!




浏览 20
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报