Kafka 生产环境实战

📖 前置阅读:本文是 Kafka 系列的终篇,假设读者已经掌握前五篇的全部内容(核心架构、SpringBoot 集成、Producer 深入、Consumer 深入、Kafka Streams)。

一、⚡ 问题切入:单节点 Docker 的瓶颈

第一篇搭的单节点 KRaft Kafka(一个 Controller + Broker 合体进程)只能用来学习——生产环境中:

单点后果
唯一的 Broker 挂了所有消息不可发送/消费——整个 Kafka 瘫痪
无副本Broker 磁盘损坏 → 消息永久丢失
JVM 内存不足Full GC 频繁 → 消息延迟抖动 → Producer 超时失败
磁盘写满Partition 无法写入 → Producer 阻塞或报错

生产最低配:3 台 Broker + 3 台 Controller(或 3 台合体节点,controller + broker 混合模式),每个 Topic 至少 2 副本。

二、KRaft 三节点集群搭建

2.1 架构设计

第一篇用的 KRaft 模式是单节点(Controller 和 Broker 运行在一个进程中)。生产环境拆开:

KRaft Controller Quorum(3 台——负责元数据管理和选举):
    Controller-1  (node.id=1)
    Controller-2  (node.id=2)
    Controller-3  (node.id=3)

Broker 集群(3 台——负责数据存储和分发):
    Broker-1  (node.id=11)
    Broker-2  (node.id=12)
    Broker-3  (node.id=13)

Topic 副本分布(replication.factor=3):
    Partition-0: Leader=Broker-1, Follower=Broker-2, Broker-3
    Partition-1: Leader=Broker-2, Follower=Broker-3, Broker-1
    Partition-2: Leader=Broker-3, Follower=Broker-1, Broker-2

为什么 Controller 最少 3 台? KRaft 使用 Raft 共识算法——3 台 Controller 可以容忍 1 台宕机((3-1)/2 = 1 台)。5 台可以容忍 2 台。

2.2 Docker Compose 三节点

# docker-compose-kraft.yml
version: '3.8'
services:
  # ========== Controller 节点 ==========
  controller-1:
    image: apache/kafka:3.7.0
    container_name: kafka-controller-1
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: controller
      KAFKA_LISTENERS: CONTROLLER://0.0.0.0:29093
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@controller-1:29093,2@controller-2:29093,3@controller-3:29093
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
    ports:
      - "29093:29093"
    volumes:
      - ./data/controller-1:/var/lib/kafka/data

  controller-2:
    image: apache/kafka:3.7.0
    container_name: kafka-controller-2
    environment:
      KAFKA_NODE_ID: 2
      KAFKA_PROCESS_ROLES: controller
      KAFKA_LISTENERS: CONTROLLER://0.0.0.0:29093
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@controller-1:29093,2@controller-2:29093,3@controller-3:29093
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
    ports:
      - "29094:29093"
    volumes:
      - ./data/controller-2:/var/lib/kafka/data

  controller-3:
    image: apache/kafka:3.7.0
    container_name: kafka-controller-3
    environment:
      KAFKA_NODE_ID: 3
      KAFKA_PROCESS_ROLES: controller
      KAFKA_LISTENERS: CONTROLLER://0.0.0.0:29093
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@controller-1:29093,2@controller-2:29093,3@controller-3:29093
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
    ports:
      - "29095:29093"
    volumes:
      - ./data/controller-3:/var/lib/kafka/data

  # ========== Broker 节点 ==========
  broker-1:
    image: apache/kafka:3.7.0
    container_name: kafka-broker-1
    depends_on:
      - controller-1
      - controller-2
      - controller-3
    environment:
      KAFKA_NODE_ID: 11
      KAFKA_PROCESS_ROLES: broker
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@controller-1:29093,2@controller-2:29093,3@controller-3:29093
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_LOG_DIRS: /var/lib/kafka/data
      KAFKA_NUM_PARTITIONS: 6           # 默认 Partition 数
      KAFKA_DEFAULT_REPLICATION_FACTOR: 3  # 默认副本数
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3  # __consumer_offsets 的副本数
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 2
      KAFKA_MIN_INSYNC_REPLICAS: 2      # 最少 ISR 副本数
      KAFKA_LOG_RETENTION_HOURS: 72      # 消息保留 72 小时
      KAFKA_LOG_SEGMENT_BYTES: 1073741824  # 1GB Segment
      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
      KAFKA_HEAP_OPTS: "-Xms4g -Xmx4g"
    ports:
      - "9092:9092"
    volumes:
      - ./data/broker-1:/var/lib/kafka/data

  broker-2:
    image: apache/kafka:3.7.0
    container_name: kafka-broker-2
    depends_on:
      - controller-1
      - controller-2
      - controller-3
    environment:
      KAFKA_NODE_ID: 12
      KAFKA_PROCESS_ROLES: broker
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9093
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9093
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@controller-1:29093,2@controller-2:29093,3@controller-3:29093
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_LOG_DIRS: /var/lib/kafka/data
      KAFKA_NUM_PARTITIONS: 6
      KAFKA_DEFAULT_REPLICATION_FACTOR: 3
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 2
      KAFKA_MIN_INSYNC_REPLICAS: 2
      KAFKA_LOG_RETENTION_HOURS: 72
      KAFKA_LOG_SEGMENT_BYTES: 1073741824
      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
      KAFKA_HEAP_OPTS: "-Xms4g -Xmx4g"
    ports:
      - "9093:9093"
    volumes:
      - ./data/broker-2:/var/lib/kafka/data

  broker-3:
    image: apache/kafka:3.7.0
    container_name: kafka-broker-3
    depends_on:
      - controller-1
      - controller-2
      - controller-3
    environment:
      KAFKA_NODE_ID: 13
      KAFKA_PROCESS_ROLES: broker
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9094
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9094
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@controller-1:29093,2@controller-2:29093,3@controller-3:29093
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_LOG_DIRS: /var/lib/kafka/data
      KAFKA_NUM_PARTITIONS: 6
      KAFKA_DEFAULT_REPLICATION_FACTOR: 3
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 2
      KAFKA_MIN_INSYNC_REPLICAS: 2
      KAFKA_LOG_RETENTION_HOURS: 72
      KAFKA_LOG_SEGMENT_BYTES: 1073741824
      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
      KAFKA_HEAP_OPTS: "-Xms4g -Xmx4g"
    ports:
      - "9094:9094"
    volumes:
      - ./data/broker-3:/var/lib/kafka/data

