(三)kafka log
Kafka中的消息以主题为基本单位进行归类。一个主题对应多个分区。在不考虑多副本时,一个分区对应一个日志Log,每个Log又切分为许多个LogSegMent,这样方便消息的维护和清理。Log在物理上以文件夹的形式存储,而LogSegment对应于磁盘上的一个日志文件和两个索引文件,以及其它可能的文件。 当有消息被发送时,会根据分区规则被追加到指定的分区中,进而追加到某个活跃的log Segment的末尾,并分配一个该分区内唯一的offset。
kafka Log日志
偏移量索引:
偏移量索引项的格式:
relativeOffset 4B |
position 4B |
---|
查找偏移量为23的消息,根据跳跃表找到BaseOffset=0的日志分段,计算相对偏移量为23 - 0 =23.然后再通过二分法在偏移量索引文件中找到不大于23的索引项【22 656】,随后在日志分段文件中根据物理位置656开始顺序查找偏移量为23的消息。
偏移量索引示意图
如果要查找偏移量为268的消息,根据跳跃表找到BaseOffset=251的日志分段,然后计算出相对偏移量268-251 = 17;在对应的索引文件中找到不大于17的索引项,根据索引项定位到具体的日志分段文件的物理位置后再顺序查找目标消息。
偏移量跳跃表
时间戳索引:
时间戳索引项的格式:
timestamp 8B |
relativeOffset 4B |
---|
要查找指定时间戳targetTimeStamp=1526384718288开始的消息,首先是根据时间戳索引文件找到不大于指定时间戳的最大偏移量项【15263847128283,28】,然后根据相对偏移量28在偏移量索引文件中使用二分法找到不大于28的最大索引项【26,838】,然后在日志分段文件中838的物理位置开始查找不小于targetTimeStamp的消息。
时间戳索引示意图
日志删除:
Kafka的消息存储在磁盘中,为了控制磁盘占用空间的不断增加,需要清理Kafka消息。由于Kafka的消息是分段存储的,目前提供了两种日志清理策略。
一种是日志删除:基于时间删除、基于日志大小删除、基于日志起始偏移量删除
另一种是日志压缩:针对每个消息的key进行整合,只保留相同key的最后一个版本的值。
磁盘存储与I/O流程:
Kafka消息只能在文件的尾部追加消息,并且允许修改已经写入的消息,这种方式属于典型的顺序写盘的方式。
操作系统本身有一层缓存,叫做页缓存 (Page Cache),又被称为 OS Cache,即为操作系统自己管理的缓存。页缓存可以将磁盘中的数据缓存到内存中,将对磁盘的访问转换为对内存的访问。
Java程序进程中的内存是与JVM内存模型相关的,进程中的内存主要是Heap所占用的空间。
页缓存和 JVM 内存的主要区别在于它们的用途和管理的范围。页缓存是由操作系统管理的,用于缓存磁盘数据块,而 JVM 内存是由 Java 虚拟机管理的,用于存储和管理 Java 对象的内存空间。
从编程角度而言,一般磁盘I/O的场景有以下四种。
(1)用户调用标准C库进行I/O操作,数据流为:应用程序buffer→C库标准IObuffer→文件系统页缓存→通过具体文件系统到磁盘。
(2)用户调用文件 I/O,数据流为:应用程序 buffer→文件系统页缓存→通过具体文件系统到磁盘。
(3)用户打开文件时使用O_DIRECT,绕过页缓存直接读写磁盘。
(4)用户使用类似dd工具,并使用direct参数,绕过系统cache与文件系统直接写磁盘。发起I/O请求的步骤可以表述为如下的内容(以最长链路为例)。
· 写操作:用户调用fwrite把数据写入C库标准IObuffer后就返回,即写操作通常是异步操作;数据写入C库标准IObuffer后,不会立即刷新到磁盘,会将多次小数据量相邻写操作先缓存起来合并,最终调用write函数一次性写入(或者将大块数据分解多次write 调用)页缓存;数据到达页缓存后也不会立即刷新到磁盘,内核有pdflush 线程在不停地检测脏页,判断是否要写回到磁盘,如果是则发起磁盘I/O请求。
· 读操作:用户调用fread到C库标准IObuffer中读取数据,如果成功则返回,否则继续;到页缓存中读取数据,如果成功则返回,否则继续;发起 I/O 请求,读取数据后缓存buffer和C库标准IObuffer并返回。可以看出,读操作是同步请求。
· I/O请求处理:通用块层根据I/O请求构造一个或多个bio结构并提交给调度层;调度器将 bio 结构进行排序和合并组织成队列且确保读写操作尽可能理想:将一个或多个进程的读操作合并到一起读,将一个或多个进程的写操作合并到一起写,尽可能变随机为顺序(因为随机读写比顺序读写要慢),读必须优先满足,而写也不能等太久。
磁盘I/O流程
页缓存技术主要用于消息写入 Kafka Broker 端的磁盘,零拷贝技术用于 Kafka Broker 将消息推送给下游消费者。
用户进程缓冲区与内核缓冲区
用户进程访问系统资源(磁盘,网卡,键盘等)时,需要切换到内核态(Kernel Mode),访问结束后,又需要从内核态切换为用户态(User Mode),这种切换十分耗时,所以用户进程会在用户进程空间中开辟一块缓冲区域,叫做用户进程缓冲区
,用户进程如果是读系统资源,则会将读到的系统资源写入用户进程缓冲区,后续读就读用户进程缓冲区的内容,用户进程如果是写数据到系统资源,则会将写的数据先写入用户进程缓冲区,然后再将用户进程缓冲区的内容写到系统资源。所以用户进程缓存区会减少用户进程在用户态和内核态之间的切换次数,从而降低切换的时间。
用户进程访问系统资源实际上需要借助操作系统内核完成,所以与系统资源发生I/O的实际是操作系统内核,操作系统内核为了减少与系统资源实际的I/O的次数,也有一个缓冲区叫做内核缓冲区
,如果是对系统资源的读,则先将系统资源数据读取并写入内核缓冲区中,然后再将内核缓冲区的内容写入用户进程缓冲区,如果是对系统资源的写,则先将用户进程缓冲区的内容写入内核缓冲区, 然后再将内核缓冲区的内容写到系统资源。这样可以有效降低操作系统内核与系统资源的实际I/O次数,降低I/O带来的时间消耗。(https://segmentfault.com/a/1190000041829812)
所谓的零拷贝是指将数据直接从磁盘文件复制到网卡设备中,而不需要经由应用程序之手。零拷贝大大提高了应用程序的性能,减少了内核和用户模式之间的上下文切换。
传统从磁盘读取数据和发送给消费者:
第一步:操作系统从磁盘文件读取内容到内核空间的页面缓存
第二步:应用程序将数据从内核空间读入用户空间缓存区
第三步:应用程序将数据写回内核空间并放到socket缓冲区
第四步:操作系统将数据从socket缓冲区复制到网卡接口,并通过网络发送。
如果有10个订阅该主题的订阅者,那么发送一组消息共需要40次的复制操作。
为了简化该操作,kafka采用了零拷贝技术,应用程序可以采用零拷贝技术(DMA(Direct Memory Access)将文件内容复制到内核模式下的Read Buffer 中。不过没有数据被复制到 Socket Buffer,相反只有包含数据的位置和长度的信息的文件描述符被加到Socket Buffer中。如下图是零拷贝的mmap/write实现方式方式
同样将一批消息发给十个订阅者,只需要读一次磁盘并放入内核读取缓冲区,再加上10次的丰富文件描述符信息,共11操作。
更多关于零拷贝技术的实现方式, 可以参考:一文彻底弄懂零拷贝原理
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)