当前位置: 首页 > news >正文

8gu-kafka

目录
  • kafka 系统设计
    • Broker怎么实现?
      • 方式一:文件系统持久化 (Kafka 的实际实现)
        • 核心理念:顺序 I/O 与零拷贝
        • 具体实现细节
      • 核心思想:日志(Log)即存储
      • 底层实现详解
        • 1. 日志分段(Log Segmentation)
        • 2. 写入过程:PageCache 与顺序追加
        • 3. 索引(Index)机制:快速查找
        • 4. 零拷贝(Zero-Copy)技术:优化读取
      • 总结:Kafka 持久化架构的精妙之处
      • 方式二:数据库持久化 (一种假设的实现)
        • 核心理念:事务与灵活查询
        • 表设计
        • 具体实现细节
      • 对比与结论:为什么 Kafka 选择了文件系统

kafka 系统设计

Broker怎么实现?

这是一个非常深入且关键的架构设计问题。一个消息中间件(Broker)的性能、可靠性和功能特性,在很大程度上取决于其持久化策略的选择。

简而言之,Kafka 坚定地选择了文件系统,并且这种设计是其实现惊人吞吐量的核心原因之一。下面,我们将详细介绍这两种方式的具体实现,并解释为什么文件系统是 Kafka 的不二之_选择_。


方式一:文件系统持久化 (Kafka 的实际实现)

Kafka 并没有使用任何现成的数据库,而是直接在操作系统的文件系统之上,构建了一个高度优化的、基于日志分段 (Log Segment) 的存储结构。

核心理念:顺序 I/O 与零拷贝

  1. 顺序 I/O: 传统观念认为硬盘读写慢,但这主要是指随机读写。硬盘的顺序读写速度非常快,甚至可以逼近内存的随机读写速度。Kafka 的设计哲学就是将所有数据以追加 (append-only) 的方式写入日志文件,这完全是顺序写操作,因此性能极高。
  2. 零拷贝 (Zero-Copy): 在消费数据时,Kafka 利用操作系统的 sendfile 系统调用,可以直接将数据从内核空间的页缓存 (Page Cache) 发送到网络套接字,避免了数据在内核空间和用户空间之间的两次冗余拷贝,极大地提升了数据消费的效率。

具体实现细节

  1. 目录结构:

    • 每个 Topic 的每个 Partition 在物理上都对应一个独立的文件夹。
    • 文件夹的命名规则通常是 [topic_name]-[partition_id]
    • 例如,一个名为 orders 的 Topic 有 3 个分区,那么在 Broker 的数据目录下就会有 orders-0, orders-1, orders-2 三个文件夹。
  2. 日志分段 (Log Segment):

    • 每个分区文件夹内,数据并不是存在一个无限大的文件中,而是被切分成多个日志段 (Log Segment)
    • 每个日志段由两部分组成:
      • *.log: 实际存储消息数据的文件。
      • *.index: 偏移量索引文件,用于快速定位消息。
      • *.timeindex (可选): 时间戳索引文件,用于按时间戳查找消息。
    • 段文件的命名是该段中第一条消息的 Offset。例如,00000000000000000000.log 表示这个段从 Offset 为 0 的消息开始。如果这个段写满了(比如达到1GB),新的段文件可能是 00000000000000537621.log
  3. 写入过程:

    • 生产者发送的消息总是被追加到当前活动的 (active) 日志段的末尾 (.log 文件)。
    • 当消息写入时,其对应的元信息(相对 Offset 和物理位置)也会被写入到索引文件 (.index) 中。索引是稀疏索引,不是每条消息都记录,而是每隔一定字节数记录一条,以节省空间并保证加载速度。
  4. 读取过程:

    • 消费者请求一个特定的 Offset。
    • Broker 首先通过二分查找确定这个 Offset 属于哪个日志段。
    • 然后,在对应的 *.index 文件中再次使用二分查找,快速定位到离目标 Offset 最近的索引项,获取其物理地址。
    • 最后,从 *.log 文件的该物理地址开始顺序扫描,直到找到目标 Offset 的消息。

结构示意图:

graph TDsubgraph Broker's Disksubgraph Partition_0_Directory ["/data/kafka/orders-0"]direction LRLS1["Log Segment 1<br>(000000.log<br>000000.index)"]LS2["Log Segment 2<br>(537621.log<br>537621.index)"]LS3["<b>Active Log Segment 3</b><br>(988345.log<br>988345.index)"]endendProducer -- "Append Only" --> LS3Consumer -- "Read from any Segment" --> LS1Consumer -- " " --> LS2