关键配置解释

配置含义原因
KAFKA_PROCESS_ROLES节点角色controller / broker / controller,broker(混合)生产建议分离
KAFKA_CONTROLLER_QUORUM_VOTERS参与 Raft 选举的节点列表1@host1:29093,2@host2:29093,...三个节点名字和端口必须完全一致——每个节点上配置相同
num.partitions新 Topic 默认 Partition 数6不小于预期消费者实例数
default.replication.factor新 Topic 默认副本数3容忍 2 台 Broker 宕机
min.insync.replicas最少 ISR 副本数2配合 acks=all——至少 2 个副本确认
offsets.topic.replication.factor__consumer_offsets 的副本数3Offset 也是数据——不能丢
log.retention.hours消息保留时间72根据业务需要和磁盘容量调整
auto.create.topics.enable自动创建 Topicfalse生产环境关闭——Topic 必须手动创建,防止业务代码写错 Topic 名

2.3 启动与验证

# 1. 格式化存储目录(每个节点都需格式化——和第一篇单节点一样)
docker run --rm -v $(pwd)/data/controller-1:/var/lib/kafka/data \
  apache/kafka:3.7.0 \
  /opt/kafka/bin/kafka-storage.sh format \
  --config /etc/kafka/server.properties \
  --cluster-id $(uuidgen)
# 重复 controller-2, controller-3, broker-1, broker-2, broker-3

# 2. 启动集群
docker compose -f docker-compose-kraft.yml up -d

# 3. 验证 Controller Quorum
docker exec kafka-controller-1 \
  /opt/kafka/bin/kafka-metadata-quorum.sh --snapshot \
  --bootstrap-server localhost:9092 describe
# 预期输出:3 voters, LeaderId=1

# 4. 验证 Broker 已注册
docker exec kafka-broker-1 \
  /opt/kafka/bin/kafka-metadata-quorum.sh --snapshot \
  --bootstrap-server localhost:9092 describe \
  | grep -E "Broker-11|Broker-12|Broker-13"

# 5. 创建测试 Topic
docker exec kafka-broker-1 \
  /opt/kafka/bin/kafka-topics.sh --create \
  --topic test-topic \
  --bootstrap-server localhost:9092 \
  --partitions 6 \
  --replication-factor 3

# 6. 验证 Topic 的副本分布
docker exec kafka-broker-1 \
  /opt/kafka/bin/kafka-topics.sh --describe \
  --topic test-topic \
  --bootstrap-server localhost:9092

# 预期输出(6 个 Partition × 3 个副本):
# Partition: 0    Leader: 11   Replicas: 11,12,13   Isr: 11,12,13
# Partition: 1    Leader: 12   Replicas: 12,13,11   Isr: 12,13,11
# ...

⚠️ 新手提示:KAFKA_CONTROLLER_QUORUM_VOTERS 中每个节点的名字(如 controller-1)必须能被其他节点通过 Docker 网络解析。如果 Broker 和 Controller 不在同一台宿主机上,需要用真实 IP 或 DNS。localhost 在这里不适用——Docker 容器内的 localhost 是容器自己。

2.4 手动创建 Topic

# 创建订单 Topic:6 个 Partition,3 个副本
docker exec kafka-broker-1 \
  /opt/kafka/bin/kafka-topics.sh --create \
  --topic order-topic \
  --bootstrap-server localhost:9092 \
  --partitions 6 \
  --replication-factor 3 \
  --config min.insync.replicas=2 \
  --config retention.ms=259200000   # 72 小时

# 创建紧凑 Topic(维表)
docker exec kafka-broker-1 \
  /opt/kafka/bin/kafka-topics.sh --create \
  --topic product-info-topic \
  --bootstrap-server localhost:9092 \
  --partitions 3 \
  --replication-factor 3 \
  --config cleanup.policy=compact

三、性能调优

3.1 Broker JVM 调优

Kafka Broker 是 Scala/Java 进程——GC 直接决定消息延迟的稳定性:

# Broker JVM 参数
KAFKA_HEAP_OPTS="-Xms6g -Xmx6g \
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=20 \
  -XX:InitiatingHeapOccupancyPercent=35 \
  -XX:G1HeapRegionSize=16m \
  -XX:MetaspaceSize=96m \
  -XX:MinMetaspaceFreeRatio=50 \
  -XX:MaxMetaspaceFreeRatio=80"
参数含义建议值
-Xms6g -Xmx6g堆内存——生产至少 4G4G ~ 8G(超过 8G 收益递减)
-XX:+UseG1GCG1 垃圾回收器——低延迟必选
-XX:MaxGCPauseMillis=20目标 GC 停顿 < 20ms20 ~ 50
-XX:InitiatingHeapOccupancyPercent=35堆使用 35% 开始并发标记35 ~ 45

Kafka 对堆大小不敏感——它的高性能来自 PageCache(OS 管理的磁盘缓存),不是堆。堆主要存 Producer 的缓冲区和 Consumer Group 元数据。所以 不要给 Kafka 64G 堆——留给 OS PageCache 效果更好。

3.2 OS 调优

# 1. 虚拟内存——避免 PageCache 过于激进
sysctl -w vm.swappiness=1         # 尽量不用 swap——PageCache 比 swap 快
sysctl -w vm.dirty_ratio=10       # 脏页比例上限
sysctl -w vm.dirty_background_ratio=5  # 后台刷盘比例

# 2. 文件句柄——Kafka 大量使用文件系统(每个 Segment 一个 fd)
ulimit -n 100000

# 3. 文件系统——推荐 XFS(ext4 也可以)
# mount -o noatime /dev/sdb1 /data/kafka
参数含义为什么重要
vm.swappiness=1尽量不用 swapKafka 的数据在 PageCache 里——swap 到磁盘等于数据读写全部变慢 100 倍
vm.dirty_ratio脏页占内存上限控制写缓冲——不会因为刷盘阻塞读写
ulimit -n 100000最大文件句柄数每个 Segment 文件一个 fd + 网络连接——128K 是常见配置
noatime不记录文件访问时间每次读文件不更新 atime——减少磁盘写入

3.3 Broker 端配置调优

# ===== 网络线程 =====
num.network.threads=8          # 处理网络请求的线程数
num.io.threads=16              # 处理磁盘 I/O 的线程数

# ===== PageCache 相关 =====
log.flush.interval.messages=9223372036854775807  # 不要按消息数刷盘——交给 OS
log.flush.interval.ms=9223372036854775807
# Kafka 的持久化靠副本,不靠刷盘——依赖 OS PageCache

# ===== 副本同步 =====
num.replica.fetchers=4         # 复制线程数——增加可以加速副本同步

# ===== 日志清理 =====
log.cleaner.threads=2          # Log Compaction 线程数
log.segment.bytes=1073741824   # 1GB——Segment 大小
log.segment.ms=604800000       # 7 天——Segment 滚动时间

# ===== Socket =====
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600  # 100MB——最大请求大小

3.4 Producer 端调优(复习 + 生产建议)

spring:
  kafka:
    bootstrap-servers: localhost:9092,localhost:9093,localhost:9094
    producer:
      acks: all
      retries: 2147483647        # Integer.MAX_VALUE——依赖 delivery.timeout.ms 控制
      batch-size: 131072         # 128KB
      linger-ms: 5               # 低延迟场景:5ms;高吞吐场景:20ms
      compression-type: lz4
      properties:
        enable.idempotence: true
        max.in.flight.requests.per.connection: 5
        delivery.timeout.ms: 120000   # 2 分钟内完成发送(含重试)
        request.timeout.ms: 30000     # 单次请求超时 30s

3.5 Consumer 端调优(复习 + 生产建议)

spring:
  kafka:
    bootstrap-servers: localhost:9092,localhost:9093,localhost:9094
    consumer:
      group-id: order-consumer-group
      enable-auto-commit: false
      max-poll-records: 100       # 不要太激进——500 可能超 max.poll.interval.ms
      auto-offset-reset: earliest
      properties:
        partition.assignment.strategy:
          org.apache.kafka.clients.consumer.CooperativeStickyAssignor
        session.timeout.ms: 45000
        max.poll.interval.ms: 600000  # 10 分钟——留足处理时间
    listener:
      ack-mode: manual_immediate

四、监控

4.1 Kafka Exporter + Prometheus + Grafana

# 在 docker-compose-kraft.yml 中加入 Prometheus Exporter
  kafka-exporter:
    image: danielqsj/kafka-exporter:latest
    container_name: kafka-exporter
    command:
      - "--kafka.server=broker-1:9092"
      - "--kafka.server=broker-2:9093"
      - "--kafka.server=broker-3:9094"
    ports:
      - "9308:9308"

  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin123

4.2 必须盯住的五个指标

指标Prometheus 查询告警阈值为什么重要
Consumer Lagkafka_consumergroup_lagLag > 预期消息量的 2 倍最关键的指标——消费跟不上生产
Under Replicated Partitionskafka_server_replicamanager_underreplicatedpartitions> 0有 Partition 副本数不足——可靠性下降
Active Controller Countkafka_controller_kafkacontroller_activecontrollercount≠ 1没有 Controller 或多于一个——集群异常
Offline Partitionskafka_controller_kafkacontroller_offlinepartitionscount> 0有 Partition 无 Leader——那些 Partition 不可读写
Disk UsageNode Exporter node_filesystem_avail_bytes> 80%磁盘满了就写不进去了

4.3 命令行监控工具

# Broker 指标——每个 Topic 的 TPS
docker exec kafka-broker-1 \
  /opt/kafka/bin/kafka-broker-api-versions.sh \
  --bootstrap-server localhost:9092

# ConsumerGroup 状态——Lag 查询(最重要)
docker exec kafka-broker-1 \
  /opt/kafka/bin/kafka-consumer-groups.sh \
  --bootstrap-server localhost:9092 \
  --group order-consumer-group \
  --describe

# Topic 级别——Partition 分布和 ISR 状态
docker exec kafka-broker-1 \
  /opt/kafka/bin/kafka-topics.sh \
  --bootstrap-server localhost:9092 \
  --describe \
  --topic order-topic

# 查看 Topic 消息量
docker exec kafka-broker-1 \
  /opt/kafka/bin/kafka-run-class.sh \
  kafka.tools.GetOffsetShell \
  --bootstrap-server localhost:9092 \
  --topic order-topic

五、常见生产故障

故障现象排查
消费积压(Lag)kafka-consumer-groups --describe 的 LAG 持续增长① Consumer 是否在运行 kafka-consumer-groups --describeconcurrency 是否小于 Partition 数 ③ 消费逻辑是否有慢调用 ④ 增加 Partition 数 + Consumer 实例
Producer 发送超时org.apache.kafka.common.errors.TimeoutExceptionbootstrap.servers 中是否有可达的 Broker ② delivery.timeout.ms 是否够 ③ max.block.ms 是否太短
Under Replicatedkafka-topics --describe 中 ISR < Replicas① Broker 是否有宕机 ② 网络是否稳定(Broker 间通信) ③ num.replica.fetchers 是否太少
Offline PartitionProducer 发送时报 NOT_LEADER_OR_FOLLOWER① 查看哪些 Broker 挂了 ② 查看 Controller 日志 ③ unclean.leader.election.enable=false 确保不丢数据(但可能牺牲可用性)
磁盘写满Broker 日志 LogDirFailureChannellog.retention.hours 调小 ② 检查 Log Compaction 是否生效 ③ 增加磁盘或清理旧数据
KRaft 脑裂Controller 日志 NotLeaderException① 检查 controller.quorum.voters 配置是否一致 ② 检查网络是否正常——Controller 之间通信不可达

