3.Kafka-数据存储流程

数据存储流程

kafka的数据存储流程总共是分为四个阶段。生产者发送与Broker的接收、日志追加与持久化、数据持久化到物理磁盘和索引与日志段滚动的四个阶段。这里暂时只做个简单的说明。后续的文章才会详细说明每个阶段里的细节。

第一阶段:生产者发送与Broker接收

这里分为生产者侧和Broker侧去聊。

生产者侧

生产者侧需要做的是四个事情。消息封装、分区选择、批次累积和发送请求。

消息封装,生产者会将消息(键、值、Headers等)封装成一个ProducerRecord,指定目标Topic。

分区选择,主要是四种情况。

  • 如果指定Partition ID,则PR被发送至指定Partition (ProducerRecord)
  • 如果未指定Partition ID,但指定了Key, PR会按照hash(key)发送至对应Partition
  • 如果未指定Partition ID也没指定Key,PR会按照默认 round-robin轮训模式发送到每个Partitio
  • 如果同时指定了Partition ID和Key, PR只会发送到指定的Partition (Key不起作用,代码逻辑决定)

批次累积,消息不会立即发送。Sender线程会将发向同一个分区的消息在内存中累积成一个批次。这也是提升吞吐量的关键所在。

发送请求,当批次达到了batch.size或者等待时间超过了linger.ms的时候,Sender线程会将其封装成一个ProduceRequest,发送给Broker的Leader。

Broker侧

Broker侧主要做两件事。接收ProduceRequest和请求解析与验证。

接收ProduceRequest,Kafka网络层会基于NIO接收ProduceRequest。

请求解析与验证,Broker验证请求的合法性(主题是否存在、是否有写入权限、请求格式等)。

第二阶段:日志追加与持久化(核心)

这个内容是Kafka存储等核心部分,主要是三个方面。Leader写入本地日志、等待副本同步(ISR机制)和响应生产者。

Leader写入本地日志

这里主要是三个工作。分配偏移量、构建内存结构和追加到页缓存。

分配偏移量,Kafka会为批次内到每条消息都分配一个单调递增到偏移量。偏移量是分区级别的

构建内存结构。Broker不会将网络接收到的原始字节直接写盘。它会将消息(带有新分配的偏移量)构建成更加高效的内存信息格式。

追加到页缓存,这是最关键的一步。

Broker调用文件系统的write()系统调用,将消息数据顺序追加到该分区对应的活跃日志段(xxx.log)的末尾。

PS:write()调用,在大部分时候,都不会触发实际的物理磁盘写入。数据首先会被写入到操作系统的页缓存。这个页缓存是Linux内核管理的一块内存区域,用于缓存磁盘数据。由于是顺序写入,而且利用了操作系统的缓存机制,这个操作速度极快,性能也是逼近内存写入。

等待副本同步(ISR机制)

为了保证数据的持久性和高可用,Kafka不会再Leader本地写入成功之后立即回复生产者。

Follower拉取:所有与这个Leader保持同步的副本(ISR)的Follwer,会不断的向Leader发送FETCH请求(和消费者拉取消息的协议是相同的),来拉取新的消息。

Follower写入:每个Follower收到Leader的数据后,会执行与Leader完全相同的流程:分配相同的偏移量,并将消息顺序追加到自己日志文件的页缓存中。

Follower确认:Follower写入本地页缓存后,会向Leader返回一个确认(ACK)

Leader判断"已提交":Leader会追踪每个消息被ISR中副本写入的状态。根据生产者设置的acks配置:

  • acks = 0: Leader自己写入页缓存立即返回成功。最快,但可能丢失数据。
  • acks = 1: Leader自己写入页缓存后即返回成功。这是默认的配置,在性能和可靠性之间取得一个平衡。
  • acks = all / acks = -1 : Leader需要等待ISR中的所有副本(包括自己)都写入到页缓存中,才认为消息是"已提交"的,然后返回成功给生产者,最可靠,但是延迟最高。

响应生产者

一旦满足了acks配置要求的条件,Leader就会向生产者发送ProduceResponse,告知消息发送成功。(包含分配的偏移量等信息)至此,从生产者的角度来看,消息存储的流程已经完成。

第三阶段:数据持久化到物理磁盘(异步后台操作)

这里需要提的一个观点:Kafka的数据可靠性并不依赖于"同步刷盘",而是依赖于"多副本" + "异步刷盘"。主要也是两种。操作系统刷盘和Kafka强制刷盘。

操作系统刷盘(fsync)

  • 写入页缓存的数据,由Linux内核的pdflush线程在后台异步的,或者是满足某种特定的条件(脏页比例、超时时间)时,将其刷新(fsync)到物理磁盘上。
  • Kafka可以配置log.flush.interval.messages和log.flush.interval.ms 来"建议"操作系统更加频繁的刷盘。但是通常来说没有必要,会降低性能。

Kafka强制刷盘

这个很少使用到,只有在极端要求数据不丢失的场景下,可以配置flush.ms或flush.messages,但这个严重牺牲了吞吐量,生产环境中通常以来acks = all和足够的副本书来保证可靠性,而不是通过同步刷盘。

第四阶段:索引与日志段滚动

这里主要也是两个方面。一个是更新索引文件,另一个是日志段滚动(Rolling)。

更新索引文件

在消息追加到.log文件之后,Kafka不会为每个消息都更新索引了,这个太耗时间了。

他会检查自上次索引更新以来写入的字节数。如果超过了log.index.interval.bytes(默认是4KB),则会向.index文件(偏移量)索引和.timeindex文件(时间戳索引)中追加一条新的稀疏索引条目。

索引条目记录了偏移量/时间戳到物理文件位置的映射,以便实现后续的高效查找。

日志段滚动(Rolling)

活跃段(Active Segment)是当前正在写入的.log文件。

当活跃段满足以下任意一个条件时,就会滚动成为一个只读的旧段,并创建一个新的空活跃段:

滚动的好处:

  • 将大文件拆分为小文件,便于管理。

  • 旧的、不再写入的日志段可以被高效地删除(基于时间或大小策略)或压缩(基于 Key 的保留最新值策略)。

  • 便于进行独立的索引维护和数据清理。

Kafka数据存储流程图

相关推荐
写bug的小屁孩1 小时前
4.Kafka-LEO+HW的定义与特性+工作流程
分布式·中间件·kafka
黑客思维者1 小时前
IEEE 1547.3-2023:分布式能源系统安全互联的技术基石与实践路径
分布式·系统安全·能源
小股虫1 小时前
Kafka副本管理深度剖析:从同步失败到自动恢复的完整生命线
分布式·kafka·linq
Zzzzzxl_1 小时前
互联网大厂Java/Agent面试实战:Spring Boot、JVM、微服务、Kafka与AI Agent场景问答
java·jvm·spring boot·redis·ai·kafka·microservices
黑客思维者1 小时前
IEEE 1547.3-2023在分布式能源(DER)系统应用中面临的挑战
分布式·能源·ieee1547.3
脸大是真的好~1 小时前
尚硅谷-消息队列-rabbitMQ
分布式·rabbitmq
不吃饭的猪1 小时前
kafka输出报错
分布式·kafka
IIIIIILLLLLLLLLLLLL12 小时前
Hadoop集群时间同步方法
大数据·hadoop·分布式
回家路上绕了弯17 小时前
大表优化实战指南:从千万到亿级数据的性能蜕变
分布式·后端