日志数据
kafka中的数据,是以log文件存储在磁盘上的,具体的说,一个主题有多个分区,每个分区对应一个Log日志,但是不是一个文件,每个log日志又拆分为多个logsegment文件,每次往一个分区追加日志时,只会追加到最新的那个logsegment文件上,前面的logsegment文件不会变动。
logsegment日志具体分为3个文件,一个log文件,和两个索引文件。他们以20位数字作为其文件名,这个数字即是这个文件中第一条消息在分区中的offset。
v1消息结构
消息集合
消息压缩
消息压缩是一个消息集合为压缩主体,并且压缩后将其作为一个消息结构中的value字段。压缩后的消息offset是从0开始的,外层的消息结构中的offset保存了内层消息中的最后一条消息的绝对位移。绝对位移就是相对于整个分区而言的。
生产者产生的数据发送给服务端,发送的是压缩后的数据。服务端在将数据发送给消费者时,发送的也是压缩的数据,消费者在处理消息之前才会解压消息。保持了端到端的压缩。
v2版本消息结构
生产者客户端中的ProducerBatch对应这里的RecordBatch。而ProducerRecord对应这里的Record。
索引日志
偏移量索引
偏移量索引日志文件建立了 消息偏移量 到 物理地址 之间的映射关系。
前面我们已经知道,索引日志文件的文件名为该日志分片第一条消息的绝对偏移量。而他的内部,存储的结构是稀疏索引,也就是他并不会将每个消息的索引信息都存下来。每当写入一定量的消息时,就会增加一个索引项。 稀疏索引通过MappedByteBuffer将索引文件映射到内存中,加快索引的查询速度。
索引中的偏移量是递增的,所以可以用二分查询的方式来快速定位到 小于 目标偏移量的 最大偏移量,然后从该位置开始,顺序找到目标偏移量的消息。
索引中的索引项,保存的是相对该文件的 相对偏移量和物理地址。相对偏移量 = 绝对偏移量-日志文件名偏移量
上图就表示了该文件中的每个索引项,稀疏索引的结构,所以二分查找到小于目标偏移量的最大偏移量后,还需要顺序查找到目标偏移量。
而在外面,我们肯定需要先找到消息在哪个日志段文件上,外面使用的是跳表的方式,根据偏移量来快速找到日志分段。
时间戳索引
- timestamp:当前日志分段的最大时间戳
- relativeOffset:时间戳对应的消息的相对位移量
他的写入和偏移量索引是一起的,这里时间戳也是递增的,所以根据某个时间戳查询,也是二分法进行查询,由于是稀疏的,所以也是找到小于大的最大的时间戳,然后在顺序去找。 注意这里找到的是偏移量,还需要根据偏移量去偏移量索引找到具体的消息。
这里由于文件名不是时间戳,所以也无法使用跳表的方。这里会逐一与每个日志分段的最大时间戳进行比较,直到找到到不小于目标时间戳的日志分段。
日志删除
日志管理器会有一个专门的日志删除任务来周期性的检测和删除不符合保留条件的日志分段文件。周期默认为5分钟。 保留策略有基于时间,日志文件大小以及日志起始偏移量三种策略
- 基于时间删除。默认是7天。超过7天的删除。
- 基于日志大小。有两个参数配置,一个是总日志文件大小,默认是-1,表示无穷大;一个是单个日志分段的大小,默认是1gb。超过阈值则用当前值减去阈值,然后从日志第一个日志分段开始进行查找可删除的日志分段文件集合。然后执行删除
- 基于日志起始偏移量。会根据logStartOffset 日志起始偏移量来计算。对于每一个日志分段文件,如果该日志分段文件的下一个分段文件的baseOffset小于起始偏移量,则会将该文件删除。
日志压缩(Log Compaction)在默认的日志删除规则之外提供的一种过时清理数据的方式。对于有相同key的不同value值,只保留最后一个版本。
磁盘存储
磁盘的顺序读写速度并不慢,真正慢的是随机的读写,顺序写入和随机写入之间的性能差距可以达到6000倍。而且顺序写入磁盘甚至比随机写入内存要快。
而kafa正是采用了文件追加的方式来写入消息,即只在日志文件的尾部追加新的消息,并且也不会允许修改已写入的消息,这种方式属于典型的顺序写盘的操作。所以即便kafa采用磁盘作为存储介质,他所能承载的吞吐量也不容小觑。
页缓存是磁盘缓存,在我们读取或者修改磁盘上内容时,会先找页缓存,页缓存没有则会吧磁盘里这一页数据读到内存中,然后在进行读写操作。写过的页就变成脏页,当超过一定比率时就会将该页写入到磁盘中。 以上都是操作系统负责维护的,维护页缓存和文件之间的一致性。
进程自己维护内存缓存的问题
- 进程内部的缓存,也可能缓存在页缓存中。
- java进程的对象内存开销大,gc会越来越慢
使用页缓存的一些好处(由于页缓存是操作系统维护的,所以对于进程来说,就相当于直接操作磁盘)
- 不用进程进行维护,极大简化代码逻辑
- 比进程内自己维护更安全有效
- 通过结构紧凑的字节码代替对象可以节省空间
- 进程服务重启后,数据依旧有效。
kafka接受到消息之后,会先写入到页缓存,然后由操作系统去执行具体的刷盘任务。
kafka还采用零拷贝的技术,直接将数据从磁盘拷贝到网卡上,减少了两次拷贝和上文切换。