六、上线前 10 项检查清单

#检查项配置/命令
1Controller Quorum ≥ 3 台docker-compose-kraft.yml 中 3 个 controller 服务
2Broker ≥ 3 台docker-compose-kraft.yml 中 3 个 broker 服务
3auto.create.topics.enable=falseBroker 配置——Topic 必须手动创建
4关键 Topic 的 replication.factor=3kafka-topics --describe 确认
5min.insync.replicas=2Broker 配置——配合 acks=all
6Producer 开启幂等enable.idempotence=true
7Consumer 使用 CooperativeStickypartition.assignment.strategy=CooperativeStickyAssignor
8Consumer 手动提交 Offsetenable-auto-commit=false + ack-mode=manual_immediate
9接入 Prometheus + Grafana 或至少盯住 LagKafka Exporter + Prometheus + Grafana Dashboard
10Broker JVM 堆 ≥ 4G,OS 文件句柄 ≥ 100KKAFKA_HEAP_OPTS="-Xms4g -Xmx4g" + ulimit -n 100000

七、Kafka vs RocketMQ vs RabbitMQ 最终选型

六篇 RabbitMQ + 六篇 RocketMQ + 六篇 Kafka。实际选型时:

场景选谁理由
需要消息重放、事件溯源Kafka核心设计目标——消费后消息不删除
需要海量吞吐(> 100 万 msg/s)Kafka零拷贝 + 顺序读写 + 分区并行
需要流处理(聚合、Join、窗口)KafkaKafka Streams 原生库——不依赖外部系统
需要事务消息(下单+扣库存+通知)RocketMQ半消息 + 回查——原生灵活的事务模型
需要灵活路由(一个消息按多种规则分发)RabbitMQExchange + Binding 模型最灵活
小团队,运维简单RabbitMQ单 Docker 即可,管理界面直观
Java 技术栈,需要深度定制RocketMQ全部 Java 实现,二次开发方便
云厂商托管阿里云 → RocketMQ;AWS → MSK (Kafka)云厂商决定了用什么

🎯 总结

Kafka 的生产部署核心在四点:

  1. KRaft 高可用架构:至少 3 台 Controller(Raft 仲裁) + 3 台 Broker。process.roles 生产级建议分离,但混合模式也可以。controller.quorum.voters 在所有节点上配置必须一致。

  2. 核心可靠性配置replication.factor=3 + min.insync.replicas=2 + Producer acks=all + Producer enable.idempotence=true。这四件套是"不丢消息"的底线。

  3. 监控盯住五个指标:Consumer Lag(最重要)、Under Replicated Partitions、Active Controller Count、Offline Partitions、Disk Usage。其中 Lag 是消费端健康度的晴雨表。

  4. 调优不是调 JVM:Kafka 的性能核心在 OS PageCache——留给 OS 足够内存比给 JVM 64G 堆更有效。优先调 batch.sizelinger.mscompression.typeconcurrency——这些参数的收益远超 JVM 参数调整。


📖 系列总览

Kafka 六篇系列到此结束:

#核心收获
1核心架构与日志存储模型分布式提交日志 vs 消息队列的本质差异、Topic-Partition 模型、零拷贝 sendfile、KRaft 架构
2SpringBoot 全操作指南KafkaTemplate 三模式发送、@KafkaListener 消费、JSON 序列化全链路、手动 Offset 提交
3Producer 深入:分区、ACK 与幂等DefaultPartitioner 哈希策略、acks=0/1/all 三级可靠性、幂等生产者 PID+Seq、事务跨 Topic 原子写入
4Consumer 深入:位移管理与 RebalanceOffset 自动/手动提交、__consumer_offsets 内部 Topic、Rebalance 触发与策略、CooperativeSticky、多线程消费
5Kafka Streams 与高级特性KStream vs KTable、有状态聚合与窗口、流-表 Join、Log Compaction、exactly_once_v2
6生产环境部署与调优KRaft 三 Controller + 三 Broker 集群、JVM/OS 调优、Prometheus 监控、10 项检查清单

建议从 1 到 6 顺序阅读,每篇以前一篇为前提。学完这六篇,从核心概念到生产部署的全链路都覆盖了。