kafka--消息存储的原理

一、简介

我们知道Kafka中的消息是存储在磁盘上的, 那么为什么要使用磁盘作为存储介质?具体消息的存储格式又是什么呢?怎么样能够快速检索到指定的消息?消息不可能无限制存储, 那么清理规则又是什么呢?
 

二、文件目录布局

Kafk a 中的消息是以主题为基本单位进行归类的, 各个主题在逻辑 上相互独立。 每个主题又可以分为一 个或多个分区, 分区的数量可以在主题创建的时候指定, 也可以在之后修改。 每条消息在发送的时候会根据分区规则被追加到指定的分区中, 分区中的每条消息都会被分配一 个唯 一的序列号, 也就是通常所说的偏移量(offset ), 如果分区规则设置得合理, 那么所有的消息可以均匀地分布到不同的分区中, 这样就可以 实现水平扩展。 不考虑多副本的情况, 一个分区对应 个日志(Log)。 为了防止Log过大, Kafka又引入了日志分段(LogSegment)的概念,将Log切分为多个LogSegment, 相当于 一个 巨型文件被平均分配为多个相对较小的文件, 这样也便于消息的维护和清理
 
如图每个日志段对应一个log两个索引文件, 以及可能的其他文件(比如以 " . txnindex"为后缀的事务索引文件)
 
其他知识点:
(1)向Log中追加 消息时是顺序写入的, 只有最后 一 个LogSegment才能执行写入操作, 在此之 前所有的LogSegment都 不能写入数据,最后一个日志段也被称为当前活跃的日志分段

(2)Log对应了个命名形式为<topic<partition>的文件夹。 举个例子, 假设有 一 个名为"topic-log" 的主题 ,此主题中具有4个分区, 那么在实际物理存储上表现为 "topic-log-0" "topic-log-I" "topic-log-2" "topic-log-3"这4个文件夹

日志格式

   最新版本的kafka日志是以批为单位进行日志存储的,所谓的批指的是kafka会将多条日志压缩到同一个batch中,然后以batch为单位进行后续的诸如索引的创建和消息的查询等工作。对于每个批次而言,其默认大小为4KB,并且保存了整个批次的起始位移和时间戳等元数据信息,而对于每条消息而言,其位移和时间戳等元数据存储的则是相对于整个批次的元数据的增量,通过这种方式,kafka能够减少每条消息中数据占用的磁盘空间。这里我们首先展示一下每个批次的数据格式:

日志分段条件:

(1)当前日志分段文件的大小超过了 broker 端参数 log.s egme bytes 配置的值(默认1G)

(2)当前日志分段中消息的最大时间戳与当前系统的时间戳的差值大于 log.roll .ms 或log.roll.hours 参数配置的值(默认七天)

(3)偏移量索引文件或时间戳索引文件的大小达到 broker 端参数 最大值

(4)追加的消息的偏移量与当前日志分段的偏移量之间的差值大于 Integer.MAX_VALUE, 

三、日志索引

上面提到日志索引主要包含偏移量索引和时间戳索引

1.偏移量索引

偏移量索引项。每个索引项占用8个字节,分为两个部分。

( 1 ) relativeOffset :相 对偏移量,表示消息相对于 aseOffset 的偏移量,占用4 字节 当前索引文件的文件名即为 bas eOffset的值
( 2) position :物 理地址,也就是消息在日志分段文件中对应的物理位置,占用4 个字节。

 如果要查找偏移量为268的消息,过程应该是:首先是定位到 baseOffset 为251的日志分段,然后计算相对偏移量relativeOffset = 268 - 251 = 17 ,之后再在对应的索引文件中找到不大于17的索引项,最后根据索引项中的 position 定位到具体的日志分段文件位置开始查找目标消息。
那么又是如何查找 baseOffset 为 251 的日志分段的呢?这里并不是顺序查找,而是用了跳跃表的结构。Kafka 的每个日志对象中使用了 ConcurrentSkipListMap 来保存各个日志分段,每个日志分段的 baseOffset 作为 key,这样可以根据指定偏移量来快速定位到消息所在的日志分段。

Kafka强制要求索引文件大小必须是索引项大小的整数倍,对偏移量索引文件而言,必须为 8 的整数倍。如果 broker 端参数 log.index.size.max.bytes 配置为 67 ,那么 kafka 在内部会将其转换为 64,即不大于 67,并且满足为 8 的整数倍的条件。

2.时间戳索引

