文件目录布局
同一分区,有一个日志目录,日志目录下分为多个日志文件,每个日志文件被称为日志段,相当于一个大文件被切分为多个小文件。一个日志段包含一个.log的日志文件,2个索引文件。
向Log中追加消息是顺序写入的,只有最后一个日志段才能写入,之前的都不能写入,此称为activeSegment。日志段文件的命名包含baseOffset,也就是起始或基准偏移量,名称固定为20位,没有达到则用0补齐,比如第一个日志段的基准偏移量,对应的文件名称为:00000000000000000000.log。如本机的日志目录文件如图:
磁盘存储
顺序写(线性写)
操作系统会针对线性写作很多优化,比如预读(read-ahead, 提前将连续的磁盘块读入内存),后写(write-behind,将很多小的逻辑写操作合并为一个大的物理写操作)。顺序写入磁盘的速度不仅比随机写磁盘块,而且比随机写内存的速度都快。比如一个例子:
Kafka在写磁盘的上选择了这种处理模式,在设计上采用文件追加的顺序写入方式来写入消息 ,通过在文件默认追加日志,且不允许修改已写入的日志。这种方式极大的提高了Kafka的写入性能与消息吞吐量。
页缓存
页缓存是操作系统用来实现磁盘的缓存,用来减少对磁盘的IO操作。
当一个进程准备读取磁盘上的文件时,操作系统会先检查待读取的数据所在的页(page)是否在页缓存中(pagecache). 如果存在(命中)则直接返回数据,从而避免对物理磁盘的IO操作。如果没有命中,则操作系统会向磁盘发起读取请求并把读取的数据页载入页缓存,之后再将数据返回给进程。
如果是写入数据到磁盘,操作系统也会先检查页,如果不存在仍然先载入页到页缓存中,然后将数据直接写入对应的页中。被修改过的页也称为脏页,操作系统会自己控制将页写入到磁盘中,以保证数据一致性。
Kafka默认写入数据到页缓存中后,默认交给操作系统执行刷盘,也可以调用操作系统的函数执行同步刷盘。
磁盘IO流程
零拷贝
Kafka除了使用顺序追加写,页缓存等技术,还是用零拷贝(Zero-Copy)技术来进一步提升性能。所谓零拷贝就是直接将数据从磁盘文件复制到网卡设备中,而不需要经过应用程序的转换。零拷贝大大提升了应用程序的性能,减少了用户空间与内核空间的上下文切换,Linux底层通过sendfile()
来实现零拷贝,对应Java应用程序,使用FileChannel.transferTo()
来间接使用sendFile()函数。
例如发送文件A时,简单的抽象为read() -> write(),非零拷贝的处理模式(常规的模式)如下:
而零拷贝处理模式中,应用程序可以直接请求内核把磁盘中的数据传入到网卡缓冲区中: