欢迎光临
我们一直在努力

【Kafka】零基础学习教程

👋 关于作者

你好,这里是一条泥憨鱼 | 专注于 后端开发、AI 前沿技术与计算机基础 。 请添加图片描述 🏠 个人主页:一条泥憨鱼

通过技术博客记录技术成长,分享学习心得与经验。期待与您共同交流!

如果你第一次接触 Kafka,很容易被 Topic、Partition、Offset、Consumer Group、ISR 这一串名词劝退。其实它的核心很简单:

Kafka 是一个只能追加、可以重复读的分布式日志。生产者往里写,消费者按自己的进度读,消息按策略保留,而不是读完就删。

先建立这个印象,后面的概念都是它的展开。

全文较长,建议先收藏,再按下方「怎么读」里的路线选择阅读方式。


写在前面

先看三句话

  • Kafka 是一个「只能追加、可以重复读」的分布式日志,不是传统意义上「读完就删」的队列。
  • 生产者写进去,消费者按自己的进度读。每个消费组自己记一个 offset(读到哪了),互不干扰。
  • Kafka 快,是因为顺序写磁盘 + 批量 + 零拷贝;Kafka 难,主要难在「分区、副本、消费组」这几个概念和运维。
  • 版本与环境

    组件本文使用版本
    Apache Kafka 4.4.0(KRaft 模式,无需 ZooKeeper)
    Spring Boot 4.1.2
    JDK 17+
    Maven 3.9+
    运行环境 Docker Desktop;命令以 Windows + PowerShell 为例

    Linux / macOS 用户也能跟着做,只需注意几处命令差异:

    • curl.exe 换成 curl;
    • Test-NetConnection localhost -Port 9092 换成 nc -zv localhost 9092;
    • Get-NetTCPConnection -LocalPort 9092 换成 lsof -i :9092。

    版本提示:本文基于 Kafka 4.x 和 Spring Boot 4.x。如果你用的是 Spring Boot 3.x,依赖要改成 org.springframework.kafka:spring-kafka,JSON 序列化类用 JsonSerializer / JsonDeserializer。核心概念和 Kafka 命令行基本一致。

    怎么读

    阅读方式建议路线时间
    30 分钟速通 1.3~1.4 起服务 → 2.1~2.5 核心模型 → 3.1~3.4 收发消息 → 5.4 选型对比 30 分钟
    完整入门 按顺序读完,并动手跑一遍 4~6 小时
    当手册查 直接看第三、六章 随时

    全文结构

    部分你会学到
    一、5 分钟跑起来 用 Docker 起单机 Kafka、常用运维命令、advertised.listeners 怎么配
    二、核心概念 Topic / Partition / Offset、副本与 ISR、消费组、投递语义、顺序性
    三、命令行操作 建 topic、收发消息、查看 LAG、重置 offset、修改保留策略
    四、Spring Boot 实战 完整可运行的生产者 + 消费者 + REST 接口,以及手动提交、JSON、并发等进阶用法
    五、优缺点与选型 Kafka 的边界,和 RabbitMQ / RocketMQ / Pulsar 怎么选
    六、踩坑面试进阶 故障排查、16 道高频面试题、生产检查清单、四周学习路线

    一、5 分钟把 Kafka 跑起来

    目标:用 Docker 在本机起一个单机 Kafka,能建 topic、能收发消息。学完这一章,你就有了一个随时可以折腾的实验环境。

    1.1 为什么用 Docker,而不是 Windows 原生安装

    • Kafka 官方不推荐在 Windows 上直接跑生产环境,原生安装容易在路径、脚本、权限上踩坑。
    • Kafka 4.x 已经彻底移除 ZooKeeper,单机模式只需要一个进程(KRaft 模式),Docker 一条命令就能起。
    • 用 Docker 的好处:删了重来只要几秒,不污染系统;换电脑时复制一个 yml 文件即可。

    Windows 上建议启用 WSL2(wsl -l -v 能看到发行版),Docker Desktop 正好用 WSL2 作为后端,不用额外装虚拟机。macOS / Linux 直接用各自的 Docker 引擎。

    1.2 安装 Docker Desktop

    已经装好 Docker 的话,终端里 docker version 能同时输出 Client 和 Server 两段,就说明装好了,直接跳到 1.3。

    第一步:确认 WSL2 正常。

    wsl —status
    wsl —update

    如果提示需要重启,就重启一次。

    第二步:安装 Docker Desktop。

    winget install –e —id Docker.DockerDesktop

    安装完成后注销并重新登录 Windows(或者重启),让 docker 命令进入 PATH。

    想把 Docker 装到非系统盘,可以改用官方安装器参数:

    "Docker Desktop Installer.exe" install —quiet —accept-license —backend=wsl-2 `
    —installation-dir=D:\\Docker `
    —wsl-default-data–root=D:\\docker-data

    前一个参数控制程序目录,后一个控制镜像和 WSL 磁盘位置。装完后如果 docker compose 提示找不到命令,在 %USERPROFILE%\\.docker\\config.json 里加上:

    "cliPluginsExtraDirs": ["D:\\\\Docker\\\\resources\\\\cli-plugins"]

    第三步:启动并检查。

    从开始菜单打开 Docker Desktop:首次启动接受协议,可以跳过登录,左下角显示 Engine running 才算启动完成。

    新开一个 PowerShell 窗口验证:

    docker —version
    docker version

    docker version 能同时显示 Client 和 Server 两段信息,才算真的连上了 Docker 引擎。只有 Client 没有 Server,说明 Docker Desktop 还没启动。

    国内网络提示:拉取 apache/kafka 镜像可能很慢或超时。可以在 Docker Desktop → Settings → Docker Engine 里配置镜像加速或代理。2024 年以后大量公共加速站已关停,最稳的方式通常是公司或学校代理。镜像有几百 MB,配不上也别慌,多等一会儿通常能拉下来。

    1.3 启动 Kafka

    新建一个目录,放入下面这个 docker-compose.yml:

    # 最简单机 Kafka(KRaft 模式,无需 ZooKeeper)
    # 启动:docker compose up -d
    # 停止并删除:docker compose down
    services:
    kafka:
    image: apache/kafka:4.4.0
    container_name: kafka
    ports:
    – "9092:9092"
    restart: unless–stopped

    在该目录下执行:

    docker compose up –d

    第一次会下载 apache/kafka:4.4.0 镜像,耐心等。看到类似输出就成功了:

    [+] Running 2/2
    ✔ Network demo_default Created
    ✔ Container kafka Started

    检查容器状态:

    docker compose ps

    STATUS 应该是 Up …。再看日志确认 Kafka 真的启动了:

    docker compose logs kafka

    在输出里找这一行:

    Kafka Server started

    单机模式下,同一个进程既是 broker 又是 controller(KRaft 的 combined 模式),所以只有一个容器、没有 ZooKeeper。官方镜像自带一套「单机 KRaft 可用」的默认配置,因此只要映射 9092 端口就能用。学习阶段这样就够了。

    1.4 验证:第一条 Kafka 命令

    docker exec –it kafka /opt/kafka/bin/kafka-topics.sh —bootstrap-server localhost:9092 —list

    • 没有任何输出、也没有报错 = 成功。说明 Kafka 正常运行,当前还没有 topic。
    • 如果报 Connection to node -1 could not be established,回到 1.3 看容器是否 Up、日志里有没有 Kafka Server started。

    再确认一下版本:

    docker exec –it kafka /opt/kafka/bin/kafka-topics.sh —version

    应该显示 4.4.0。

    容器里的 Kafka 命令都在 /opt/kafka/bin/ 下,所以每条命令都要带这个前缀。嫌长可以在当前 PowerShell 窗口定义一个函数:

    function kfk-topics { docker exec –it kafka /opt/kafka/bin/kafka-topics.sh @args }

    之后就可以写 kfk-topics –bootstrap-server localhost:9092 –list。

    1.5 常用运维命令

    # 启动(后台)
    docker compose up –d

    # 看状态
    docker compose ps

    # 看日志(Ctrl+C 退出查看,不会停容器)
    docker compose logs –f kafka

    # 停止并删除容器(数据会丢,学习环境无所谓)
    docker compose down

    # 只停止,不删除
    docker compose stop

    # 重新启动
    docker compose start

    最简版没有挂数据卷,docker compose down 之后消息就没了。学习阶段这是好事,每次从干净状态开始。想要数据保留,用下面的 full 版。

    安全提示:不要随便在 Docker Hub 页面上点「Run」。这个按钮会直接拉取并运行镜像里的程序,镜像作者可以放任何东西(挖矿、后门、反弹 shell)。只运行官方镜像(apache/、library/、confluentinc/ 等)或你明确信任的镜像。

    1.6 进阶:显式配置 + 数据卷

    等你看完第二章,可以换成下面这份 docker-compose.full.yml。它把关键参数都写出来了,更适合理解:

    # 显式配置版:把关键参数写清楚,并挂载数据卷做持久化
    # 启动:docker compose -f docker-compose.full.yml up -d
    # 停止:docker compose -f docker-compose.full.yml down
    services:
    kafka:
    image: apache/kafka:4.4.0
    container_name: kafka
    hostname: kafka
    ports:
    – "9092:9092"
    environment:
    # —- 节点与角色 —-
    KAFKA_NODE_ID: 1
    KAFKA_PROCESS_ROLES: broker,controller
    CLUSTER_ID: "5L6g3nShT-eMCtK–X86sw"

    # —- 监听地址 —-
    # LISTENERS:进程实际监听的地址
    # ADVERTISED_LISTENERS:告诉客户端用哪个地址回来连
    KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
    KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
    KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
    KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT

    # —- KRaft 控制器投票节点 —-
    KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9093

    # —- 单机必须把内部 topic 的副本数改成 1 —-
    KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
    KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
    KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0

    # —- 数据目录(与下面的 volume 保持一致)—-
    KAFKA_LOG_DIRS: /var/lib/kafka/data

    # —- 开发环境方便起见,允许自动建 topic —-
    KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
    volumes:
    – kafka–data:/var/lib/kafka/data
    restart: unless–stopped
    healthcheck:
    test: ["CMD-SHELL", "/opt/kafka/bin/kafka-topics.sh –bootstrap-server localhost:9092 –list >/dev/null 2>&1"]
    interval: 10s
    timeout: 10s
    retries: 10
    start_period: 30s

    volumes:
    kafka-data:

    核心配置逐条解释:

    配置作用
    KAFKA_NODE_ID: 1 节点编号,集群内唯一
    KAFKA_PROCESS_ROLES: broker,controller 同时承担 broker 和 controller(单机合并模式)
    KAFKA_LISTENERS 进程监听哪些地址和端口
    KAFKA_ADVERTISED_LISTENERS 告诉客户端「你应该用这个地址来找我」
    KAFKA_CONTROLLER_QUORUM_VOTERS KRaft 控制器选举的投票节点列表
    KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 单机必须改成 1,否则内部 topic 副本数为 3 会建不出来
    KAFKA_LOG_DIRS 数据目录,配合 volume 做持久化

    ADVERTISED_LISTENERS 是 Kafka 最容易配错的地方,记住一句话:

    客户端连上 broker 后,broker 会返回 advertised.listeners 里的地址让客户端再去连。这个地址必须是客户端视角下能访问的地址。本机客户端就写 localhost:9092;如果客户端是另一个容器,就要写容器网络里的服务名,比如 kafka:9092。

    配错的典型症状:能建立连接,但发消息一直超时或重试。

    1.7 排错清单

    现象原因 / 解决
    docker 不是内部或外部命令 装完没重开终端或没注销;重开 PowerShell
    docker version 只有 Client Docker Desktop 没启动,或 Engine 还在 starting
    wsl 报错 / Docker 起不来 wsl –update,然后在 BIOS 里确认虚拟化已开启
    docker compose up 卡在 pull 网络问题,配代理或镜像;确认 docker pull apache/kafka:4.4.0 能单独成功
    容器启动后马上 Exited docker compose logs kafka 看报错;常见是端口占用或配置项拼写错误
    9092 端口被占用 Get-NetTCPConnection -LocalPort 9092 查进程;或把 compose 改成 "19092:9092",客户端连 localhost:19092
    kafka-topics.sh 连不上 等 10~30 秒再试;确认容器 Up;确认用了 –bootstrap-server localhost:9092
    docker compose down 后消息没了 正常,最简版没挂数据卷;需要保留就用 full 版

    二、核心概念:看懂 Kafka

    概念顺了,后面的命令和代码都是自然结果。

    2.1 定义

    Kafka 是一个分布式的、只能追加的日志(append-only log),生产者往里写,消费者按自己的进度读,消息按策略保留而不是读完就删。

    三个关键词:

    • 分布式:可以多台机器组成集群,水平扩展。
    • 只能追加:消息一旦写入就不可修改,只能往后加。
    • 日志:像一本只往后写的账本,每行有一个递增编号。

    所以 Kafka 常被叫做「分布式提交日志」或「事件流平台」,而不只是「消息队列」。

    传统队列像传送带,东西被取走就没了。Kafka 像银行流水账,每笔交易按顺序记下来,谁想看都可以从任意一页开始看,账本按时间保留,不会因为有人看过就撕掉。

    2.2 看懂整体结构

    ┌──────────────────── Kafka 集群 ────────────────────┐
    │ │
    Producer ──写──▶│ Topic: orders │
    (生产者) │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
    │ │Partition 0 │ │Partition 1 │ │Partition 2 │ │
    │ │ 0 1 2 3 4..│ │ 0 1 2 3 4..│ │ 0 1 2 3 4..│ │
    │ └────────────┘ └────────────┘ └────────────┘ │
    │ ▲ ▲ ▲ │
    └─────────┼──────────────┼──────────────┼──────────────┘
    │ │ │
    ┌─────────────┴───┐ ┌──────┴──────────┐ │
    │ 消费组 A │ │ 消费组 B │ │
    │ offset: 3 │ │ offset: 5 │ │
    │ (自己记自己的) │ │ (互不影响) │ │
    └─────────────────┘ └─────────────────┘ │

    记住这张图的四个角色:Producer、Topic/Partition、Consumer Group、Broker。

    如果你用过 RabbitMQ,可以这样对照:RabbitMQ 的核心是「队列 + Exchange 路由」,Kafka 的核心是「分区 + offset」,消息不是被确认后删除,而是按保留策略留在日志里。两者不是替代关系,选型部分会细说。

    2.3 一条消息长什么样

    一条 Kafka 消息本质上是一个键值对,外加一些元数据:

    字段说明
    key 可选。决定消息进哪个分区,也常用于业务主键(如订单号、用户 ID)
    value 消息正文,通常是 JSON / Avro / Protobuf 序列化后的字节
    timestamp 生产时间或写入时间
    headers 可选的自定义头,类似 HTTP header
    partition / offset 写入后由 Kafka 分配,标识它落在哪、是第几条

    Kafka 本身不关心 value 里是什么,它只存字节。序列化和反序列化是客户端的事。

    2.4 Topic 和 Partition

    Topic 是逻辑上的消息分类,类似数据库里的「表」或 RabbitMQ 里的「队列名」。比如:

    • orders:订单事件
    • payments:支付事件
    • user-behavior:用户行为日志

    一个 Topic 在物理上被切成多个 Partition(分区)。每个分区是一个独立的、有序的、只能追加的日志文件。

    分区带来三个好处:

  • 水平扩展:不同分区可以放在不同 broker 上,吞吐随机器增加。
  • 并行消费:一个消费组内,不同消费者可以同时消费不同分区。
  • 局部有序:同一分区内严格有序。
  • 代价也要知道:

    • 分区数不是越多越好。分区多了会增加 broker 的元数据、文件句柄、选举和 rebalance 成本。
    • 单机学习环境用 1~3 个分区足够;生产环境常见每个 topic 6~48 个分区,按吞吐和消费者数量定。
    • 分区数只能增加,不能减少(减少会丢消息),所以创建时要稍微留余量。

    2.5 Offset:消息在分区里的「门牌号」

    每个分区内部,消息从 0 开始编号,这个编号就是 offset。

    Partition 0:
    offset: 0 1 2 3 4 5
    ┌────┬─────┬─────┬─────┬─────┬─────┐
    │msg │ msg │ msg │ msg │ msg │ … │
    └────┴─────┴─────┴─────┴─────┴─────┘
    ▲
    消费者下次读这里(offset=3)

    两个容易混的概念:

    • 消费位置(position):消费者当前读到哪,存在内存里。
    • 提交的 offset(committed offset):消费者告诉 Kafka「我已经处理到哪了」,存在 Kafka 内部的 __consumer_offsets topic 里。

    committed offset 指的是「下一条要读的消息」,不是「最后一条读过的消息」。所以提交 offset=3,表示 0、1、2 都处理完了,下次从 3 开始读。

    offset 只在分区内有意义。Partition 0 的 offset=5 和 Partition 1 的 offset=5 是两条完全不同的消息。

    2.6 Broker、Controller 和 KRaft

    • Broker:一个 Kafka 服务进程。一个 broker 可以管理多个分区。3 台机器组成集群,就是 3 个 broker。
    • Controller:集群里负责管理元数据(topic、分区、副本、leader 选举)的角色。以前由 ZooKeeper 承担,现在由 Kafka 自己选出一个 controller。
    • KRaft:Kafka 自己实现的 Raft 一致性协议,用来替代 ZooKeeper。Kafka 4.0 起 ZooKeeper 已被彻底移除,所有集群都跑在 KRaft 上。

    单机学习环境通常是「combined 模式」:一个进程同时是 broker 和 controller。生产环境一般把 controller 和 broker 分开部署。

    2.7 副本(Replica)与 ISR:高可用的基础

    每个分区可以有多个副本,分散在不同 broker 上:

    Partition 0, replication-factor = 3

    Broker 1: [Leader] ← 生产和消费都找它
    Broker 2: [Follower] ← 持续从 Leader 拉数据
    Broker 3: [Follower]

    • Leader:唯一对外提供读写的副本。
    • Follower:只负责同步,不对外服务(除非发生故障切换)。
    • ISR(In-Sync Replicas):当前与 leader 保持同步的副本集合。落后太多的 follower 会被踢出 ISR。

    关键参数:

    参数含义
    replication.factor 每个分区总共几个副本,生产环境通常 3
    min.insync.replicas 至少几个副本确认才算写入成功,通常 2
    acks=all 生产者要求所有 ISR 都确认(Kafka 3.0+ 默认)

    三者配合才能在坏一台机器时不丢数据:

    replication.factor=3 + min.insync.replicas=2 + acks=all:最多允许坏 1 台,写入仍然安全。

    单机学习环境副本数只能是 1,所以没有高可用可言,broker 挂了数据就没了。这是正常的,别在单机上纠结副本。

    2.8 生产者(Producer)怎么工作

    生产者发送一条消息,大致经历:

  • 序列化:把对象变成字节(String、JSON、Avro…)。
  • 选分区:
    • 指定了 partition → 直接用;
    • 有 key → 对 key 做哈希,再对分区数取模;
    • 没有 key → 粘性分区(一段时间内集中写一个分区,攒批更高效)。
  • 攒批:消息先进入客户端缓冲区,凑够 batch.size 或等够 linger.ms 再一起发。
  • 发送并等待确认:按 acks 决定等谁确认。
  • 失败重试:按 retries 重试,配合幂等性避免重复。
  • 常用参数(初学先知道有这些):

    参数默认值(Kafka 4.x)作用
    acks all 0=不等确认,1=leader 确认,all=ISR 全部确认
    enable.idempotence true 幂等生产者,避免重试导致重复
    linger.ms 5 最多等多久攒批,调大吞吐高但延迟高
    batch.size 16KB 单批最大字节数
    compression.type none 压缩算法,常用 lz4 / zstd

    吞吐和延迟是一对矛盾:linger.ms 越大,批越大、吞吐越高,但单条消息等待越久。默认值已经是折中。

    2.9 消费者与消费组(Consumer Group)

    这是 Kafka 最核心、也最容易搞混的部分。

    基本规则:

    • 消费者用 group.id 表明自己属于哪个消费组。
    • 同一个消费组内,一个分区最多分配给一个消费者。
    • 不同消费组之间完全独立,各自维护自己的 offset,同一条消息可以被多个组各读一次。

    举例。 Topic 有 3 个分区:

    消费组 A(2 个消费者):
    consumer-1 → Partition 0, Partition 1
    consumer-2 → Partition 2

    消费组 B(1 个消费者):
    consumer-3 → Partition 0, Partition 1, Partition 2

    消费组 C(4 个消费者):
    consumer-4 → Partition 0
    consumer-5 → Partition 1
    consumer-6 → Partition 2
    consumer-7 → 空闲(没有分区可分)

    由此得出两个重要结论:

  • 消费者数量超过分区数,多出来的消费者会闲着。想提高消费并行度,先加分区。
  • 同一个分区不会被同组两个消费者同时消费,所以组内不会重复消费(正常情况)。
  • 拉模型(Pull)。 Kafka 消费者是主动拉消息的,不是 broker 推。好处是消费者能自己控制节奏,不会被压垮;坏处是空闲时会有一点点轮询开销(Kafka 用长轮询解决)。

    Rebalance(重平衡)。 以下情况会让消费组重新分配分区:

    • 消费者加入或离开;
    • 消费者心跳超时(被认为挂了);
    • 订阅的 topic 分区数变化。

    重平衡期间消费会短暂停顿,生产环境要尽量避免频繁 rebalance。常见原因:消费者处理一条消息太慢,超过 max.poll.interval.ms(默认 5 分钟)被踢出组。

    Offset 提交与重复消费。

    • 默认 enable.auto.commit=true,每 5 秒自动提交一次。
    • 如果消息处理到一半消费者崩了,已提交的 offset 可能已经越过未处理完的消息,导致消息丢失(at-most-once 风险)。
    • 如果先处理业务、再提交 offset,崩在提交前,导致消息重复(at-least-once)。

    所以 Kafka 默认语义通常是 at-least-once(至少一次),业务要自己做幂等。

    2.10 消息保留:读完不会删

    这是 Kafka 和传统队列最大的区别。

    Kafka 的消息不会因为被消费而删除,而是按配置保留:

    配置默认含义
    log.retention.hours 168(7 天) 超过 7 天的消息删除
    log.retention.bytes -1(不限) 分区总大小超过阈值后删旧消息
    cleanup.policy delete 也可设为 compact,按 key 保留最新值

    由此带来几个能力:

    • 重放(replay):把 offset 重置回更早的位置,重新消费历史消息。
    • 多消费组独立读:新上线的服务可以从头读历史数据。
    • 事件溯源 / 流处理:把 Kafka 当成可回放的事件存储。

    代价是存储成本:消息留得越久、流量越大,磁盘占用越高。

    2.11 三种投递语义

    语义做法风险
    At most once(至多一次) 先提交 offset,再处理 可能丢消息
    At least once(至少一次) 先处理,再提交 offset 可能重复(最常见)
    Exactly once(精确一次) 幂等生产者 + 事务 + read_committed 配置复杂,只覆盖 Kafka 内部读写

    现实建议:

    • 绝大多数业务用 at-least-once + 消费端幂等(比如用业务唯一 ID 去重)。
    • 「精确一次」只在 Kafka 的读-处理-写链路内成立;如果消费时要调外部接口、写数据库,仍然要靠业务幂等或事务消息方案。

    2.12 顺序性:只在分区内保证

    Kafka 不保证全局顺序,只保证单个分区内的顺序。

    想保证同一业务实体的消息有序:

  • 用业务主键作为消息 key(如 orderId);
  • Kafka 会对 key 哈希,同一个 key 永远进同一个分区;
  • 该分区内按写入顺序消费。
  • 注意:

    • 分区数一旦变化,key 到分区的映射会变,历史消息和新消息可能不在同一分区,顺序假设会被打破。
    • 全局有序只能把 topic 设成 1 个分区,吞吐会严重受限,绝大多数场景不需要。

    2.13 术语速查表

    术语一句话解释
    Broker 一个 Kafka 服务节点
    Controller 管理集群元数据和 leader 选举的角色
    KRaft Kafka 自带的共识协议,替代 ZooKeeper
    Topic 消息的逻辑分类
    Partition Topic 的物理分片,有序追加日志
    Offset 消息在分区内的递增编号
    Replica 分区的副本
    Leader / Follower 副本中的主 / 从
    ISR 与 leader 保持同步的副本集合
    Producer 消息生产者
    Consumer 消息消费者
    Consumer Group 一组协同消费的消费者,组内分区不重复分配
    Rebalance 消费组重新分配分区
    Lag 消费进度落后量 = 最新 offset − 已提交 offset
    Retention 消息保留策略
    Compaction 按 key 压缩,只保留每个 key 的最新值

    2.14 五个常见误解

  • 「Kafka 消息消费完就删了」 ❌ 错。按保留策略删除,和消费无关。这正是它能重放的原因。

  • 「分区越多越好」 ❌ 分区有元数据、文件句柄、rebalance、端到端延迟等成本。够用就好。

  • 「Kafka 能保证全局顺序」 ❌ 只保证单分区内有序。要业务有序就用 key。

  • 「消费者加得越多消费越快」 ❌ 同组内并行度上限 = 分区数。消费者多于分区,多出来的空闲。

  • 「Kafka 就是个消息队列」 ⚠️ 它同时是消息队列、事件存储和流处理平台(Kafka Streams / Connect)。把它只当队列用,会浪费它的核心能力。


  • 三、命令行操作:把 Kafka 玩起来

    这一章全是命令,建议边看边敲。前提是第一章的容器已经启动。

    约定:容器名 kafka,Kafka 命令目录 /opt/kafka/bin/,broker 地址 localhost:9092。每条命令的完整形式是:

    docker exec –it kafka /opt/kafka/bin/<脚本> —bootstrap-server localhost:9092 ...

    3.1 先认识几个脚本

    脚本用途
    kafka-topics.sh 建 / 删 / 改 / 查 topic
    kafka-console-producer.sh 命令行发消息
    kafka-console-consumer.sh 命令行收消息
    kafka-consumer-groups.sh 查消费组、LAG、重置 offset
    kafka-configs.sh 改 topic / broker 配置
    kafka-get-offsets.sh 查分区最新 offset

    Kafka 4.x 的命令统一用 –bootstrap-server,不再支持 –zookeeper(ZooKeeper 已被移除)。

    3.2 Topic 的增删改查

    创建 topic:

    docker exec –it kafka /opt/kafka/bin/kafka-topics.sh —bootstrap-server localhost:9092 —create —topic demo-topic —partitions 3 —replication-factor 1

    参数说明:–partitions 3 是 3 个分区;–replication-factor 1 是因为单机只能 1 个副本。

    成功输出:

    Created topic demo-topic.

    查看所有 topic:

    docker exec –it kafka /opt/kafka/bin/kafka-topics.sh —bootstrap-server localhost:9092 —list

    查看某个 topic 的详情:

    docker exec –it kafka /opt/kafka/bin/kafka-topics.sh —bootstrap-server localhost:9092 —describe —topic demo-topic

    输出类似:

    Topic: demo-topicTopicId: xxxxPartitionCount: 3ReplicationFactor: 1Configs:
    Topic: demo-topicPartition: 0Leader: 1Replicas: 1Isr: 1Elr: LastKnownElr:
    Topic: demo-topicPartition: 1Leader: 1Replicas: 1Isr: 1Elr: LastKnownElr:
    Topic: demo-topicPartition: 2Leader: 1Replicas: 1Isr: 1Elr: LastKnownElr:

    • Leader:负责这个分区的 broker id(单机就是 1);
    • Replicas:副本所在 broker;
    • Isr:当前同步的副本。

    增加分区(只能加,不能减):

    docker exec –it kafka /opt/kafka/bin/kafka-topics.sh —bootstrap-server localhost:9092 —alter —topic demo-topic —partitions 6

    生产环境慎用:分区数变化会影响按 key 的哈希映射,可能破坏「同一 key 顺序消费」的假设。

    删除 topic:

    docker exec –it kafka /opt/kafka/bin/kafka-topics.sh —bootstrap-server localhost:9092 —delete —topic demo-topic

    3.3 发消息:console producer

    打开一个终端执行:

    docker exec –it kafka /opt/kafka/bin/kafka-console-producer.sh —bootstrap-server localhost:9092 —topic demo-topic —property parse.key=true —property key.separator=:

    出现 > 提示符后,一行一条消息,格式是 key:value:

    user-1:第一条消息
    user-2:第二条消息
    user-1:第三条消息

    按 Ctrl+C 退出。

    不带 parse.key=true 时,整行都会当成 value,没有 key。有 key 才能控制分区。

    3.4 收消息:console consumer

    从最早的消息开始读。 新开一个终端:

    docker exec –it kafka /opt/kafka/bin/kafka-console-consumer.sh —bootstrap-server localhost:9092 —topic demo-topic —from–beginning —property print.key=true —property print.partition=true —property print.offset=true

    你应该能看到刚才发的三条消息,并且输出里会带上每条消息的 key、partition 和 offset。不同 Kafka 版本、不同格式化器的排版可能略有差异,不用纠结列的顺序,重点观察:同一个 key 的消息是否落在同一个 partition。

    带消费组读(会记录 offset):

    docker exec –it kafka /opt/kafka/bin/kafka-console-consumer.sh —bootstrap-server localhost:9092 —topic demo-topic —group g1 —property print.key=true —property print.partition=true

    要点:

    • 加了 –group 才会提交 offset,下次再启动会从上次位置继续。
    • –from-beginning 只在这个组还没有已提交 offset 时生效。组一旦有了 offset,就按 offset 继续读。

    再开一个不同组的消费者:

    docker exec –it kafka /opt/kafka/bin/kafka-console-consumer.sh —bootstrap-server localhost:9092 —topic demo-topic —group g2 —from–beginning

    你会看到 g2 把历史消息又完整读了一遍。不同消费组互不影响,这就是 Kafka 多订阅者的方式。

    同一个组开两个消费者。 再开一个终端,仍然用 –group g1:

    docker exec –it kafka /opt/kafka/bin/kafka-console-consumer.sh —bootstrap-server localhost:9092 —topic demo-topic —group g1 —property print.partition=true

    两个 g1 消费者会各自分到一部分分区(3 个分区分给 2 个人),同一条消息不会被同组两个人同时消费。

    3.5 查看消费组和 LAG

    docker exec –it kafka /opt/kafka/bin/kafka-consumer-groups.sh —bootstrap-server localhost:9092 —list

    查看某个组:

    docker exec –it kafka /opt/kafka/bin/kafka-consumer-groups.sh —bootstrap-server localhost:9092 —describe —group g1

    输出类似:

    GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID HOST CLIENT-ID
    g1 demo-topic 0 2 2 0 …
    g1 demo-topic 1 1 1 0 …
    g1 demo-topic 2 1 1 0 …

    三个关键列:

    • CURRENT-OFFSET:这个组已提交到的位置;
    • LOG-END-OFFSET:分区当前最新位置(下一条消息的编号);
    • LAG:还差多少条没消费 = LOG-END-OFFSET – CURRENT-OFFSET。

    LAG 是 Kafka 最重要的监控指标。 LAG 持续上涨,说明消费速度跟不上生产速度。

    想亲手制造一点 LAG:

  • 停掉 g1 的所有消费者(Ctrl+C);
  • 用 console producer 再发 5 条消息;
  • 再执行 –describe –group g1,会看到 LAG 变成 5;
  • 重新启动 g1 消费者,消息消费完后 LAG 回到 0。
  • 3.6 重置 offset(重放历史消息)

    前提:这个消费组当前没有活跃消费者,否则会报错。

    docker exec –it kafka /opt/kafka/bin/kafka-consumer-groups.sh —bootstrap-server localhost:9092 —group g1 —topic demo-topic —reset-offsets —to-earliest —execute

    常用变体:

    # 重置到最新(相当于跳过所有积压)
    ... —reset-offsets —to-latest —execute

    # 重置到指定 offset
    ... —reset-offsets —to-offset 2 —execute

    # 从当前位置往回退 3 条
    ... —reset-offsets —shift-by –3 —execute

    # 重置到某个时间点之后
    ... —reset-offsets —to-datetime 2026-10-29T00:00:00.000 —execute

    把 –execute 换成 –dry-run 可以先预览结果,不实际修改。

    重置后再启动消费者,就会重新读历史消息,这就是「重放」。

    3.7 查看分区最新 offset

    docker exec –it kafka /opt/kafka/bin/kafka-get-offsets.sh —bootstrap-server localhost:9092 —topic demo-topic

    输出:

    demo-topic:0:3
    demo-topic:1:2
    demo-topic:2:1

    格式是 topic:partition:最新offset。可以拿它和消费组的 CURRENT-OFFSET 对照算 LAG。

    3.8 修改 topic 配置

    比如把保留时间改成 1 小时:

    docker exec –it kafka /opt/kafka/bin/kafka-configs.sh —bootstrap-server localhost:9092 —alter —entity-type topics —entity-name demo-topic —add-config retention.ms=3600000

    查看当前覆盖的配置:

    docker exec –it kafka /opt/kafka/bin/kafka-configs.sh —bootstrap-server localhost:9092 —describe —entity-type topics —entity-name demo-topic

    删除这个覆盖,恢复默认:

    docker exec –it kafka /opt/kafka/bin/kafka-configs.sh —bootstrap-server localhost:9092 —alter —entity-type topics —entity-name demo-topic —delete-config retention.ms

    3.9 完整练习(建议按顺序做一遍)

    # 1. 建一个 3 分区 topic
    docker exec –it kafka /opt/kafka/bin/kafka-topics.sh —bootstrap-server localhost:9092 —create —topic practice —partitions 3 —replication-factor 1

    # 2. 发 6 条带 key 的消息(进入交互模式后逐行输入,Ctrl+C 退出)
    docker exec –it kafka /opt/kafka/bin/kafka-console-producer.sh —bootstrap-server localhost:9092 —topic practice —property parse.key=true —property key.separator=:
    # user-1:下单
    # user-2:下单
    # user-1:支付
    # user-3:下单
    # user-2:支付
    # user-1:发货

    # 3. 用 g1 组从头消费,观察 key、partition、offset
    docker exec –it kafka /opt/kafka/bin/kafka-console-consumer.sh —bootstrap-server localhost:9092 —topic practice —group g1 —from–beginning —property print.key=true —property print.partition=true —property print.offset=true

    # 4. Ctrl+C 停掉,再发 3 条消息,然后看 LAG
    docker exec –it kafka /opt/kafka/bin/kafka-consumer-groups.sh —bootstrap-server localhost:9092 —describe —group g1

    # 5. 重置到最早,重新消费
    docker exec –it kafka /opt/kafka/bin/kafka-consumer-groups.sh —bootstrap-server localhost:9092 —group g1 —topic practice —reset-offsets —to-earliest —execute
    docker exec –it kafka /opt/kafka/bin/kafka-console-consumer.sh —bootstrap-server localhost:9092 —topic practice —group g1 —from–beginning

    做完这五步,你已经掌握了 Kafka 日常 80% 的命令行操作。

    3.10 命令速查

    # 建 topic
    docker exec –it kafka /opt/kafka/bin/kafka-topics.sh —bootstrap-server localhost:9092 —create —topic <name> —partitions 3 —replication-factor 1
    # 列 topic
    docker exec –it kafka /opt/kafka/bin/kafka-topics.sh —bootstrap-server localhost:9092 —list
    # 看详情
    docker exec –it kafka /opt/kafka/bin/kafka-topics.sh —bootstrap-server localhost:9092 —describe —topic <name>
    # 删 topic
    docker exec –it kafka /opt/kafka/bin/kafka-topics.sh —bootstrap-server localhost:9092 —delete —topic <name>
    # 发消息
    docker exec –it kafka /opt/kafka/bin/kafka-console-producer.sh —bootstrap-server localhost:9092 —topic <name>
    # 收消息
    docker exec –it kafka /opt/kafka/bin/kafka-console-consumer.sh —bootstrap-server localhost:9092 —topic <name> —from–beginning
    # 看组
    docker exec –it kafka /opt/kafka/bin/kafka-consumer-groups.sh —bootstrap-server localhost:9092 —describe —group <group>
    # 重置 offset
    docker exec –it kafka /opt/kafka/bin/kafka-consumer-groups.sh —bootstrap-server localhost:9092 —group <group> —topic <name> —reset-offsets —to-earliest —execute


    四、Spring Boot 实战:写一个能跑的生产者 + 消费者

    目标:从零建一个 Spring Boot 项目,提供 HTTP 接口发消息,后台消费者把消息打印出来。下面给出完整代码,并解释每一行为什么这么写。

    4.1 准备 JDK 17 和 Maven

    Kafka 跑在 Docker 里,不需要本机 Java。但 Spring Boot 4.x 需要 JDK 17 及以上。

    先确认当前用的是哪个 JDK:

    java –version

    只对当前 PowerShell 窗口生效的切换方式(推荐,不影响系统里其他用旧 JDK 的项目):

    $env:JAVA_HOME = "C:\\Program Files\\Java\\jdk-17" # 换成你自己的 JDK 17 安装路径
    $env:Path = "$env:JAVA_HOME\\bin;$env:Path"
    java –version
    mvn –v

    mvn -v 里应该显示 Java version: 17.x。

    在 IntelliJ IDEA 里则更简单:打开项目后 File → Project Structure → Project → SDK 选 17 即可,不用改系统环境变量。

    不建议用 setx JAVA_HOME 永久改成 17:你其他项目可能还在用 11,全局改容易出别的问题。

    如果 Maven 下载依赖很慢,在 ~/.m2/settings.xml 的 <mirrors> 里加一个阿里云镜像:

    <mirror>
    <id>aliyunmaven</id>
    <mirrorOf>central</mirrorOf>
    <name>阿里云公共仓库</name>
    <url>https://maven.aliyun.com/repository/public</url>
    </mirror>

    已经能正常下载依赖的话,这一段可以跳过。

    4.2 版本要求

    组件版本说明
    JDK 17+ Spring Boot 4.x 最低要求
    Spring Boot 4.1.2 当前稳定版
    spring-kafka 4.1.2 由 Boot 管理,不用手写版本号
    kafka-clients 4.2.2 由 Boot 管理
    Kafka broker 4.4.0 Docker 里的那个,客户端 4.2 连 4.4 完全兼容

    Spring Boot 4 的变化:Kafka 的 starter 改名为 spring-boot-starter-kafka(Boot 3 是直接引 spring-kafka)。网上很多 Boot 3 的教程依赖写法不一样,注意区分。

    4.3 项目结构

    spring-kafka-demo/
    ├── pom.xml
    └── src/main/
    ├── java/com/example/kafkademo/
    │ ├── KafkaDemoApplication.java # 启动类
    │ ├── config/TopicConfig.java # 启动时自动建 topic
    │ ├── producer/MessageProducer.java
    │ ├── consumer/MessageConsumer.java
    │ └── web/MessageController.java
    └── resources/application.yml

    4.4 依赖:pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.1.2</version>
    <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>spring-kafka-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-kafka-demo</name>

    <properties>
    <java.version>17</java.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-kafka</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    </dependencies>

    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>
    </project>

    只引了两个 starter:

    • spring-boot-starter-kafka:带来 spring-kafka、kafka-clients 和自动配置;
    • spring-boot-starter-web:提供 REST 接口方便测试。

    4.5 配置:application.yml

    server:
    # 用 8081,避免和本机常见的 8080 服务冲突
    port: 8081

    spring:
    application:
    name: spring–kafka–demo

    kafka:
    bootstrap-servers: localhost:9092

    producer:
    key-serializer: org.apache.kafka.common.serialization.StringSerializer
    value-serializer: org.apache.kafka.common.serialization.StringSerializer

    consumer:
    group-id: demo–group
    auto-offset-reset: earliest
    key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    value-deserializer: org.apache.kafka.common.serialization.StringDeserializer

    listener:
    ack-mode: batch
    missing-topics-fatal: false

    逐项解释:

    • bootstrap-servers:Kafka 地址。客户端只需要连上一个 broker,就能拿到整个集群的元数据。
    • key-serializer / value-serializer:发送前把 Java 对象转成字节。这里用最简单的 String。
    • group-id:消费组。同一个组内分区不重复分配。
    • auto-offset-reset: earliest:组里没有已提交 offset 时,从最早开始读。注意:只在没有 offset 时生效。
    • ack-mode: batch:Spring 的监听容器在一批消息处理完后提交 offset。这是 Spring 的默认行为,和原生客户端的 enable.auto.commit 不是一回事。
    • missing-topics-fatal: false:topic 暂时不存在也不要让应用启动失败。

    4.6 启动类

    package com.example.kafkademo;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.kafka.annotation.EnableKafka;

    @EnableKafka
    @SpringBootApplication
    public class KafkaDemoApplication {

    public static void main(String[] args) {
    SpringApplication.run(KafkaDemoApplication.class, args);
    }
    }

    @EnableKafka 开启 @KafkaListener 注解的解析。Spring Boot 通常会自动帮你开启,但显式写上更清楚,也不会冲突。

    4.7 启动时创建 topic

    package com.example.kafkademo.config;

    import org.apache.kafka.clients.admin.NewTopic;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.kafka.config.TopicBuilder;

    @Configuration
    public class TopicConfig {

    @Bean
    public NewTopic demoTopic() {
    return TopicBuilder.name("demo-topic")
    .partitions(3)
    .replicas(1)
    .build();
    }
    }

    Spring Boot 检测到 NewTopic 类型的 Bean 后,会通过 KafkaAdmin 在 broker 上创建它。已经存在则跳过。

    单机环境副本数必须是 1,写 3 会创建失败。

    生产环境更推荐用运维脚本或平台建 topic,把分区数、副本数、保留策略纳入版本管理;NewTopic Bean 适合本地开发和 demo。

    4.8 生产者

    package com.example.kafkademo.producer;

    import org.springframework.kafka.core.KafkaTemplate;
    import org.springframework.kafka.support.SendResult;
    import org.springframework.stereotype.Service;

    import java.util.concurrent.CompletableFuture;

    @Service
    public class MessageProducer {

    private final KafkaTemplate<String, String> kafkaTemplate;

    public MessageProducer(KafkaTemplate<String, String> kafkaTemplate) {
    this.kafkaTemplate = kafkaTemplate;
    }

    public CompletableFuture<SendResult<String, String>> send(String topic, String key, String message) {
    if (key == null || key.isBlank()) {
    // 没有 key:由 Kafka 的粘性分区器决定分区
    return kafkaTemplate.send(topic, message);
    }
    // 有 key:hash(key) % 分区数,同一个 key 永远进同一个分区
    return kafkaTemplate.send(topic, key, message);
    }
    }

    KafkaTemplate 是 Spring 对 KafkaProducer 的封装,已经由 Boot 自动配置好,直接注入即可。

    send 是异步的,返回 CompletableFuture。生产环境可以加 .whenComplete(…) 记录成功/失败,或者用同步的 .get()(会阻塞,慎用)。

    4.9 消费者

    package com.example.kafkademo.consumer;

    import org.apache.kafka.clients.consumer.ConsumerRecord;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.kafka.annotation.KafkaListener;
    import org.springframework.stereotype.Component;

    @Component
    public class MessageConsumer {

    private static final Logger log = LoggerFactory.getLogger(MessageConsumer.class);

    @KafkaListener(topics = "demo-topic", groupId = "demo-group")
    public void consume(ConsumerRecord<String, String> record) {
    log.info("收到消息 -> partition={}, offset={}, key={}, value={}",
    record.partition(),
    record.offset(),
    record.key(),
    record.value());

    // 这里写你的业务逻辑:写数据库、调用下游、发通知……
    }
    }

    @KafkaListener 背后是 Spring 启动的一个监听容器:它在后台循环 poll 消息、反序列化、调用你的方法、按 ack-mode 提交 offset。你不需要自己写 while 循环。

    方法参数可以直接是 String(只关心 value),也可以是 ConsumerRecord(需要 key/partition/offset 时)。这里用 ConsumerRecord 方便观察。

    4.10 REST 接口

    package com.example.kafkademo.web;

    import com.example.kafkademo.producer.MessageProducer;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;

    import java.util.Map;

    @RestController
    @RequestMapping("/messages")
    public class MessageController {

    private final MessageProducer producer;

    public MessageController(MessageProducer producer) {
    this.producer = producer;
    }

    @PostMapping
    public Map<String, Object> send(@RequestParam String content,
    @RequestParam(required = false) String key,
    @RequestParam(defaultValue = "demo-topic") String topic) {
    producer.send(topic, key, content);
    return Map.of(
    "status", "sent",
    "topic", topic,
    "key", key == null ? "" : key,
    "content", content
    );
    }
    }

    4.11 运行和验证

    第一步:确认 Kafka 在跑。

    docker compose ps

    第二步:切到 JDK 17 并启动应用。

    $env:JAVA_HOME = "C:\\Program Files\\Java\\jdk-17" # 换成你自己的 JDK 17 安装路径
    $env:Path = "$env:JAVA_HOME\\bin;$env:Path"

    cd spring-kafka-demo
    mvn spring-boot:run

    第一次会下载依赖,耐心等。看到下面的日志就启动成功了:

    Tomcat started on port 8081
    Started KafkaDemoApplication in x.xxx seconds

    这里不用 8080,是为了避开本机常见的其他 Web 服务,端口冲突时改 application.yml 即可。

    第三步:另开一个终端发消息。

    curl.exe –X POST "http://localhost:8081/messages?content=hello-kafka"

    PowerShell 里 curl 是 Invoke-WebRequest 的别名,所以这里写 curl.exe。Linux / macOS 直接用 curl。

    返回:

    {"status":"sent","topic":"demo-topic","key":"","content":"hello-kafka"}

    回到应用控制台,应该能看到:

    收到消息 -> partition=0, offset=0, key=null, value=hello-kafka

    第四步:观察 key 和分区的关系。

    curl.exe –X POST "http://localhost:8081/messages?content=order-created&key=order-1"
    curl.exe –X POST "http://localhost:8081/messages?content=order-paid&key=order-1"
    curl.exe –X POST "http://localhost:8081/messages?content=order-shipped&key=order-1"

    三条消息的 key 都是 order-1,观察日志里的 partition,一定相同。这就是 Kafka 保证同一业务消息有序的方式。

    第五步:用命令行独立验证。

    用另一个消费组从头读一遍,证明消息确实在 Kafka 里:

    docker exec –it kafka /opt/kafka/bin/kafka-console-consumer.sh —bootstrap-server localhost:9092 —topic demo-topic —group cli–check —from–beginning —property print.key=true —property print.partition=true

    cli-check 是新的组,所以能从头读到所有消息,和 demo-group 互不影响。

    4.12 背后发生了什么

  • 自动配置:spring-boot-starter-kafka 检测到配置,创建 KafkaTemplate、ConsumerFactory、KafkaListenerContainerFactory、KafkaAdmin。
  • 建 topic:KafkaAdmin 发现 NewTopic Bean,调用 AdminClient 创建 demo-topic(3 分区)。
  • 发消息:Controller 收到请求 → KafkaTemplate.send → 序列化 → 选分区 → 攒批 → 发到 broker。
  • 消费:监听容器后台 poll → 反序列化 → 调 consume() → 一批处理完提交 offset。
  • offset 存在哪:Kafka 内部的 __consumer_offsets topic,所以重启应用后能从上次位置继续。
  • 默认 ack-mode: batch 意味着「先处理、后提交」。如果应用在处理完但提交前崩溃,重启后会重复消费,这就是 at-least-once。业务要按幂等设计(比如用订单号 + 状态机去重)。

    4.13 进阶用法

    手动提交 offset。

    配置:

    spring:
    kafka:
    listener:
    ack-mode: manual_immediate

    消费者方法加 Acknowledgment 参数,处理成功后手动确认:

    import org.springframework.kafka.support.Acknowledgment;

    @KafkaListener(topics = "demo-topic", groupId = "demo-group")
    public void consume(ConsumerRecord<String, String> record, Acknowledgment ack) {
    // 业务处理
    log.info("处理: {}", record.value());
    ack.acknowledge(); // 处理成功才提交
    }

    适合「宁可重复,不能丢」的场景。注意:如果业务处理抛异常且没 catch,消息不会提交,会触发重试。

    发送 JSON。

    Boot 4 / Spring Kafka 4 里,旧的 JsonSerializer 已标记为过时,新类是 JacksonJsonSerializer / JacksonJsonDeserializer。配置示例:

    spring:
    kafka:
    producer:
    value-serializer: org.springframework.kafka.support.serializer.JacksonJsonSerializer
    consumer:
    value-deserializer: org.springframework.kafka.support.serializer.JacksonJsonDeserializer
    properties:
    spring.json.trusted.packages: "com.example.kafkademo"

    同时把消费者方法的参数改成你的 DTO 类型。Boot 4 需要 Jackson 3,确认 classpath 里有 spring-boot-starter-jackson(starter-web 一般会带)。

    建议先用 String 发 JSON 字符串,把 Kafka 的机制跑通,再上自动序列化。少一层魔法,出错时更容易定位。

    提高消费并发。

    spring:
    kafka:
    listener:
    concurrency: 3

    每个并发对应一个消费者线程。并发数不要超过分区数,否则多出来的线程分不到分区。

    指定分区发送。

    kafkaTemplate.send(topic, partition, key, message);

    一般不需要手动指定,交给 key 哈希更稳。

    消费失败怎么办。

    • 临时故障(下游超时):让异常抛出,Spring 的重试机制会重试;
    • 永久故障(脏数据):重试多次后仍失败,应该进死信 topic(DLT),人工处理;
    • Spring Kafka 提供 DefaultErrorHandler + DeadLetterPublishingRecoverer,可以搜索这两个类继续深入。

    4.14 排错

    现象原因 / 解决
    启动报 Class file has wrong version 61.0 运行或编译用的还是旧 JDK(61.0 对应 Java 17)。确认 mvn -v 显示 17,或 IDEA 的 Project SDK 改成 17
    Connection to node -1 … / 发送一直重试 Kafka 没启动,或 bootstrap-servers 地址不对
    消费者收不到消息 检查 group-id、topic 名、auto-offset-reset;换个新 group 试 –from-beginning
    每次重启都重复消费 正常(at-least-once)。检查业务是否幂等,或改用 manual_immediate
    启动报 8081 端口占用 改 server.port,或停掉占用进程
    启动很慢(约 40 秒),日志里有 Could not configure topics Kafka 没启动,KafkaAdmin 在建 topic 时超时。先 docker compose up -d 再启动应用
    missing-topics-fatal 报错 已配置为 false;仍报错说明 Kafka 连不上,不是 topic 问题
    IDEA 里 @KafkaListener 不生效 检查是否有 @EnableKafka、类是否被 Spring 扫描到(在启动类同包或子包下)

    五、优缺点与选型:什么时候该用 Kafka

    学完怎么用,还要知道什么时候不该用。这一章帮你建立选型判断,面试也常考。

    5.1 优点:Kafka 为什么流行

    1. 吞吐量高。

    单集群做到百万级消息/秒很常见。原因不是魔法,是几个工程优化叠加:

    • 顺序写磁盘:只追加,不随机改,磁盘顺序写接近内存速度;
    • 页缓存(page cache):读写都先走操作系统缓存,减少真实 IO;
    • 零拷贝(zero-copy):发送时用 sendfile,数据不从内核态拷到用户态;
    • 批量 + 压缩:生产者攒批、broker 批量落盘、消费者批量拉取,再配合 lz4/zstd 压缩;
    • 分区并行:一个 topic 的吞吐可以随分区数水平扩展。

    2. 持久化 + 可重放。

    消息按策略保留(默认 7 天),消费者可以重置 offset 重新读。这让 Kafka 能当「事件存储」用:

    • 新服务上线,可以从头消费历史数据回填;
    • 线上出 bug,可以重放某段时间的消息复现;
    • 流处理任务失败重启,从 checkpoint/offset 继续。

    3. 多消费者、互不影响。

    不同消费组各自维护 offset,同一条消息可以被风控、推荐、报表等多个系统各读一遍,生产者不用为每个下游单独发一份。

    4. 水平扩展。

    加 broker、加分区就能提升吞吐和存储。生产环境 3 broker 起步,几十上百 broker 的集群也很常见。

    5. 生态完整。

    • Kafka Connect:和各种数据源/目标对接(MySQL、ES、HDFS、S3),少写很多同步代码;
    • Kafka Streams:库级别的流处理,不用额外部署计算集群;
    • Schema Registry:管理 Avro/Protobuf schema 和兼容性;
    • MirrorMaker 2:跨机房/跨云复制;
    • 各大云厂商都有托管 Kafka。

    6. 削峰填谷和解耦。

    上游突发流量先写进 Kafka,下游按自己的能力消费,避免把数据库打挂。这是 Kafka 最经典的用法。

    7. 顺序性可控。

    按业务 key 分区,就能保证同一实体(订单、用户)的消息有序,不需要全局有序。

    5.2 缺点:用之前必须知道的代价

    1. 运维复杂度高。

    Kafka 是一个分布式存储系统,不是一个轻量队列。你需要关心:

    • 分区数规划、副本分布、磁盘容量;
    • broker 扩容/缩容、版本升级;
    • controller 选举、ISR 收缩、under-replicated 分区;
    • 消费组 LAG、rebalance 频率;
    • 监控、告警、日志清理。

    KRaft 去掉了 ZooKeeper,部署简化了不少,但「分布式系统该有的复杂度」一个没少。小团队没有专职运维,建议直接用云托管。

    2. 延迟不是它的强项。

    为了吞吐,生产端会攒批(linger.ms 默认 5ms),端到端通常是毫秒级。如果你要的是微秒级 RPC、实时性极高的交易撮合,Kafka 不是最优解。

    3. 存储成本。

    消息不会因为消费而删除,保留期内是全量存储。流量大、保留久,磁盘成本很可观。虽然支持分层存储(tiered storage)把旧数据放对象存储,但会增加架构复杂度。

    4. 功能相对「朴素」。

    和 RabbitMQ 比,Kafka 缺少很多业务向的能力:

    • 没有 Exchange 那种灵活路由(direct/topic/fanout/headers);
    • 没有原生的单条消息延迟投递(需要自己用多级 topic + 定时任务实现);
    • 没有优先级队列;
    • 不支持「单条消息拒绝/重新入队」这种队列语义;
    • 不适合请求-响应(RPC)模式。

    5. 消费端要自己处理重复。

    默认 at-least-once,网络抖动、rebalance、处理超时都可能导致重复。业务必须做幂等(唯一键、状态机、去重表)。

    6. 分区数规划是门手艺。

    • 分区太少:并行度不够、单分区成为瓶颈;
    • 分区太多:元数据膨胀、文件句柄多、rebalance 慢、端到端延迟上升;
    • 分区只能加不能减,加分区还会改变 key 的哈希映射,破坏顺序假设。

    7. 不适合超大消息。

    broker 默认单条消息上限约 1MB(message.max.bytes)。大文件、大图片不要塞 Kafka,应该存对象存储,消息里只放 URL。

    8. 学习曲线。

    分区、副本、ISR、offset、消费组、rebalance、事务……概念比 RabbitMQ 多一层。会用不难,用好需要时间。

    5.3 适合 / 不适合的场景

    适合:

    场景说明
    日志 / 埋点 / 监控数据采集 高吞吐、可多下游消费,Kafka 的主场
    微服务事件驱动 订单创建、支付成功等事件广播给多个服务
    削峰填谷 秒杀、大促流量先入 Kafka,后端匀速消费
    数据管道 / CDC 用 Debezium 抓 MySQL binlog,同步到 ES/数仓
    流式计算 实时风控、实时推荐、指标聚合(配合 Flink/Streams)
    事件溯源 / 审计 保留完整事件历史,可回放
    系统解耦 上游不关心下游有几个、什么时候消费

    不适合:

    场景更适合的方案
    任务队列(每任务只处理一次、要优先级/延迟) RabbitMQ、Redis、云队列
    请求-响应 / RPC HTTP/gRPC
    超低延迟(微秒级) 内存队列、RabbitMQ、专用交易系统
    复杂路由(按 header/规则分发) RabbitMQ
    单条消息延迟投递(如 30 分钟后提醒) RabbitMQ 延迟插件、RocketMQ 延迟消息、定时任务
    超大文件传输 对象存储 + 消息里放地址
    只有一两个消费者、量很小的简单异步 数据库任务表、Redis、RabbitMQ 更省事

    5.4 主流消息队列对比

    下面的吞吐/延迟是量级,具体取决于硬件、配置和消息大小,不要当 benchmark 用。

    维度KafkaRabbitMQRocketMQPulsar
    定位 分布式事件流平台 通用消息代理 阿里系业务消息 云原生消息/流平台
    存储模型 分区日志 队列 队列(CommitLog) 存算分离(BookKeeper)
    吞吐量级 百万/秒 万/秒 十万~百万/秒 百万/秒
    延迟量级 毫秒 微秒~毫秒 毫秒 毫秒
    顺序性 分区内 单队列内 队列内 分区内
    延迟消息 无原生 插件 原生(5.x 任意时间) 原生
    事务消息 流处理事务 无 原生 事务
    消息重放 原生 Stream 类型支持 支持 支持
    路由能力 弱 强 中 中
    运维复杂度 高 低 中 高
    典型用户 大数据/日志/流处理 业务解耦/任务队列 电商/金融 多租户/云原生

    怎么选(简化版):

    已经有 RabbitMQ,量不大、没痛点?
    → 别换,够用就好

    日志采集、数据管道、流处理、需要重放、吞吐要求高?
    → Kafka

    电商/金融,需要事务消息、延迟消息,团队在国内?
    → RocketMQ

    多租户、存算分离、云原生环境、想要弹性扩缩?
    → Pulsar(但运维门槛更高)

    团队小、没有专职中间件运维?
    → 优先云托管 Kafka(阿里云/腾讯云/Confluent Cloud),别自己搭生产集群

    结论:Kafka 不是「更高级的 RabbitMQ」,两者解决的是不同问题。 很多公司两者并用:Kafka 做数据管道和事件流,RabbitMQ 做业务任务队列。

    5.5 生产环境的最低配置认知

    即使是入门,也要知道生产不是 docker compose up:

    项目建议
    broker 数量 至少 3 台,才能容忍坏 1 台
    副本数 3
    min.insync.replicas 2
    生产者 acks all
    磁盘 独立数据盘,容量按「日流量 × 保留天数 × 副本数 × 1.3」估算
    监控 LAG、under-replicated partitions、磁盘使用率、请求延迟、controller 状态
    升级 滚动升级,先升 broker 再升客户端,注意协议兼容
    安全 SASL/SSL + ACL,别裸奔

    单机 Docker 只适合学习和本地开发,不要直接搬上生产。

    5.6 一页纸总结

    Kafka 的优点:吞吐高、能存能重放、多消费者、可扩展、生态好。

    Kafka 的代价:运维重、延迟毫秒级、存储贵、功能不花哨、消费端要幂等。

    选型:

    需要「高吞吐的事件管道 + 可重放的历史」选 Kafka; 需要「灵活路由 + 低延迟的任务队列」选 RabbitMQ; 别为了用 Kafka 而用 Kafka。


    六、踩坑、面试与进阶

    这一章当手册用,遇到问题先来这里查。

    6.1 故障排查

    连不上 Kafka。 按顺序检查:

    # 1. 容器在不在
    docker compose ps

    # 2. 日志有没有启动成功
    docker compose logs kafka | Select-String "Kafka Server started"

    # 3. 端口通不通
    Test-NetConnection localhost –Port 9092

    # 4. 用 Kafka 自带脚本测协议连通性
    docker exec –it kafka /opt/kafka/bin/kafka-broker-api-versions.sh —bootstrap-server localhost:9092

    常见原因:

    • 容器没起来 / 还在启动(等 10~30 秒);
    • bootstrap-servers 写错(端口不是 9092、host 不是 localhost);
    • Docker 端口映射改了但客户端没跟着改;
    • 远程访问时 advertised.listeners 配的是 localhost,远程客户端拿到 localhost 后连自己,必然失败。

    消费者收不到消息。 依次排查:

  • topic 名对不对(大小写敏感);
  • 消费组是不是已经有 offset:换个新 group.id 试 –from-beginning;
  • auto.offset.reset 只在「没有已提交 offset」时生效,不是每次启动都从头读;
  • 消息是不是发到了别的 topic / 别的分区;
  • 消费者有没有报反序列化异常(毒消息会导致一直重试或跳过);
  • Spring 项目里 @KafkaListener 所在类是否被扫描、是否加了 @EnableKafka。
  • 重复消费。 Kafka 默认 at-least-once,以下情况都会重复:

    • 处理完、提交 offset 前进程崩溃或重启;
    • rebalance 导致分区换人,新消费者从上次提交位置重读;
    • 处理时间超过 max.poll.interval.ms,消费者被踢出组;
    • 生产者重试(Kafka 3.0+ 默认开启幂等,正常不会因此重复,除非跨会话或配置被改)。

    解决思路:业务幂等,不要指望 Kafka 帮你做到 exactly-once。

    常见幂等方案:

    • 数据库唯一索引(业务 ID + 事件类型);
    • 状态机(只允许 待支付→已支付,重复的「已支付」直接忽略);
    • Redis SETNX 去重键,带过期时间;
    • 消费记录表(messageId 唯一)。

    消息丢失。 可能原因:

    原因说明
    acks=0 发出去就不管,broker 没收到也不知道
    acks=1 leader 写完就确认,leader 挂了且未同步到 follower 就丢
    min.insync.replicas=1 + acks=all ISR 只剩 leader 时仍算成功,随后 leader 挂就丢
    生产者缓冲区满 buffer.memory 满且 block.on.buffer.full=false(旧配置)时丢弃
    自动提交 offset 早于处理 处理失败但 offset 已提交,重启后跳过
    保留时间太短 消费者还没读,消息已被 retention 删除

    防线:acks=all + replication.factor=3 + min.insync.replicas=2 + 关闭自动提交/处理成功后提交 + 合理保留时间 + 生产端处理发送失败。

    顺序错乱。

    • 没设 key:消息被分散到多个分区,分区之间本来就没有顺序;
    • 同一 key 但分区数变了:哈希映射改变,历史和新消息可能不同分区;
    • 生产端重试 + 多请求在途:老版本可能乱序,开启幂等(默认)后单分区内可保证;
    • 消费端多线程处理同分区消息:自己把顺序打乱了。

    保证顺序的做法:同一业务 key + 单分区消费 + 处理完再提交。

    LAG 持续上涨。 说明消费速度 < 生产速度。处理顺序:

  • 看是哪个分区、哪个消费者慢:kafka-consumer-groups.sh –describe;
  • 消费者数是否已经等于分区数?没到就先加消费者;
  • 单条处理是否太慢(DB 慢查询、远程调用)?优化业务或批量处理;
  • 分区数是否成为瓶颈?评估后加分区(注意 key 顺序影响);
  • 临时扩容消费能力,或先降级非核心业务。
  • Rebalance 频繁。 常见触发:

    • 消费者处理太慢,超过 max.poll.interval.ms(默认 5 分钟);
    • 心跳超时(session.timeout.ms,默认 45 秒);
    • 滚动发布频繁上下线;
    • 消费者 GC 停顿过长。

    缓解:

    • 减小每次 max.poll.records,让单次处理更快返回;
    • 调大 max.poll.interval.ms(要匹配真实处理时间);
    • 用静态成员 group.instance.id,滚动重启时减少不必要的 rebalance;
    • 用 Cooperative Sticky Assignor(默认已较优)。

    磁盘满。

    # 看 Docker 整体占用
    docker system df

    # 如果用的是 1.6 节的 full 版 compose,数据目录是:
    docker exec –it kafka du –sh /var/lib/kafka/data

    最简版 compose 没有挂数据卷,数据目录在容器内的临时路径,看 docker system df 就够了。

    处理:

    • 调小 topic 的 retention.ms / retention.bytes;
    • 清理无用 topic;
    • 生产环境给 Kafka 独立数据盘,并监控磁盘水位;
    • 不要手动删数据目录里的文件。

    WSL / Docker 相关。

    • Docker Desktop 占用内存大:Settings → Resources 限制 WSL 内存;
    • WSL 磁盘只增不减:wsl –manage <distro> –set-sparse true 或定期 compact;
    • 时间不同步会导致 SSL/认证异常,重启 WSL 即可。

    6.2 面试高频题(附答题要点)

    1. Kafka 为什么快?

    顺序写磁盘、页缓存、零拷贝(sendfile)、批量发送与压缩、分区并行。答题时别只说「因为快」,要说清每个机制解决什么问题。

    2. Topic、Partition、Offset 的关系?

    Topic 是逻辑分类,Partition 是物理分片(有序追加日志),Offset 是消息在分区内的唯一递增编号。分区是并行和扩展的单位。

    3. 副本和 ISR 是什么?

    每个分区有多个副本,一主多从。Leader 提供读写,Follower 同步。ISR 是与 leader 保持同步的副本集合。Leader 挂掉后从 ISR 中选新 leader。

    4. acks=all 就一定不丢吗?

    不一定。还要看 min.insync.replicas:如果 ISR 只剩 leader 且 min.insync.replicas=1,写入仍成功,leader 挂就丢。生产建议 RF=3、min.insync.replicas=2、acks=all,并禁用 unclean leader election(现代版本默认已禁用)。

    5. 如何保证消息不丢?

    生产端:acks=all + 重试 + 处理发送失败;broker:RF=3 + min.insync.replicas=2 + 合理刷盘;消费端:处理成功后再提交 offset;保留时间足够长。

    6. 如何保证不重复消费?

    Kafka 只能做到 at-least-once,重复不可避免。业务幂等:唯一索引、状态机、去重表、Redis SETNX。追求 exactly-once 时用 Kafka 事务 + read_committed,但只覆盖 Kafka 内部链路。

    7. 如何保证顺序?

    同一业务 key 进同一分区 + 单分区内单线程消费 + 处理完再提交。全局有序只能单分区,代价是吞吐。

    8. 消费组和 rebalance?

    同一消费组内一个分区只分给一个消费者,不同组独立。消费者上下线、心跳超时、分区变化会触发 rebalance,期间消费暂停。要减少 rebalance:控制处理时长、静态成员、合理超时。

    9. Offset 存在哪?什么时候提交?

    存在内部 topic __consumer_offsets。原生客户端默认每 5 秒自动提交;Spring Kafka 默认一批处理完后提交(ack-mode=batch)。提交的是「下一条要读的位置」。

    10. 消费者是推还是拉?为什么?

    拉(pull)。消费者能控制消费速率,避免被 broker 压垮;broker 不用维护每个消费者的状态;配合长轮询兼顾实时性。

    11. 分区数怎么定?

    按目标吞吐 / 单分区吞吐估算,再考虑消费者并行度上限。不是越多越好:分区多会增加元数据、文件句柄、rebalance 和端到端延迟。生产常见每 topic 6~48 个,创建时留一点余量。

    12. Kafka 4.0 为什么移除 ZooKeeper?

    KRaft 让 Kafka 自己管理元数据,架构更简单:少一个组件、部署和运维更轻、controller 故障恢复更快、支持更大规模集群。4.0 起 ZK 模式彻底删除。

    13. 消息在磁盘上怎么存?

    每个分区对应一个目录,内部按 segment 切分(默认 1GB),每个 segment 有 .log 数据文件和 .index / .timeindex 索引文件。按 offset 二分查找索引,再顺序读日志。

    14. 零拷贝是什么?

    传统发送要经过「磁盘 → 内核缓冲区 → 用户缓冲区 → socket 缓冲区 → 网卡」。零拷贝用 sendfile 让数据在内核态直接从页缓存送到网卡,减少两次拷贝和用户态/内核态切换。

    15. 延迟队列怎么用 Kafka 实现?

    Kafka 没有原生延迟消息。常见做法:按延迟级别建多个 topic(1s/5s/30s/1m…),消息先投到对应延迟 topic,由定时任务到点后转发到真实 topic;或者用时间轮 + 外部调度。复杂度高,简单场景直接用 RabbitMQ/RocketMQ 更合适。

    16. 消息积压怎么处理?

    先定位瓶颈(消费者数量、单条处理耗时、分区数),再:加到分区数个消费者 → 优化处理逻辑/批量写 → 临时扩容 → 极端情况下新建大分区 topic 做临时转发。事后补监控和容量规划。

    6.3 把 Kafka 接进真实项目的检查清单

    • 明确用途:是任务队列还是事件流?前者可能不该用 Kafka
    • topic 命名规范:环境.业务域.实体.事件,如 prod.order.order.created
    • key 怎么选:要顺序就用业务主键;不要用随机值(会打散顺序)
    • 分区数:按吞吐和消费者数估算,留余量
    • 副本与 acks:生产至少 RF=3、min.insync.replicas=2、acks=all
    • 消费幂等:唯一键/状态机/去重表
    • 失败处理:重试策略 + 死信 topic + 告警
    • 监控:LAG、消费速率、失败率、under-replicated 分区、磁盘水位
    • 序列化:统一 JSON/Avro/Protobuf,带 schema 版本
    • 安全:SASL/SSL、ACL,别裸奔
    • 保留策略:按业务需要设置,别无脑 7 天
    • 容量:日流量 × 保留天数 × 副本数 × 1.3

    6.4 学习资源

    官方(最权威,建议先看)

    • Kafka 文档:https://kafka.apache.org/documentation/
    • Quickstart:https://kafka.apache.org/quickstart
    • Kafka 4.4 文档:https://kafka.apache.org/44/
    • 官方 Docker 镜像说明:https://kafka.apache.org/44/getting-started/docker/

    书

    • 《Kafka 权威指南》(Kafka: The Definitive Guide)第 2 版。概念部分仍然经典;注意它写于 3.x 时代,4.x 已移除 ZooKeeper,看到 ZK 相关章节可以跳过。
    • 《深入理解 Kafka:核心设计与实践原理》,偏源码和设计,适合进阶。

    社区

    • Confluent 开发者中心:https://developer.confluent.io/
    • awesome-kafka 资源列表:https://github.com/conduktor/awesome-kafka
    • Kafka Summit 的公开演讲(YouTube 可搜)

    学习建议

  • 视频只能帮你建立第一印象,一定要自己敲命令、看日志、改配置;
  • 每学一个概念,就用 CLI 验证一次(比如「分区内有序」,就发带 key 的消息看分区);
  • 遇到报错先看 broker 日志和客户端日志,Kafka 的报错信息通常很具体;
  • 不要一上来啃源码,先把「用对」和「排错」练熟。

  • 最后:

  • Kafka 是分区日志,不是读完就删的队列。 offset 决定你读到哪,保留策略决定消息活多久。
  • 并行靠分区,顺序靠 key,可靠靠副本 + acks,正确性靠业务幂等。
  • 选型看场景:高吞吐、可重放、事件流选 Kafka;灵活路由、低延迟、任务队列选 RabbitMQ。
  • 到这里,Kafka 的入门部分就完整了。剩下的就是动手:把容器跑起来,改改分区数,制造一次 LAG,重置一次 offset,再故意让消费者挂掉看看重复消费。

    赞(0)
    未经允许不得转载:171主机测评 » 【Kafka】零基础学习教程
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址