好的,这是一个非常深入的问题。我们来详细剖析 Kafka Broker 是如何实现消息持久化的,并深入到其底层实现。

Kafka 高性能持久化的核心在于其日志结构的设计哲学,它巧妙地利用了顺序写入这一磁盘最快速的操作,并围绕此构建了一整套优化体系。

核心思想:日志(Log)即存储

在 Kafka 中,一个 Topic 被分成多个 Partition。每个 Partition 在物理上对应一个日志(Log) 目录。这才是消息真正被存储的地方。

topic-order-events-0/   <- Partition 0 的目录
topic-order-events-1/   <- Partition 1 的目录
topic-order-events-2/   <- Partition 2 的目录

底层实现详解

1. 日志分段(Log Segmentation)

单个 Partition 的日志不会被写成一个无限增大的巨型文件,而是被切分成多个段(Segment)。这带来了诸多好处:

  • 便于过期数据删除:只需删除旧的 Segment 文件即可。
  • 加速日志清理和压缩:可以针对单个 Segment 进行操作。
  • 提高查找效率:基于偏移量的二分查找可以在索引的帮助下快速定位。

每个 Segment 由两个核心文件组成,它们共享相同的基准偏移量(Base Offset,即该 Segment 第一条消息的偏移量):

  • .log 文件:数据文件,实际存储消息的地方。
  • .index 文件:偏移量索引文件,用于将消息偏移量映射到其在 .log 文件中的物理位置。
  • .timeindex 文件:时间戳索引文件(可选但默认开启),用于按时间戳查找消息。

例如:

00000000000000000000.log  # 第一个Segment,存储偏移量0到12344的消息
00000000000000000000.index
00000000000000000000.timeindex
00000000000000012345.log  # 下一个Segment,基准偏移量是12345,存储偏移量12345及之后的消息
00000000000000012345.index
00000000000000012345.timeindex

2. 写入过程:PageCache 与顺序追加

当 Producer 发送消息到 Broker 时,Broker 的持久化过程如下:

  1. 确认目标:根据消息的 Topic 和 Partition,找到对应的 Log 对象和当前活跃的 Segment(activeSegment)。
  2. 追加写入(Append-Only):Broker 不会随机修改已有的数据文件,而是将所有新消息顺序地追加(Sequential Append) 到当前活跃的 .log 文件末尾。
    • 关键优势:即使是机械硬盘(HDD),顺序写入的速度也远高于随机写入。这是 Kafka 高吞吐量的根本原因之一。
  3. 利用操作系统 PageCache:消息并不是直接“刷”到磁盘上。它们首先被写入到操作系统的页缓存(PageCache) 中。
    • 好处
      • 速度极快:写入内存的速度远高于写入磁盘。
      • 批量刷盘:操作系统会在后台智能地将多个脏页(Dirty Page)合并后一次性写入磁盘(Flush),这大大减少了磁盘 I/O 次数,提升了效率。
      • 读写结合:后续的消费者读取消息时,如果消息还在 PageCache 中,可以直接从内存中读取,速度极快。Kafka 通过利用 PageCache,同时优化了写和读的性能。
  4. 同步刷盘(Flush)策略:Kafka 并不完全依赖操作系统的刷盘机制。它提供了可配置的持久化保证:
    • log.flush.interval.messages:当积累了多少条消息后,触发一次刷盘。
    • log.flush.interval.ms:当消息在内存中停留了多长时间后,触发一次刷盘。
    • 注意:在追求极致吞吐量的场景下,通常会放宽刷盘策略,依赖副本机制(Replication)来保证数据不丢失,而不是同步刷盘。因为刷盘是一个昂贵的操作。Kafka 的默认配置是让操作系统来决定刷盘时机,以获取最佳性能。

3. 索引(Index)机制:快速查找

如果每次消费者读取消息都需要从头扫描 .log 文件,那将是灾难性的。Kafka 使用稀疏索引(Sparse Index) 来解决这个问题。

  • .index 文件的结构:它不是为每条消息都建立索引,而是每隔一定数量的字节(由 log.index.interval.bytes 配置)才建立一条索引记录。
    • 每条索引记录包含两个字段(通常各占4字节):(relativeOffset, position)
      • relativeOffset相对偏移量(相对于当前 Segment 的基准偏移量),用于节省空间。例如,基准偏移量为100,消息偏移量105的相对偏移量就是5。
      • position:该条消息在对应的 .log 文件中的字节偏移量
  • 查找过程(例如,要读取 offset=152 的消息,假设当前 Segment 基准偏移量为100):
    1. 计算相对偏移量152 - 100 = 52
    2. 二分查找.index文件:在索引文件中找到小于等于52的最大索引项。例如,找到 (50, 4592),这表示相对偏移量50的消息位于 .log 文件的第4592字节处。
    3. 顺序扫描.log文件:从 .log 文件的4592字节处开始顺序扫描,直到找到相对偏移量为52(即绝对偏移量152)的消息。
  • 为什么用稀疏索引?
    • 用极小的空间开销(一个索引文件通常很小),将全局顺序扫描变成了 “小范围顺序扫描” ,查找效率极高。虽然最坏情况下需要扫描一个索引间隔的数据(例如 4KB),但这在内存中是非常快的。

