Kafka日志存储

1.文件目录布局

Kafka消息以日志文件的形式存储,不同主题下不同分区的消息分开存储,同一个分区的不同副本分布在不同的broker上存储

逻辑上看来日志是以副本为单位的,每个副本对应一个log对象,实际在物理上,一个log划分为多个logSegment

创建一个topic为3个分区,会在log.dirs路径下创建三个文件夹,代表3个分区,命名规则为“topic名称-分区编号”

logSegment并不代表这个文件夹,logSegment代表逻辑上的一组文件,这组文件就是.log、.index、.timeindex这三个不同文件扩展名,但是同文件名的文件

  • .log存储消息
  • .index存储消息的索引
  • .timeIndex,时间索引文件,通过时间戳做索引

同一个logSegment的三个文件,文件名是一样的。命名规则为.log文件中第一条消息的前一条消息偏移量,也称为基础偏移量,左边补0,补齐20位。比如说第一个LogSegement的日志文件名为00000000000000000000.log,假如存储了200条消息后,达到了log.segment.bytes配置的阈值(默认1个G),那么将会创建新的logSegment,文件名为00000000000000000200.log。以此类推。另外即使没有达到log.segment.bytes的阈值,而是达到了log.roll.ms或者log.roll.hours设置的时间触发阈值,同样会触发产生新的logSegment。

2.日志定位

日志定位也就是消息定位,输入一个消息的offset,kafka如何定位到这条消息呢?

日志定位的过程如下:

1、根据offset定位logSegment。(kafka将基础偏移量也就是logsegment的名称作为key存在concurrentSkipListMap中)

2、根据logSegment的index文件查找到距离目标offset最近的被索引的offset的position x。

3、找到logSegment的.log文件中的x位置,向下逐条查找,找到目标offset的消息。

这里先说明一下.index文件的存储方式。.index文件中存储了消息的索引,存储内容是消息的offset及物理位置position。并不是每条消息都有自己的索引,kafka采用的是稀疏索引,说白了就是隔n条消息存一条索引数据。这样做比每一条消息都建索引,查找起来会慢,但是也极大的节省了存储空间。此例中我们假设跨度为2,实际kafka中跨度并不是固定条数,而是取决于消息累积字节数大小。

例子中consumer要消费offset=15的消息。我们假设目前可供消费的消息已经存储了三个logsegment,分别是00000000000000000,0000000000000000010,0000000000000000020。为了讲解方便,下面提到名称时,会把前面零去掉。

下面我们详细讲一下查找过程。

  • kafka收到查询offset=15的消息请求后,通过二分查找,从concurrentSkipListMap中找到对应的logsegment名称,也就是10。
  • 从10.index中找到offset小于等于15的最大值,offset=14,它对应的position=340
  • 从10.log文件中物理位置340,顺序往下扫描文件,找到offset=15的消息内容。

可以看到通过稀疏索引,kafka既加快了消息查找的速度,也顾及了存储的开销

3.日志清理

Kafka提供了两种日志清理的方式:

1.日志删除(Log Retention):直接删除

2.日志压缩(Log Compaction):对相同key的不同value值,只保留最后一个版本

通过log.cleanup.policy参数来设置清理策略,默认值为"delete",如果要设置为压缩,需要改为"compact",还可以同时支持两种策略

1.日志删除

1.基于时间

根据日志分段中最大的时间戳来查找过期的日志分段文件

2.基于日志大小

检查当前日志大小是否超过retentionSize,默认值为-1,表示无穷大,该值代表所有日志文件的总大小,不是单个日志分段的大小,单个日志分段大小默认为1GB

3.基于日志起始偏移量

判断某个日志分段的下一个日志分段的起始偏移量baseOffset是否小于等于logStartOffset

4.磁盘存储

1.页缓存

页缓存是操作系统实现的一种主要的磁盘缓存,用来减少对磁盘I/O的操作,将磁盘中的数据缓存到内存中,将对磁盘的访问转变为对内存的访问

当一个进程准备读取磁盘上的文件内容时,操作系统会首先查看待读取的数据所在的是否在页缓存中,如果在直接返回数据,如果没有,操作系统会向磁盘发起读取请求并将读取的数据页存入页缓存,再将数据返回,写入的时候也是类似

优点:对于进程而言,会在进程内部缓存数据,同时有可能这些数据缓存在操作系统的页缓存中,被缓存了两次,使用页缓存可以省去进程内部的缓存消耗,还可以通过结构紧凑的字节码替代使用对象的方式节省更多空间,假设服务重启页缓存还是保持有效

Kafka大量使用了页缓存,这是Kafka实现高吞吐的重要因素之一。在Kafka中提供了同步刷盘和间断性强制刷盘的功能,但不建议使用,因为会严重影响性能

Linux系统使用磁盘的一部分作为swap分区,把非活跃进程放入swap分区,对于Kafka而言,应该避免这种内存的交换,否则会影响性能,建议把vm.swappiness值设为1,保留了该机制又使得它对Kafka的影响最小

2.磁盘I/O流程

I/O调度策略:

1.NOOP

No Operation,I/O请求大致按照先来后到顺序进行操作,做了相邻I/O请求的合并

2.CFQ

Completely Fair Queuing(默认),按照I/O请求地址进行排序

3.DEADLINE

除了CFQ本身具有的I/O队列,还为读I/O和写I/O提供了FIFO队列

4.ANTICIPATORY

满足随机I/O和顺序I/O混合的场景

3.零拷贝

将数据直接从磁盘文件复制到网卡设备中,不需要经过应用程序,减少了内核和用户态之间的上下文切换,依赖sendFile()方法实现

通过DMA(Direct Memory Access)方式数据只需要复制2次,上下文切换也只需要两次,零拷贝是针对内核模式的

原文地址:https://www.cnblogs.com/jordan95225/p/13437110.html