kafka如何做到磁盘读写比内存读写还快?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了kafka如何做到磁盘读写比内存读写还快?相关的知识,希望对你有一定的参考价值。

参考技术A

Kafka作为一个支持大数据量写入写出的消息队列,由于是基于Scala和Java实现的,而Scala和Java均需要在JVM上运行,所以如果是基于内存的方式,即JVM的堆来进行数据存储则需要开辟很大的堆来支持数据读写,从而会导致GC频繁影响性能。考虑到这些因素,kafka是使用磁盘存储数据的。

Kafka 中消息是以 topic 进行分类的,生产者生产消息,消费者消费消息,都是面向topic的。topic存储结构见下图:

由于生产者生产的消息会不断追加到 log 文件末尾,为防止 log 文件过大导致数据定位效率低下,Kafka 采取了 分片 索引 机制,将每个partition分为多个segment。每个 segment对应两个文件——“.index”文件和“.log”文件。

partition文件夹命名规则:
topic 名称+分区序号,举例有一个topic名称文“kafka”,这个topic有三个分区,则每个文件夹命名如下:

index和log文件的命名规则:
1)partition文件夹中的第一个segment从0开始,以后每个segement文件以上一个segment文件的最后一条消息的offset+1命名(当前日志中的第一条消息的offset值命名)。
2)数值最大为64位long大小。19位数字字符长度,没有数字用0填充。

举例,有以下三对文件:

以第二个文件为例看下对应的数据结构:

稀疏索引 需要注意下。

消息查找过程

找message-2589,即offset为2589:
1)先定位segment文件,在0000000000000002584中。
2)计算查找的offset在日志文件的相对偏移量
offset - 文件名的数量 = 2589 - 2584 = 5;
在index文件查找第一个参数的值,若找到,则获取到偏移量,通过偏移量到log文件去找对应偏移量的数据即可;
本例中没有找到,则找到当前索引中偏移量的上线最接近的值,即3,偏移量文246;然后到log文件中从偏移量为246数据开始向下寻找。

简单了解了kafka在数据存储方面的知识,线面我们具体分析下为什么kafka基于磁盘却快于内存。

在前面了解存储结构过程中,我们发现kafka记录log日志使用的结尾追加的方式,即 顺序写 。这样要比随机写块很多,这与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。

mmap,简单描述其就是将磁盘文件映射到内存, 用户通过修改内存就能修改磁盘文件。

即便是顺序写磁盘,磁盘的读写速度任然比内存慢慢的多得多,好在操作系统已经帮我们解决这个问题。在Linux操作系统中,Linux会将磁盘中的一些数据读取到内存当中,我们称之为内存页。当需要读写硬盘的时候,都优先在内存页中进行处理。当内存页的数据比硬盘数据多的时候,就形成了 脏页 ,当脏页达到一定数量,操作系统会进行 刷脏 ,即将内存也数据写到磁盘。

问题:不可靠,写到 mmap 中的数据并没有被真正的写到硬盘,操作系统会在程序主动调用 Flush 的时候才把数据真正的写到硬盘。

零拷贝并不是不需要拷贝,而是减少不必要的拷贝次数,通常使用在IO读写过程中。

传统io过程

如上图所示,上图共经历了四次拷贝的过程:
1)数据到到内核态的read buffer;
2)内核态的read buffer到用户态应用层的buffer;
3)用户态到内核态的socket buffer;
4)socket buffer到网卡的buffer(NIC)。

DMA
引入DMA技术,是指外部设备不通过CPU而直接与系统内存交换数据的接口技术,网卡等硬件设备支持DMA技术。

如上图所示,上图共经历了两次拷贝的过程。

sendfile
在内核版本 2.1 中,引入了 Sendfile 系统调用,以简化网络上和两个本地文件之间的数据传输。同时使用了DMA技术。

如上图所示,上图共经历了一次拷贝的过程。

sendfile( DMA 收集拷贝)
之前我们是把页缓存的数据拷贝到socket缓存中,实际上,我们仅仅需要把缓冲区描述符传到 socket 缓冲区,再把数据长度传过去,这样 DMA 控制器直接将页缓存中的数据打包发送到网络中就可以了。

如上图所示,最后一次的拷贝也被消除了,数据->read buffer->NIC。

kafka通过java和scala实现,而Java对sendfile是通过NIO 的 FileChannel (java.nio.channels.FileChannel )的 transferTo 和 transferFrom 方法实现零拷贝

注: transferTo 和 transferFrom 并不保证一定能使用零拷贝。实际上是否能使用零拷贝与操作系统相关,如果操作系统提供 sendfile 这样的零拷贝系统调用,则这两个方法会通过这样的系统调用充分利用零拷贝的优势,否则并不能通过这两个方法本身实现零拷贝。

GitHub标星过万!mysql绝对值

Kafka 如何做到支持百万级 TPS ?

先用一张思维导图直接告诉你答案:

顺序读写磁盘

生产者写入数据和消费者读取数据都是顺序读写的,先来一张图直观感受一下顺序读写和随机读写的速度:

从图中可以看出传统硬盘或者SSD的顺序读写甚至超过了内存的随机读写,当然与内存的顺序读写对比差距还是很大。

所以Kafka选择顺序读写磁盘也不足为奇了。

下面以传统机械磁盘为例详细介绍一下什么是顺序读写和随机读写。

盘片盘面:一块硬盘一般有多块盘片,盘片分为上下两面,其中有效面称为盘面,一般上下都有效,也就是说:盘面数 = 盘片数 * 2。

磁头:磁头切换磁道读写数据时是通过机械设备实现的,一般速度较慢;而磁头切换盘面读写数据是通过电子设备实现的,一般速度较快,因此磁头一般是先读写完柱面后才开始寻道的(不用切换磁道),这样磁盘读写效率更快。

磁道:磁道就是以中间轴为圆心的圆环,一个盘面有多个磁道,磁道之间有间隙,磁道也就是磁盘存储数据的介质。磁道上布有一层磁介质,通过磁头可以使磁介质的极性转换为数据信号,即磁盘的读,磁盘写刚好与之相反。

柱面:磁盘中不同盘面中半径相同的磁道组成的,也就是说柱面总数 = 某个盘面的磁道数。

扇区:单个磁道就是多个弧形扇区组成的,盘面上的每个磁道拥有的扇区数量是相等。扇区是最小存储单元,一般扇区大小为512bytes。

如果系统每次只读取一个扇区,那恐怕效率太低了,所以出现了block(块)的概念。文件读取的最小单位是block,根据不同操作系统一个block一般由多个扇区组成。

有了磁盘的背景知识我们就可以很容易理解顺序读写和随机读写了。

插播维基百科定义:顺序读写:是一种按记录的逻辑顺序进行读、写操作的存取方法 ,即按照信息在存储器中的实际位置所决定的顺序使用信息。 随机读写:指的是当存储器中的消息被读取或写入时,所需要的时间与这段信息所在的位置无关。

当读取第一个block时,要经历寻道、旋转延迟、传输三个步骤才能读取完这个block的数据。而对于下一个block,如果它在磁盘的其他任意位置,访问它会同样经历寻道、旋转、延时、传输才能读取完这个block的数据,我们把这种方式叫做随机读写。但是如果这个block的起始扇区刚好在刚才访问的block的后面,磁头就能立刻遇到,不需等待直接传输,这种就叫顺序读写

好,我们再回到 Kafka,详细介绍Kafka如何实现顺序读写入数据。

Kafka 写入数据是顺序的,下面每一个Partition 都可以当做一个文件,每次接收到新数据后Kafka会把数据插入到文件末尾,虚框部分代表文件尾。

这种方法有一个问题就是删除数据不方便,所以 Kafka 一般会把所有的数据都保留下来,每个消费者(Consumer)对每个Topic都有一个 offset 用来记录读取进度或者叫坐标。

Memory Mapped Files(MMAP)

在文章开头我们看到硬盘的顺序读写基本能与内存随机读写速度媲美,但是与内存顺序读写相比还是太慢了,那 Kafka 如果有追求想进一步提升效率怎么办?可以使用现代操作系统分页存储来充分利用内存提高I/O效率,这也是下面要介绍的 MMAP 技术。

MMAP也就是内存映射文件,在64位操作系统中一般可以表示 20G 的数据文件,它的工作原理是直接利用操作系统的 Page 来实现文件到物理内存的直接映射,完成映射之后对物理内存的操作会被同步到硬盘上。

通过MMAP技术进程可以像读写硬盘一样读写内存(逻辑内存),不必关心内存的大小,因为有虚拟内存兜底。这种方式可以获取很大的I/O提升,省去了用户空间到内核空间复制的开销。

也有一个很明显的缺陷,写到MMAP中的数据并没有被真正的写到硬盘,操作系统会在程序主动调用 flush 的时候才把数据真正的写到硬盘。

Kafka提供了一个参数:producer.type 来控制是不是主动 flush,如果Kafka写入到MMAP之后就立即flush然后再返回Producer叫同步(sync);写入MMAP之后立即返回Producer不调用flush叫异步(async)。

Zero Copy(零拷贝)

Kafka 另外一个黑技术就是使用了零拷贝,要想深刻理解零拷贝必须得知道什么是DMA。

什么是DMA?

众所周知 CPU 的速度与磁盘 IO 的速度比起来相差几个数量级,可以用乌龟和火箭做比喻。

一般来说 IO 操作都是由 CPU 发出指令,然后等待 IO 设备完成操作后返回,那CPU会有大量的时间都在等待IO操作。

但是CPU 的等待在很多时候并没有太多的实际意义,我们对于 I/O 设备的大量操作其实都只是把内存里面的数据传输到 I/O 设备而已。比如进行大文件复制,如果所有数据都要经过 CPU,实在是有点儿太浪费时间了。

基于此就有了DMA技术,翻译过来也就是直接内存访问(Direct Memory Access),有了这个可以减少 CPU 的等待时间。

Kafka 零拷贝原理

如果不使用零拷贝技术,消费者(consumer)从Kafka消费数据,Kafka从磁盘读数据然后发送到网络上去,数据一共发生了四次传输的过程。其中两次是 DMA 的传输,另外两次,则是通过 CPU 控制的传输。

第一次传输:从硬盘上将数据读到操作系统内核的缓冲区里,这个传输是通过 DMA 搬运的。

第二次传输:从内核缓冲区里面的数据复制到分配的内存里面,这个传输是通过 CPU 搬运的。

第三次传输:从分配的内存里面再写到操作系统的 Socket 的缓冲区里面去,这个传输是由 CPU 搬运的。

第四次传输:从 Socket 的缓冲区里面写到网卡的缓冲区里面去,这个传输是通过 DMA 搬运的。

实际上在kafka中只进行了两次数据传输,如下图:

第一次传输:通过 DMA从硬盘直接读到操作系统内核的读缓冲区里面。

第二次传输:根据 Socket 的描述符信息直接从读缓冲区里面写入到网卡的缓冲区里面。

我们可以看到同一份数据的传输次数从四次变成了两次,并且没有通过 CPU 来进行数据搬运,所有的数据都是通过 DMA 来进行传输的。没有在内存层面去复制(Copy)数据,这个方法称之为零拷贝(Zero-Copy)。

无论传输数据量的大小,传输同样的数据使用了零拷贝能够缩短 65% 的时间,大幅度提升了机器传输数据的吞吐量,这也是Kafka能够支持百万TPS的一个重要原因。

Batch Data(数据批量处理)

当消费者(consumer)需要消费数据时,首先想到的是消费者需要一条,kafka发送一条,消费者再要一条kafka再发送一条。但实际上 Kafka 不是这样做的,Kafka 耍小聪明了。

Kafka 把所有的消息都存放在一个一个的文件中,当消费者需要数据的时候 Kafka 直接把文件发送给消费者。比如说100万条消息放在一个文件中可能是10M的数据量,如果消费者和Kafka之间网络良好,10MB大概1秒就能发送完,既100万TPS,Kafka每秒处理了10万条消息。

看到这里你可以有疑问了,消费者只需要一条消息啊,kafka把整个文件都发送过来了,文件里面剩余的消息怎么办?不要忘了消费者可以通过offset记录消费进度。

发送文件还有一个好处就是可以对文件进行批量压缩,减少网络IO损耗。

学习分享,共勉

这里是小编拿到的学习资源,其中包括“中高级Java开发面试高频考点题笔记300道.pdf”和“Java核心知识体系笔记.pdf”文件分享,内容丰富,囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。同时还有Java进阶学习的知识笔记脑图(内含大量学习笔记)!

资料都是免费提供的,整理不易,有需要的朋友可以转发分享下,同时可以关注我,定期分享学习资源还会更新一些技术分享!

资料免费领取:点击这里即可免费获取!

整理不易,有需要的朋友转发支持下,感谢感谢!

Java核心知识体系笔记.pdf

中高级Java开发面试高频考点题笔记300道.pdf

架构进阶面试专题及架构学习笔记脑图

Java架构进阶学习视频分享

0691)]

架构进阶面试专题及架构学习笔记脑图

[外链图片转存中…(img-ov7CFYYI-1627092570692)]

Java架构进阶学习视频分享

[外链图片转存中…(img-tYTqQ3Vn-1627092570693)]

以上是关于kafka如何做到磁盘读写比内存读写还快?的主要内容,如果未能解决你的问题,请参考以下文章

Java框架!java线程安全的list

Java框架!java线程安全的list

GitHub标星过万!mysql绝对值

2021最新Java面经分享,内容太过真实

四面阿里拿offer面经分享,Java面试题汇总

专属于Java程序员的学习福音,附高频面试题合集