4. 零拷贝(Zero-Copy)技术:优化读取

当消费者需要读取消息时,Broker 需要将 .log 文件中的数据发送到网络。传统的做法是:

  1. 操作系统从磁盘读取数据到内核空间的 PageCache。
  2. 应用程序(Kafka)将数据从内核空间拷贝到用户空间的缓冲区。
  3. 应用程序将数据从用户空间缓冲区拷贝到内核空间的 Socket 缓冲区。
  4. 操作系统将 Socket 缓冲区的数据发送到网卡。

这个过程涉及 4 次上下文切换2 次不必要的数据拷贝(步骤 2 和 3)。

Kafka 使用了 sendfile 系统调用(Linux 支持)来实现零拷贝

  1. 操作系统从磁盘读取数据到内核空间的 PageCache。
  2. sendfile 系统调用直接指示内核将数据从 PageCache 直接拷贝到网卡缓冲区
  3. 操作系统将网卡缓冲区的数据发送出去。

这个过程减少到 2 次上下文切换1 次数据拷贝,避免了 CPU 和内存的额外开销,极大地提升了数据传输效率,特别适合大文件(或大量小消息)的网络传输。

总结:Kafka 持久化架构的精妙之处

技术/机制 解决的问题 带来的好处
日志分段(Segmentation) 单个文件过大,难以维护和清理 易于管理、删除、压缩和查找
顺序追加写入 磁盘随机写入速度慢 极高的写入吞吐量,充分利用磁盘顺序I/O性能
PageCache 每次写入都刷盘速度太慢 批量I/O,将磁盘写操作转为内存写,由OS异步刷盘
稀疏索引(.index) 如何快速定位消息 快速查找,将全局扫描变为小范围顺序扫描,空间开销小
零拷贝(sendfile) 网络发送数据时CPU和内存拷贝开销大 极高的读取吞吐量,减少CPU开销和上下文切换

因此,Kafka 持久化的底层实现是一个系统工程,它通过“日志结构 + 顺序追加 + PageCache + 稀疏索引 + 零拷贝”这一系列组合拳,完美地将“持久化”(通常意味着低性能)这个缺点转变为了其“高吞吐、低延迟”的核心优势。 它更多地是依靠架构设计来最大化硬件(尤其是磁盘)的性能,而不是依赖某种特殊的黑科技。


方式二:数据库持久化 (一种假设的实现)

如果让我们用关系型数据库(如 MySQL)来设计一个 Broker 的持久化层,会是什么样子?

核心理念:事务与灵活查询

数据库的核心优势在于 ACID 事务保证和强大的 SQL 查询能力。我们可以为每一条消息创建一个记录。

表设计

我们可以设计一张 messages 表来存储所有消息:

CREATE TABLE messages (-- 主键与标识id BIGINT AUTO_INCREMENT PRIMARY KEY,          -- 唯一自增ID,作为物理主键message_uuid VARCHAR(36) NOT NULL UNIQUE,       -- 消息的全局唯一ID,防止生产者重试导致重复-- 路由与分区信息 (核心索引)topic_name VARCHAR(255) NOT NULL,             -- 主题名称partition_id INT NOT NULL,                    -- 分区IDmessage_offset BIGINT NOT NULL,                 -- 在该分区内的偏移量-- 消息内容message_key VARBINARY(1024),                    -- 消息的Key(可用于分区或业务查询)payload BLOB NOT NULL,                          -- 消息的实际内容 (二进制大对象)headers TEXT,                                   -- 消息头 (可存储为 JSON 格式)-- 元数据producer_id VARCHAR(255),                       -- 生产者IDcreated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), -- 消息创建时间戳-- 建立联合唯一索引以保证 Offset 的唯一性,并加速查询UNIQUE KEY idx_partition_offset (topic_name, partition_id, message_offset)
);-- 为了加速按 Key 或时间戳的查找,可以额外添加索引
CREATE INDEX idx_key ON messages (message_key);
CREATE INDEX idx_timestamp ON messages (created_at);