每个索引项 占用 12 个字节,分为两个部分。
( 1 ) timestamp 当前日志分段最大的时间戳。
( 2) relativeOffset :时间戳所对应的消息的相对偏移 量。
 
 
如果要查找指定时间戳 targetTimeStamp 开始的消息,首先是找到不小于指定时间戳的日志分段。 这里就无法像偏移量索引那样使用跳跃表来快速定位到相应的日志分段 了,需要分以下 几个步骤来完成。
步骤1 : 将 targetTimeStamp和每个日志分段中的最大时间戳 largestTimeStamp逐 对比, 直到找到不小于targetTimeStamp 的 largestTimeStamp 所对应的日志分段。 日志分段中的 largestTimeStamp的计算是先查询该日志分段所对应的时间戳索引文件, 找到最后一 条索引项, 若最后一 条索引项的时间戳字段值大于 o, 则取其值,否则取该日志分段的最近修改时间。
步骤2: 找到相应的日志分段之后,在时间戳索引文件中使用二分 查找算法查找到不大千 targetTimeStamp的最大索引项,即[1526384718283, 28], 如此便 找到了一 个相对偏移量 28。
步骤3: 在偏移量索引文件中使用二分 算法查找到不大 千28的最大索引项,即[26, 838]。
步骤4: 从步骤1中 找到日志分段文件中的838的物理位置开始 查找不小于targetTimeStamp 的消息。
 

四、消息压缩

1.简介:

每个Partition的日志文件存储的时候又会被分成一个个的Segment,默认的Segment的大小是1GB,有属性offsets.topic.segment.bytes来控制。Segment是日志清理的基本单元,当前正在使用的Segment是不会被清理的,对于每一个Partition的日志,以Segment为单位,都会被分为两部分,已清理和未清理的部分。同时,未清理的那部分又分为可以清理和不可清理。日志压缩是Kafka的一种机制,可以提供较为细粒度的记录保留,而不是基于粗粒度的基于时间的保留。

Kafka中的每一条数据都包含Key和Value,数据存储在磁盘上,一般不会永久保留,而是在数据达到一定的量或者时间后,对最早写入的数据进行删除。日志压缩在默认的删除规则之外提供了另一种删除过时数据(或者说是保留有价值的数据)的方式,就是对于具有相同的Key,而数据不同,值保留最后一条数据,前面的数据在合适的情况下删除。

常见的压缩算法是数据量越大压缩效果越好, 条消息通常不会太大,这就导致压缩效果并不是太好。 而Kafka实现的压缩方式是将多条消息 一 起进行压缩,这样可以保证较好的压缩效果。 在 一般情况下,生产者发送 的压缩数据在broker中也是保待压缩状态进行存储的,消费者从服务端获取的也是压缩的消息,消费者在处理消息之前才会解压消息,这样保待了端到端的压缩,
 

2.日志压缩方式的实现原理简介

当Topic中的cleanup.policy(默认为delete)设置为compact时,Kafka的后台线程会定时将Topic遍历两次,第一次将每个Key的哈希值最后一次出现的offset记录下来,第二次检查每个offset对应的Key是否在较为后面的日志中出现过,如果出现了就删除对应的日志。日志压缩是允许删除的,这个删除标记将导致删除任何先前带有该Key的消息,但是删除标记的特殊之处在于,它们将在一段时间后从日志中清理,以释放空间。这些需要注意的是,日志压缩是针对Key的,所以在使用时应注意每个消息的Key值不为NULL。压缩是在Kafka后台通过定时的重新打开Segment来完成的

 

五、消息删除

Kafka 将 消息存储在磁盘中,为了 控制磁盘占用空间的不断增加就需要对消息做 定的清 理操作。 Kafka 中 每一 个分区副本都对应 个 Log, 而Log又可以分为多个日志分段,这样也 便于日志的清理操作。 Kafka提供了两种日志清理策略。一种是日志删除一种是日志压缩,其中日志压缩上面已经讲过了,这里主要讲日志删除
 
在Kafka 的日志管理器中会有 一个专门的日志删除任务来周期性地检测和删除不符合 保留条 件的日志分段文件,这个周期可以通过broker端参数log.retention.check.interval.ms 来配置 ,默认值为300000, 即5分钟。 当前日志分段的保留策略有3 种:基于时间 的保留策略、 基于日志大小的保留策略 和基于日志起始偏移量的保留策略
 

1.基于时间

日志删除任务会检查当前日志文件中是否有保留时间超过设定的阙值(retentionMs)来寻 找可删除的日志分段文件集合(deletableSegments) 默认情况下只配置 了log.retention.hours参数, 其值为168, 故默认情况下日志分段文件的保留时间为7天
 
删除流程:
删除日志分段时,首先会从Log对象中所维护日志分段的跳跃表中移除待删除的日志分段, 以保证没有线程对这些日志分段进行读取操作。 然后将日志分段所对应的所有文件添加上 ".deleted"的后缀(当然也包括对应的索引文件)。 最后交由一 个以"delete-fi le"命名的延迟 任务来删 除这些以 "deleted "为 后 缀的文件,
 