具体实现细节

  1. 写入过程:

    • 生产者发送一条消息。
    • Broker 收到后,将其构造成一条记录,执行一个 INSERT 语句:
      INSERT INTO messages (topic_name, partition_id, message_offset, ...) 
      VALUES ('orders', 0, 12345, ...);
      
    • 为了保证 message_offset 的连续性,Broker 需要在事务中先获取当前分区的最大 Offset,加一后再插入。这会引入锁竞争。
  2. 读取过程:

    • 消费者请求从某个 Topic 的某个分区的特定 Offset 开始消费。
    • Broker 执行一个 SELECT 查询:
      SELECT * FROM messages 
      WHERE topic_name = 'orders' AND partition_id = 0 AND message_offset >= 12345 
      ORDER BY message_offset ASC 
      LIMIT 100; -- 一次拉取100条
      

对比与结论:为什么 Kafka 选择了文件系统

特性 文件系统 (Kafka) 数据库
写入性能 极高。纯顺序追加写入,充分利用磁盘带宽。 较低。B-Tree 索引的插入是随机 I/O,涉及索引页分裂和重新平衡,开销巨大。高并发下有严重的锁竞争。
读取性能 极高。流式读取是顺序读,利用 OS 页缓存和零拷贝技术。 中等。依赖索引,但仍涉及多次 I/O 和数据库引擎的复杂处理。不适合大规模流式读取。
存储开销 。数据紧凑,索引稀疏,开销小。 。行存储、索引、事务日志等都会带来巨大的额外空间开销。
查询能力 。只能按 Offset 和时间戳粗略查找。 。强大的 SQL,可以按任意字段组合查询。
设计复杂度 。需要自己处理文件管理、索引、崩溃恢复等所有细节。 。可以利用数据库成熟的事务、索引和管理功能。
适用场景 高吞吐量流数据处理、日志收集、事件溯源。 需要复杂查询、事务保证的业务系统(OLTP),不适合做消息队列底层。

结论:

Kafka 的设计目标是成为一个高吞吞吐量、可扩展的分布式流处理平台。在这个目标下,写入和流式读取的性能是压倒一切的。数据库提供的强事务和灵活查询能力,对于 Kafka 的核心场景来说不仅不是必需品,反而会成为巨大的性能瓶颈。

因此,Kafka 选择了直接操作文件系统,通过将随机写转换为顺序写这一天才般的设计,并结合操作系统的底层优化(如页缓存、零拷贝),最大限度地榨干了硬件的性能,从而实现了单机每秒处理数十万甚至上百万条消息的能力。

http://www.sczhlp.com/news/80301/

相关文章:

  • MATLAB的加权K-means(Warp-KMeans)聚类算法
  • 网站建设与运营未来发展搭建影视网站违法
  • 免费网站后台效果图外包
  • 温州建设网站哪家好有哪些搜索引擎
  • 免费行情软件网站下载大全wordpress显示文章点击量
  • 合肥网络公司 网站建设kotlin做网站
  • 电商网站零售客户软件开发要多少钱
  • 个人网站备案 备注威联通怎么建设网站
  • 备战软考3
  • 邦泽网站建设知乎关键词搜索排名
  • 个人网站设计怎么做做得不好的知名企业网站
  • 杭州网站设计询问蓝韵网络爱空间家装怎么样?两点告诉你
  • 成都双流 网站建设2016最新wordpress模板
  • 企业网站模板源码免费玉溪哪有网站建设服务公司
  • 乐高设计师网seo关键词排名优化报价
  • 展示型企业网站营销目标主要有wordpress怎么找到php文件
  • 杭州网站设计网站深圳网站建设运营公司
  • 旋转公式
  • finalshell远程连接虚拟机Ubuntu
  • LSD直线提取算法 MATLAB
  • 目录的结构
  • 有关网站开发的文献或论文成都住建局官网报名入口
  • 内蒙古工程建设网站网站平台设计
  • 做外贸可以在哪些网站注册哪里可以鉴定钻石
  • 如何免费建个人网站企业工商查询官网
  • 阎良区建设局网站wordpress插件 osgi
  • 新兴县建设局网站广州的网站建设公司
  • 创建 Windows 11 安装媒体失败导致U盘报错Windows无法格式化解决方案
  • vue实现antd-vue table表格 右上角设置列功能(支持列搜索列、上移、下移、置顶、置底)
  • 企业网站建设相关书籍2020新闻大事件摘抄