存在问题:
若待删除的日志分段的总数等千该日志文件中所有的日志分段的数量, 那么说明所有的日志分段都已过期, 但该日志文件中还要有一 个日志分段用千接收消息的写入, 即必须要保证有 一个活跃的日志分段activeSegment, 在此种情况下, 会先切分出 个新的日志分段作为activeSegment, 然后执行删除操作

 

2.基于日志大小

日志删除任务会检查当前日志的大小是否超过设定的阀值 (retentionSize)来寻找可删除的 日志分段的文件集合(deletableSegments),
基于日志大小的保留策略与基于时间的保留策略类似, 首先计算日志文件的总大小 size 和 retentionSize的差值 diff, 即计算需要删除的日志总大小, 然后从日志文件中的第一 个日志分段 开始进行查找可删除的日志分段的文件集合deletableSegments。 查找出deletableSegments之后就执行删除操作, 这个删除操作和基千时间的保留策略的删除操作相同
 
 

3.基于日志起始偏移量

基于日志起始偏移量的保留策略的判断依据是某日志分段的下 个日志分段的起始偏移量 baseOffset 是否小于等于logStartOffset, 若是, 则可以删除此日志分段
 
删除流程:
先按顺序遍历所有日志段,将偏移量小于等于 logStartOffset的日志段加入删除序列中,如果找到了大于logStartOffset的日志段就停止查找(后面都是大于的了),然后删除流程和基于时间相同
 

六、磁盘存储

Kafka依赖于文件系统(更底层地来说就是磁盘)来存储和缓存消息,对于各个存储介质的速度如下图所示,可见磁盘的存储速度是很慢的,在传统的中间件RabbitMQ中, 就使用内存作为默认的存储介质,然而其实磁盘并不慢,取决于怎么用

1.页缓存

页缓存是操作系统实现的 种主要的磁盘缓存, 以此用来减少对磁盘I/0 的操作。 具体 来说, 就是把磁盘中的数据缓存到内存中, 把对磁盘的访间变为对内存的访问。 为了弥补性 能上的差异, 现代操作系统越来越 “ 激进地 将内存作为磁盘缓存, 甚至会非常乐意将所有 可用的内存用作磁盘缓存, 这样当内存回收时也几乎没有性能损失, 所有对于磁盘的读写也将经由统一 的缓存
 
使用流程
个进程准备读取磁盘上的文件内容时, 操作系统会先查看待读取的数据所在的页 (page)是否在页缓存(pagecache)中,如果存在(命中)则直接返回数据, 从而避免了对物理磁盘的I/0操作;
如果没有命中, 则操作系统会向磁盘发起读取请求并将读取的数据页存入页缓存, 之后再将数据返回给进程。
同样,如果一 个进程需要将数据写入磁盘, 那么操作系统也会检测数据对应的页是否在页缓存中,如果不存在, 则会先在页缓存中添加相应的页, 最后 将数据写入对应的页。 被修改过后的页也就变成了脏页,操作系统会在合适的时间把脏页中的 数据写入磁盘, 以保持数据的一 致性。
其实就和我们平时用redis的方式一样,缓存有直接用,缓存没有取来存起来
Kafka 中大量使用了页缓存, 这是Kafka实现高吞吐的重要因素之 一。

2.顺序存储

Kafka 写入数据时采用了文件追加的方式写入消息,在日志文件的尾部追加新消息,属于典型的顺序写盘的操作,它依赖于硬盘来存储和缓存消息。通常来说,内存的读写效率要高于磁盘,但磁盘的顺序读写效率也是非常高的,顺序写磁盘的速度甚至高于随机写内存的速率。所以 Kafka 使用了顺序写磁盘的方式做持久化工作。

3.零拷贝 (Zero-Copy)

页缓存技术主要用于消息写入 Kafka Broker 端的磁盘,零拷贝技术用于 Kafka Broker 将消息推送给下游消费者, 是指将数据直接从磁盘文件复制到网卡设备中,而不需要经由应用程序之手 。零 拷贝大大提高了应用程序的性能,减少了内核和用户模式之间的上下文切换
传统IO流程
比如:读取文件,再用socket发送出去
传统方式实现:
先读取、再发送,实际经过1~4四次copy。
buffer = File.read 
Socket.send(buffer)

1、第一次:将磁盘文件,读取到操作系统内核缓冲区;
2、第二次:将内核缓冲区的数据,copy到application应用程序的buffer;
3、第三步:将application应用程序buffer中的数据,copy到socket网络发送缓冲区(属于操作系统内核的缓冲区);
4、第四次:将socket buffer的数据,copy到网卡,由网卡进行网络传输。

kafka操作流程 

是指读取磁盘文件后,不需要做其他处理,直接用网络发送出去。试想,如果读取磁盘的数据需要用程序进一步处理的话,必须要经过第二次和第三次数据copy,让应用程序在内存缓冲区处理。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值