Kafka实战

Kafka 实战

适合人群:后端开发工程师、架构师、大数据工程师

本文从 Kafka 核心概念讲起,结合 Java 代码实战,覆盖生产者/消费者开发、消息可靠性、Spring Boot 集成、Kafka Streams、性能调优等进阶内容,带你从零掌握 Kafka 这把分布式消息队列利器。

目录

  • [1. Kafka 简介](#1. Kafka 简介)
  • [2. 核心概念与架构](#2. 核心概念与架构)
  • [3. 环境搭建](#3. 环境搭建)
  • [4. Java 客户端依赖与配置](#4. Java 客户端依赖与配置)
  • [5. 生产者实战](#5. 生产者实战)
  • [6. 消费者实战](#6. 消费者实战)
  • [7. 分区策略与消息路由](#7. 分区策略与消息路由)
  • [8. 序列化与自定义序列化器](#8. 序列化与自定义序列化器)
  • [9. 消费者组与 Rebalance](#9. 消费者组与 Rebalance)
  • [10. 消息可靠性保障](#10. 消息可靠性保障)
  • [11. Spring Boot 集成 Kafka](#11. Spring Boot 集成 Kafka)
  • [12. Kafka Streams 实战](#12. Kafka Streams 实战)
  • [13. 性能调优](#13. 性能调优)
  • [14. 监控与运维](#14. 监控与运维)
  • [15. 最佳实践与常见问题](#15. 最佳实践与常见问题)

1. Kafka 简介

1.1 什么是 Kafka

Apache Kafka 最初由 LinkedIn 开发,后捐赠给 Apache 基金会,现为顶级开源项目。它是一个分布式流处理平台 ,本质上是分布式发布-订阅消息系统

1.2 Kafka 的核心特点

  • 高吞吐量:单集群每秒可处理百万级消息,普通硬件即可达到 GB/s 级吞吐
  • 低延迟:毫秒级消息延迟
  • 可扩展性:支持水平扩展,可轻松扩展到数百个 Broker
  • 持久化:消息持久化到磁盘,支持多副本,数据不丢失
  • 高可用:支持副本机制和故障自动转移
  • 多生产者/多消费者:多个生产者写入,多个消费者独立读取
  • 顺序保证:单分区内消息有序

1.3 Kafka 的应用场景

场景 说明 示例
消息队列 异步解耦、削峰填谷 订单系统 → 消息队列 → 库存/积分/通知系统
日志收集 统一收集分布式系统日志 各服务日志 → Kafka → ES/HDFS
流处理 实时数据处理 实时计算 UV/PV、实时风控
事件溯源 记录状态变更事件 CQRS 架构中的事件存储
数据管道 系统间数据同步 MySQL CDC → Kafka → 数据仓库
指标监控 系统指标采集上报 服务器指标 → Kafka → Grafana

1.4 Kafka vs 其他消息队列

特性 Kafka RabbitMQ RocketMQ Pulsar
吞吐量 极高(百万/s) 中等(万级/s) 高(十万/s) 极高
延迟 毫秒级 微秒级 毫秒级 毫秒级
持久化 磁盘 + 副本 可选 磁盘 + 副本 分离存储
顺序保证 分区内有序 队列内有序 分区内有序 分区内有序
事务 支持 支持 支持 支持
消息回溯 支持(offset) 不支持 支持 支持
适用场景 日志/流处理/大数据 业务消息/低延迟 金融/电商业务 云原生/多租户

2. 核心概念与架构

2.1 整体架构

复制代码
                          ┌─────────────────────────────────────────┐
                          │              ZooKeeper / KRaft           │
                          │         (集群协调、元数据管理)            │
                          └──────────┬──────────────────┬───────────┘
                                     │                  │
┌──────────┐                  ┌──────┴──────┐   ┌───────┴─────┐   ┌──────────┐
│ Producer  │───写入消息──────│   Broker 1   │   │   Broker 2   │   │ Broker 3 │
│ (生产者)  │                  │  ┌────────┐ │   │ ┌────────┐ │   │┌────────┐│
└──────────┘                  │  │Topic-A  │ │   │ │Topic-A  │ │   ││Topic-A ││
                              │  │P0(Leader)│ │   │ │P1(Leader)│ │   ││P2(Leader)│
┌──────────┐                  │  │P2(Follower)│  │ │P0(Follower)│  ││P1(Follower)│
│ Consumer  │                  │  └────────┘ │   │ └────────┘ │   │└────────┘│
│  Group 1  │───读取消息──────│             │   │            │   │          │
└──────────┘                  └─────────────┘   └────────────┘   └──────────┘
┌──────────┐
│ Consumer  │
│  Group 2  │───独立消费──────→ (每个Group独立消费同一Topic的全量消息)
└──────────┘

2.2 核心概念详解

2.2.1 Broker(代理)

Kafka 集群中的一台服务器就是一个 Broker。多个 Broker 组成集群,每个 Broker 负责存储部分分区数据。

2.2.2 Topic(主题)

消息的逻辑分类,类似于数据库中的表。生产者将消息发送到 Topic,消费者从 Topic 订阅消息。

复制代码
Topic: order-events
├── Partition 0: [msg0, msg1, msg4, msg7, ...]
├── Partition 1: [msg2, msg3, msg5, msg8, ...]
└── Partition 2: [msg6, msg9, msg10, ...]
2.2.3 Partition(分区)

Topic 物理上的分片,是 Kafka 并发处理的基本单位。

  • 每个 Partition 是一个有序的、不可变的追加日志

  • 每个 Partition 有一个唯一 Leader 处理读写

  • Follower 从 Leader 同步数据,提供冗余

  • 分区数决定了最大并行度

    Partition 内部结构:

    ┌─────────────────────────────────────────────────────────┐
    │ Partition (有序日志) │
    │ │
    │ Offset: 0 1 2 3 4 5 │
    │ ┌─────┬─────┬─────┬─────┬─────┬─────┐ │
    │ Message: │msg0 │msg1 │msg2 │msg3 │msg4 │msg5 │ ... │
    │ └─────┴─────┴─────┴─────┴─────┴─────┘ │
    │ ↑ │
    │ Consumer Offset (消费位移) │
    │ │
    │ Log Segment: [msg0~msg2] [msg3~msg5] [msg6~...] │
    │ (按大小/时间切分,旧Segment可被删除) │
    └─────────────────────────────────────────────────────────┘

2.2.4 Offset(位移)

消息在 Partition 中的唯一编号,从 0 开始单调递增。消费者通过 Offset 记录消费位置。

2.2.5 Replica(副本)

每个 Partition 可以有多个副本,提供数据冗余和高可用。

概念 说明
Leader Replica 处理所有读写请求的主副本
Follower Replica 从 Leader 同步数据的从副本
ISR (In-Sync Replicas) 与 Leader 保持同步的副本集合
OSR (Out-of-Sync Replicas) 落后于 Leader 的副本集合
AR (All Replicas) ISR + OSR = 所有副本
2.2.6 Producer(生产者)

向 Kafka Topic 发送消息的客户端。

2.2.7 Consumer(消费者)

从 Kafka Topic 读取消息的客户端。

2.2.8 Consumer Group(消费者组)

消费者组是 Kafka 实现负载均衡广播的核心机制:

  • 同一组内:一个分区只能被一个消费者消费(负载均衡)

  • 不同组间:各自独立消费全量消息(广播)

    Topic: order-events (3个分区)

    消费者组 A:
    Consumer A1 ←── Partition 0
    Consumer A2 ←── Partition 1
    Consumer A3 ←── Partition 2

    消费者组 B:
    Consumer B1 ←── Partition 0, 1, 2 (一个消费者消费所有分区)

    消费者组 C:
    Consumer C1 ←── Partition 0
    Consumer C2 ←── Partition 1, 2
    Consumer C3 ←── (空闲,分区不够分)

💡 关键规则:一个消费者组中的消费者数量不应超过分区数,否则多余的消费者会空闲。

2.2.9 ZooKeeper / KRaft

Kafka 依赖协调服务来管理集群元数据:

  • ZooKeeper 模式(传统):Kafka 2.x 及之前依赖外部 ZooKeeper
  • KRaft 模式(新架构):Kafka 3.x 引入,内置共识协议,摆脱 ZooKeeper 依赖

3. 环境搭建

3.1 Docker Compose 快速搭建

最快的方式是使用 Docker Compose 搭建本地 Kafka 环境。

docker-compose.yml:

yaml 复制代码
version: '3.8'

services:
  # KRaft 模式(无需 ZooKeeper)
  kafka:
    image: bitnami/kafka:3.7
    container_name: kafka
    ports:
      - "9092:9092"     # 外部访问
      - "9093:9093"     # Controller 通信
    environment:
      # KRaft 模式配置
      KAFKA_CFG_NODE_ID: 1
      KAFKA_CFG_PROCESS_ROLES: controller,broker
      KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
      KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
      KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
      KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_CFG_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      # 自动创建 Topic
      KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: "true"
      # 日志保留
      KAFKA_CFG_LOG_RETENTION_HOURS: 168
    volumes:
      - kafka_data:/bitnami/kafka

  # Kafka UI 管理界面(可选)
  kafka-ui:
    image: provectuslabs/kafka-ui:latest
    container_name: kafka-ui
    ports:
      - "8080:8080"
    environment:
      KAFKA_CLUSTERS_0_NAME: local
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
    depends_on:
      - kafka

volumes:
  kafka_data:
bash 复制代码
# 启动
docker-compose up -d

# 查看状态
docker-compose ps

# 访问 Kafka UI: http://localhost:8080

3.2 命令行操作 Topic

bash 复制代码
# 进入容器
docker exec -it kafka bash

# 创建 Topic(3个分区,2个副本)
kafka-topics.sh --create \
  --bootstrap-server localhost:9092 \
  --topic order-events \
  --partitions 3 \
  --replication-factor 1

# 查看 Topic 列表
kafka-topics.sh --list --bootstrap-server localhost:9092

# 查看 Topic 详情
kafka-topics.sh --describe --topic order-events --bootstrap-server localhost:9092

# 命令行生产者
kafka-console-producer.sh --topic order-events --bootstrap-server localhost:9092

# 命令行消费者
kafka-console-consumer.sh --topic order-events --from-beginning --bootstrap-server localhost:9092

# 消费者组消费
kafka-console-consumer.sh --topic order-events --group order-consumer-group --bootstrap-server localhost:9092

# 查看消费者组
kafka-consumer-groups.sh --list --bootstrap-server localhost:9092

# 查看消费者组位移
kafka-consumer-groups.sh --describe --group order-consumer-group --bootstrap-server localhost:9092

# 删除 Topic
kafka-topics.sh --delete --topic order-events --bootstrap-server localhost:9092

3.3 Topic 分区数选择

考量因素 建议
吞吐量需求 分区数 ≈ 目标吞吐量 / 单分区吞吐量
消费者数 分区数 ≥ 消费者组中的消费者数
Broker 数 单 Broker 分区数不宜过多(建议 < 4000)
实际经验 中小规模 6~12 分区,大规模 24~100 分区

⚠️ 分区数一旦确定不能减少,只能增加。增加分区会破坏 Key 的路由一致性,设计时需谨慎。


4. Java 客户端依赖与配置

4.1 Maven 依赖

xml 复制代码
<dependencies>
    <!-- Kafka 客户端 -->
    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
        <version>3.7.0</version>
    </dependency>

    <!-- JSON 处理 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.16.1</version>
    </dependency>

    <!-- SLF4J 日志 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>2.0.12</version>
    </dependency>
</dependencies>

4.2 核心配置参数详解

4.2.1 生产者核心参数
参数 默认值 说明
bootstrap.servers - Kafka Broker 地址列表
key.serializer - Key 序列化器
value.serializer - Value 序列化器
acks all 确认级别:0/1/all
retries 2147483647 重试次数
retry.backoff.ms 100 重试间隔
batch.size 16384 批次大小(字节)
linger.ms 0 批次等待时间(ms)
buffer.memory 33554432 发送缓冲区大小
compression.type none 压缩:none/gzip/snappy/lz4/zstd
max.in.flight.requests.per.connection 5 每连接未确认请求数
enable.idempotence true 幂等性
transactional.id - 事务ID
4.2.2 消费者核心参数
参数 默认值 说明
bootstrap.servers - Kafka Broker 地址列表
key.deserializer - Key 反序列化器
value.deserializer - Value 反序列化器
group.id - 消费者组ID
enable.auto.commit true 自动提交位移
auto.commit.interval.ms 5000 自动提交间隔
auto.offset.reset latest 无位移时策略:earliest/latest/none
max.poll.records 500 单次拉取最大记录数
max.poll.interval.ms 300000 两次poll最大间隔
session.timeout.ms 45000 会话超时时间
heartbeat.interval.ms 3000 心跳间隔
fetch.min.bytes 1 最小拉取字节数
isolation.level read_uncommitted 事务隔离级别

5. 生产者实战

5.1 基础生产者

java 复制代码
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;

public class BasicProducer {

    public static void main(String[] args) {
        // 1. 配置生产者
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        // 2. 创建生产者
        try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {

            // 3. 发送消息
            for (int i = 0; i < 10; i++) {
                String key = "order-" + i;
                String value = "订单内容 " + i;

                ProducerRecord<String, String> record =
                        new ProducerRecord<>("order-events", key, value);

                // 同步发送(阻塞等待结果)
                RecordMetadata metadata = producer.send(record).get();
                System.out.printf("发送成功: topic=%s, partition=%d, offset=%d, key=%s%n",
                        metadata.topic(), metadata.partition(), metadata.offset(), key);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5.2 异步发送(推荐)

异步发送性能更高,通过回调处理发送结果:

java 复制代码
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;
import java.util.concurrent.TimeUnit;

public class AsyncProducer {

    public static void main(String[] args) throws InterruptedException {
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        // 开启幂等性(防止重复消息)
        props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
        // 批处理优化
        props.put(ProducerConfig.LINGER_MS_CONFIG, 10);     // 等待10ms凑批
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, 32768);  // 32KB批次
        props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "lz4"); // 压缩

        try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {

            for (int i = 0; i < 100; i++) {
                String key = "order-" + i;
                String value = String.format("{\"orderId\":%d,\"amount\":%.2f}", i, i * 9.9);

                ProducerRecord<String, String> record =
                        new ProducerRecord<>("order-events", key, value);

                // 异步发送 + 回调
                producer.send(record, (metadata, exception) -> {
                    if (exception != null) {
                        System.err.println("发送失败: " + exception.getMessage());
                        // 这里可以做重试、告警等处理
                    } else {
                        System.out.printf("成功: partition=%d, offset=%d%n",
                                metadata.partition(), metadata.offset());
                    }
                });
            }

            // 确保所有消息发送完成
            producer.flush();
            TimeUnit.SECONDS.sleep(2);
        }
    }
}

5.3 生产者拦截器

拦截器可以在消息发送前后做处理,例如添加统一 Header:

java 复制代码
import org.apache.kafka.clients.producer.*;

import java.util.Map;

public class TimestampInterceptor implements ProducerInterceptor<String, String> {

    @Override
    public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
        // 发送前:添加时间戳 Header
        long timestamp = System.currentTimeMillis();
        return new ProducerRecord<>(
                record.topic(), record.partition(),
                record.timestamp(), record.key(), record.value(),
                record.headers().add("send-timestamp", String.valueOf(timestamp).getBytes())
        );
    }

    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
        // 收到确认后:记录发送耗时等
        if (exception != null) {
            System.err.println("发送异常: " + exception.getMessage());
        }
    }

    @Override
    public void close() {}

    @Override
    public void configure(Map<String, ?> configs) {}
}

配置使用拦截器:

java 复制代码
props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TimestampInterceptor.class.getName());

5.4 发送方式对比

方式 特点 适用场景
同步发送 (send().get()) 阻塞等待、低吞吐 强一致性要求、少量消息
异步发送 (send(callback)) 非阻塞、高吞吐 大部分生产场景(推荐)
忽略结果 (send()) 最高吞吐、不保证 日志采集等容忍丢失场景

6. 消费者实战

6.1 基础消费者

java 复制代码
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.Collections;
import java.util.Properties;

public class BasicConsumer {

    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "order-consumer-group");
        // 从最早的消息开始消费
        props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
        // 关闭自动提交,手动控制
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);

        try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {

            // 订阅 Topic
            consumer.subscribe(Collections.singletonList("order-events"));

            // 轮询消费
            while (true) {
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));

                for (ConsumerRecord<String, String> record : records) {
                    System.out.printf("收到消息: partition=%d, offset=%d, key=%s, value=%s%n",
                            record.partition(), record.offset(), record.key(), record.value());
                }

                // 手动同步提交位移
                if (!records.isEmpty()) {
                    consumer.commitSync();
                }
            }
        }
    }
}

6.2 手动异步提交位移

java 复制代码
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.Collections;
import java.util.Properties;

public class AsyncCommitConsumer {

    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "order-consumer-group");
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
        props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 100);

        try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
            consumer.subscribe(Collections.singletonList("order-events"));

            while (true) {
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));

                for (ConsumerRecord<String, String> record : records) {
                    try {
                        processMessage(record);
                    } catch (Exception e) {
                        System.err.println("处理失败: " + e.getMessage());
                        // 根据业务决定是否继续
                    }
                }

                // 异步提交位移(不阻塞,性能好)
                if (!records.isEmpty()) {
                    consumer.commitAsync((offsets, exception) -> {
                        if (exception != null) {
                            System.err.println("位移提交失败: " + exception.getMessage());
                        }
                    });
                }
            }
        }
    }

    private static void processMessage(ConsumerRecord<String, String> record) {
        // 模拟业务处理
        System.out.printf("处理消息: key=%s, value=%s%n", record.key(), record.value());
    }
}

6.3 精确控制位移提交(按分区提交)

java 复制代码
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.*;

public class PreciseOffsetConsumer {

    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "order-consumer-group");
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);

        try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
            consumer.subscribe(Collections.singletonList("order-events"));

            while (true) {
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));

                // 按分区处理,精确控制每个分区的位移
                Map<TopicPartition, OffsetAndMetadata> offsets = new HashMap<>();

                for (TopicPartition partition : records.partitions()) {
                    List<ConsumerRecord<String, String>> partitionRecords = records.records(partition);

                    for (ConsumerRecord<String, String> record : partitionRecords) {
                        processMessage(record);
                        // 记录每条消息处理后的位移
                        offsets.put(partition, new OffsetAndMetadata(record.offset() + 1));
                    }
                }

                // 按分区精确提交位移
                if (!offsets.isEmpty()) {
                    consumer.commitSync(offsets);
                }
            }
        }
    }

    private static void processMessage(ConsumerRecord<String, String> record) {
        System.out.printf("处理: partition=%d, offset=%d%n", record.partition(), record.offset());
    }
}

6.4 位移提交策略对比

策略 优点 缺点 适用场景
自动提交 简单方便 可能重复消费/丢失 容错性高的场景
同步提交 可靠,阻塞等待 性能差 强一致性要求
异步提交 性能好 可能提交失败 大部分场景
按分区提交 精确控制 代码复杂 精确一次性消费

7. 分区策略与消息路由

7.1 Kafka 默认分区策略

java 复制代码
// 默认分区器逻辑(DefaultPartitioner)
public int partition(String topic, Object key, byte[] keyBytes,
                     Object value, byte[] valueBytes, Cluster cluster) {
    if (keyBytes == null) {
        // 无 Key:轮询(Round-Robin)或 Sticky(粘性)
        return stickyPartitionCache.partition(topic, cluster);
    } else {
        // 有 Key:按 Key 哈希取模
        return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
    }
}
场景 策略 说明
有 Key Hash 分区 murmur2(key) % 分区数,相同Key到同一分区
无 Key (3.x之前) Round-Robin 轮询分配到各分区
无 Key (3.x+) Sticky 粘性 批次内粘在同一分区,减少延迟

7.2 自定义分区器

按业务规则路由消息到特定分区:

java 复制代码
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import org.apache.kafka.common.PartitionInfo;

import java.util.List;
import java.util.Map;

/**
 * 自定义分区器:VIP 用户消息发送到指定分区
 */
public class VipPartitioner implements Partitioner {

    @Override
    public int partition(String topic, Object key, byte[] keyBytes,
                         Object value, byte[] valueBytes, Cluster cluster) {

        List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
        int numPartitions = partitions.size();

        // 最后一个分区专门给 VIP 用户
        if (key != null && key.toString().startsWith("VIP-")) {
            return numPartitions - 1;  // 最后一个分区
        }

        // VIP 内容:根据会员等级路由
        String valueStr = value.toString();
        if (valueStr.contains("\"level\":\"gold\"")) {
            return numPartitions - 2;  // 倒数第二个分区
        }

        // 普通用户:Hash 分区
        if (keyBytes != null) {
            return Math.abs(org.apache.kafka.common.utils.Utils.murmur2(keyBytes)) % (numPartitions - 2);
        }

        // 无 Key:随机
        return (int) (Math.random() * (numPartitions - 2));
    }

    @Override
    public void close() {}

    @Override
    public void configure(Map<String, ?> configs) {}
}

使用自定义分区器:

java 复制代码
props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, VipPartitioner.class.getName());

7.3 指定分区发送

java 复制代码
// 直接指定分区为 0(忽略 Key 的哈希路由)
ProducerRecord<String, String> record = new ProducerRecord<>(
        "order-events",  // topic
        0,               // partition(指定分区)
        "order-key",     // key
        "order-value"    // value
);
producer.send(record);

8. 序列化与自定义序列化器

8.1 内置序列化器

序列化器 说明
StringSerializer 字符串
IntegerSerializer 整数
LongSerializer 长整数
ByteArraySerializer 字节数组
ByteBufferSerializer ByteBuffer

8.2 自定义序列化器

定义业务对象:

java 复制代码
/**
 * 订单实体
 */
public class Order {
    private long orderId;
    private String userId;
    private double amount;
    private String status;
    private long timestamp;

    // 构造方法
    public Order(long orderId, String userId, double amount, String status) {
        this.orderId = orderId;
        this.userId = userId;
        this.amount = amount;
        this.status = status;
        this.timestamp = System.currentTimeMillis();
    }

    // Getters and Setters
    public long getOrderId() { return orderId; }
    public void setOrderId(long orderId) { this.orderId = orderId; }
    public String getUserId() { return userId; }
    public void setUserId(String userId) { this.userId = userId; }
    public double getAmount() { return amount; }
    public void setAmount(double amount) { this.amount = amount; }
    public String getStatus() { return status; }
    public void setStatus(String status) { this.status = status; }
    public long getTimestamp() { return timestamp; }
    public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
}

自定义序列化器:

java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.kafka.common.errors.SerializationException;
import org.apache.kafka.common.serialization.Serializer;

/**
 * Order 自定义序列化器(使用 Jackson 序列化为 JSON)
 */
public class OrderSerializer implements Serializer<Order> {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public byte[] serialize(String topic, Order data) {
        if (data == null) {
            return null;
        }
        try {
            return objectMapper.writeValueAsBytes(data);
        } catch (Exception e) {
            throw new SerializationException("Order 序列化失败: " + e.getMessage(), e);
        }
    }

    @Override
    public void close() {
        // 清理资源
    }
}

自定义反序列化器:

java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.kafka.common.errors.SerializationException;
import org.apache.kafka.common.serialization.Deserializer;

/**
 * Order 自定义反序列化器
 */
public class OrderDeserializer implements Deserializer<Order> {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public Order deserialize(String topic, byte[] data) {
        if (data == null) {
            return null;
        }
        try {
            return objectMapper.readValue(data, Order.class);
        } catch (Exception e) {
            throw new SerializationException("Order 反序列化失败: " + e.getMessage(), e);
        }
    }

    @Override
    public void close() {}
}

8.3 使用自定义序列化器

java 复制代码
import org.apache.kafka.clients.producer.*;
import java.util.Properties;

public class OrderProducer {

    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        // 使用自定义序列化器
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, OrderSerializer.class.getName());
        props.put(ProducerConfig.ACKS_CONFIG, "all");
        props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);

        try (KafkaProducer<String, Order> producer = new KafkaProducer<>(props)) {

            for (int i = 0; i < 10; i++) {
                Order order = new Order(i, "user-" + i, i * 99.9, "CREATED");
                ProducerRecord<String, Order> record =
                        new ProducerRecord<>("orders", "user-" + i, order);

                producer.send(record, (metadata, e) -> {
                    if (e != null) {
                        e.printStackTrace();
                    } else {
                        System.out.printf("订单发送成功: partition=%d, offset=%d%n",
                                metadata.partition(), metadata.offset());
                    }
                });
            }
        }
    }
}

💡 生产建议 :实际项目中推荐直接使用 StringSerializer + JSON 字符串,或使用 Avro/Protobuf 等高效序列化框架配合 Schema Registry。自定义序列化器增加维护成本,仅在特殊需求时使用。


9. 消费者组与 Rebalance

9.1 Rebalance 触发条件

Rebalance(重平衡)是指消费者组中分区的重新分配,触发条件:

触发条件 说明
消费者加入组 新消费者上线
消费者离开组 消费者下线/崩溃
消费者崩溃 心跳超时(session.timeout.ms
Topic 分区数变化 增加分区
订阅 Topic 变化 订阅的 Topic 列表变化

9.2 Rebalance 问题与影响

Rebalance 期间消费者无法消费 ,会导致消费暂停。频繁 Rebalance 会严重影响性能:

复制代码
正常消费:    ████ ████ ████ ████ ████
Rebalance:   ████ ░░░░░░░░░░░░ ████ ████
                      ↑
                 消费暂停(Stop The World)

9.3 消费者 Rebalance 监听器

java 复制代码
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.*;

public class RebalanceListenerConsumer {

    // 记录每个分区当前处理到的位移(用于Rebalance恢复)
    private static final Map<TopicPartition, Long> currentOffsets = new HashMap<>();

    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "order-consumer-group");
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);

        // 自定义 ConsumerRebalanceListener
        ConsumerRebalanceListener listener = new ConsumerRebalanceListener() {

            /**
             * 分区被撤销时调用(Rebalance 开始前)
             * 在这里提交已处理但未提交的位移
             */
            @Override
            public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
                System.out.println("分区被撤销: " + partitions);

                // 提交当前处理进度,防止重复消费
                if (!currentOffsets.isEmpty()) {
                    Map<TopicPartition, OffsetAndMetadata> offsets = new HashMap<>();
                    currentOffsets.forEach((tp, offset) ->
                            offsets.put(tp, new OffsetAndMetadata(offset + 1)));
                    // 使用全局 consumer 引用提交
                    // consumer.commitSync(offsets);
                }
            }

            /**
             * 分区被分配时调用(Rebalance 完成后)
             * 可以在这里做初始化工作
             */
            @Override
            public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
                System.out.println("分区被分配: " + partitions);
                currentOffsets.clear();
            }
        };

        try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
            consumer.subscribe(Collections.singletonList("order-events"), listener);

            while (true) {
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));

                for (ConsumerRecord<String, String> record : records) {
                    processMessage(record);
                    // 记录处理进度
                    currentOffsets.put(
                            new TopicPartition(record.topic(), record.partition()),
                            record.offset()
                    );
                }

                if (!records.isEmpty()) {
                    consumer.commitAsync();
                }
            }
        }
    }

    private static void processMessage(ConsumerRecord<String, String> record) {
        System.out.printf("处理: partition=%d, offset=%d, value=%s%n",
                record.partition(), record.offset(), record.value());
    }
}

9.4 避免 Rebalance 的最佳实践

java 复制代码
Properties props = new Properties();

// 1. 合理设置 session.timeout.ms(避免误判崩溃)
// 默认 45 秒,如果处理慢可适当调大
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 60000);

// 2. 合理设置 heartbeat.interval.ms(通常为 session.timeout 的 1/3)
props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, 20000);

// 3. 合理设置 max.poll.interval.ms(避免处理超时触发Rebalance)
// 如果单次处理时间长,必须调大此值
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 600000); // 10分钟

// 4. 减少 max.poll.records(减少单次处理量)
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 100);

// 5. 使用 Sticky 分配策略(减少Rebalance时的分区迁移)
props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,
        "org.apache.kafka.clients.consumer.CooperativeStickyAssignor");

10. 消息可靠性保障

10.1 可靠性三层次

Kafka 提供三种消息可靠性保障层次:

复制代码
最多一次 (At Most Once)     ← 消息可能丢失,不会重复
    │
至少一次 (At Least Once)    ← 消息不会丢失,可能重复
    │
精确一次 (Exactly Once)     ← 消息不丢失不重复(最强)

10.2 生产者端配置

10.2.1 acks 确认机制
java 复制代码
// acks=0: 生产者不等待确认(可能丢失,吞吐最高)
props.put(ProducerConfig.ACKS_CONFIG, "0");

// acks=1: Leader 写入即确认(Leader宕机可能丢失)
props.put(ProducerConfig.ACKS_CONFIG, "1");

// acks=all (或 -1): 所有 ISR 副本确认(最安全,推荐)
props.put(ProducerConfig.ACKS_CONFIG, "all");
复制代码
acks=all 的写入流程:

Producer ──写入──→ Leader (Broker 1)
                      │
                      ├──同步──→ Follower (Broker 2) ✓
                      └──同步──→ Follower (Broker 3) ✓
                      │
                   全部确认
                      ↓
Producer ←──ACK───── Leader
10.2.2 幂等性生产者
java 复制代码
// 开启幂等性(防止网络重试导致重复)
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
// 幂等性自动保证以下配置:
// - acks = all
// - retries > 0
// - max.in.flight.requests.per.connection <= 5

💡 幂等性通过 PID(Producer ID)+ Sequence Number 实现,保证单分区内不重复。但不能跨分区/跨会话。

10.2.3 事务性生产者(精确一次)
java 复制代码
import org.apache.kafka.clients.producer.*;

import java.util.Properties;

public class TransactionalProducer {

    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        // 开启事务
        props.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "order-tx-producer-1");
        props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);

        try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {

            // 初始化事务
            producer.initTransactions();

            try {
                // 开启事务
                producer.beginTransaction();

                // 事务内发送多条消息
                producer.send(new ProducerRecord<>("orders", "order-1", "订单1"));
                producer.send(new ProducerRecord<>("orders", "order-2", "订单2"));
                producer.send(new ProducerRecord<>("notifications", "notify-1", "通知1"));

                // 提交事务
                producer.commitTransaction();
                System.out.println("事务提交成功");

            } catch (Exception e) {
                // 异常时中止事务
                producer.abortTransaction();
                System.err.println("事务已回滚: " + e.getMessage());
            }
        }
    }
}

消费者端需要配置事务隔离级别:

java 复制代码
// 只读取已提交的事务消息
props.put(ConsumerConfig.ISOLATION_LEVEL_CONFIG, "read_committed");

10.3 消费者端配置

java 复制代码
Properties props = new Properties();

// 关闭自动提交,手动提交(防止消息丢失)
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);

// 业务处理完成后才提交位移
// 这样即使处理失败,下次还能重新消费

10.4 消费-处理-提交模式(Exactly-Once 消费)

java 复制代码
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.*;

/**
 * 精确一次消费模式:Kafka 消费 + 业务处理 + 位移提交 原子化
 * 结合事务实现 Exactly-Once 语义
 */
public class ExactlyOnceConsumer {

    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "order-consumer-group");
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
        // 只读取已提交消息
        props.put(ConsumerConfig.ISOLATION_LEVEL_CONFIG, "read_committed");

        try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
            consumer.subscribe(Collections.singletonList("orders"));

            while (true) {
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));

                for (ConsumerRecord<String, String> record : records) {
                    try {
                        // 1. 处理业务逻辑
                        processOrder(record.value());

                        // 2. 处理成功后提交位移
                        consumer.commitSync(Collections.singletonMap(
                                new org.apache.kafka.common.TopicPartition(
                                        record.topic(), record.partition()),
                                new OffsetAndMetadata(record.offset() + 1)
                        ));

                    } catch (Exception e) {
                        // 3. 处理失败,不提交位移,下次重新消费
                        System.err.println("处理失败,将重试: " + e.getMessage());
                    }
                }
            }
        }
    }

    private static void processOrder(String orderJson) {
        // 实际业务处理逻辑
        System.out.println("处理订单: " + orderJson);
    }
}

10.5 可靠性配置总结

组件 配置项 推荐值 作用
Topic min.insync.replicas 2 最少同步副本数
Topic replication.factor 3 副本总数
Producer acks all 所有副本确认
Producer enable.idempotence true 幂等防重
Producer retries 2147483647 无限重试
Producer max.in.flight.requests.per.connection 5 控制乱序
Consumer enable.auto.commit false 手动提交
Consumer isolation.level read_committed 事务隔离

11. Spring Boot 集成 Kafka

11.1 添加依赖

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.kafka</groupId>
        <artifactId>spring-kafka</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

11.2 配置文件

application.yml:

yaml 复制代码
spring:
  kafka:
    bootstrap-servers: localhost:9092
    
    # 生产者配置
    producer:
      retries: 3
      batch-size: 16384
      buffer-memory: 33554432
      acks: all
      properties:
        linger.ms: 10
        enable.idempotence: true
        compression.type: lz4
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
    
    # 消费者配置
    consumer:
      group-id: order-consumer-group
      auto-offset-reset: earliest
      enable-auto-commit: false
      max-poll-records: 500
      properties:
        max.poll.interval.ms: 600000
        session.timeout.ms: 60000
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
      properties:
        spring.json.trusted.packages: "com.example.kafka.model"
    
    # 监听器配置
    listener:
      ack-mode: manual_immediate
      concurrency: 3

11.3 消息模型

java 复制代码
package com.example.kafka.model;

public class OrderEvent {
    private Long orderId;
    private String userId;
    private Double amount;
    private String status;
    private Long timestamp;

    // 构造方法
    public OrderEvent() {}

    public OrderEvent(Long orderId, String userId, Double amount, String status) {
        this.orderId = orderId;
        this.userId = userId;
        this.amount = amount;
        this.status = status;
        this.timestamp = System.currentTimeMillis();
    }

    // Getters and Setters
    public Long getOrderId() { return orderId; }
    public void setOrderId(Long orderId) { this.orderId = orderId; }
    public String getUserId() { return userId; }
    public void setUserId(String userId) { this.userId = userId; }
    public Double getAmount() { return amount; }
    public void setAmount(Double amount) { this.amount = amount; }
    public String getStatus() { return status; }
    public void setStatus(String status) { this.status = status; }
    public Long getTimestamp() { return timestamp; }
    public void setTimestamp(Long timestamp) { this.timestamp = timestamp; }

    @Override
    public String toString() {
        return "OrderEvent{orderId=" + orderId + ", userId='" + userId +
                "', amount=" + amount + ", status='" + status + "'}";
    }
}

11.4 生产者服务

java 复制代码
package com.example.kafka.producer;

import com.example.kafka.model.OrderEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 OrderEventProducer {

    private static final Logger log = LoggerFactory.getLogger(OrderEventProducer.class);
    private static final String TOPIC = "order-events";

    private final KafkaTemplate<String, OrderEvent> kafkaTemplate;

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

    /**
     * 异步发送订单事件
     */
    public void sendOrderEvent(OrderEvent event) {
        String key = event.getUserId();

        CompletableFuture<SendResult<String, OrderEvent>> future =
                kafkaTemplate.send(TOPIC, key, event);

        future.whenComplete((result, ex) -> {
            if (ex != null) {
                log.error("订单事件发送失败: orderId={}", event.getOrderId(), ex);
            } else {
                log.info("订单事件发送成功: orderId={}, partition={}, offset={}",
                        event.getOrderId(),
                        result.getRecordMetadata().partition(),
                        result.getRecordMetadata().offset());
            }
        });
    }

    /**
     * 同步发送(阻塞等待)
     */
    public void sendOrderEventSync(OrderEvent event) throws Exception {
        String key = event.getUserId();
        kafkaTemplate.send(TOPIC, key, event).get();
        log.info("订单事件同步发送成功: orderId={}", event.getOrderId());
    }

    /**
     * 发送到指定分区
     */
    public void sendToPartition(OrderEvent event, int partition) {
        kafkaTemplate.send(TOPIC, partition, event.getUserId(), event);
        log.info("订单事件发送到分区 {}: orderId={}", partition, event.getOrderId());
    }
}

11.5 消费者服务

java 复制代码
package com.example.kafka.consumer;

import com.example.kafka.model.OrderEvent;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.stereotype.Service;

@Service
public class OrderEventConsumer {

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

    /**
     * 消费订单事件(手动确认)
     */
    @KafkaListener(
            topics = "order-events",
            groupId = "order-consumer-group",
            concurrency = "3"
    )
    public void consume(OrderEvent event, Acknowledgment ack) {
        try {
            log.info("收到订单事件: {}", event);

            // 业务处理
            processOrder(event);

            // 手动确认位移
            ack.acknowledge();

        } catch (Exception e) {
            log.error("订单处理失败: {}", event, e);
            // 不确认位移,下次重新消费
            // 也可以选择确认(跳过坏消息)避免阻塞
        }
    }

    /**
     * 获取完整 ConsumerRecord(包含分区、offset等信息)
     */
    @KafkaListener(topics = "order-events", groupId = "audit-group")
    public void consumeWithMetadata(ConsumerRecord<String, OrderEvent> record,
                                    Acknowledgment ack) {
        log.info("收到消息: topic={}, partition={}, offset={}, key={}, value={}",
                record.topic(), record.partition(), record.offset(),
                record.key(), record.value());

        ack.acknowledge();
    }

    private void processOrder(OrderEvent event) {
        // 模拟业务处理
        switch (event.getStatus()) {
            case "CREATED":
                log.info("创建订单: {}", event.getOrderId());
                break;
            case "PAID":
                log.info("订单已支付: {}", event.getOrderId());
                break;
            case "SHIPPED":
                log.info("订单已发货: {}", event.getOrderId());
                break;
        }
    }
}

11.6 REST 接口

java 复制代码
package com.example.kafka.controller;

import com.example.kafka.model.OrderEvent;
import com.example.kafka.producer.OrderEventProducer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderEventProducer producer;

    public OrderController(OrderEventProducer producer) {
        this.producer = producer;
    }

    @PostMapping
    public ResponseEntity<String> createOrder(@RequestBody OrderEvent event) {
        event.setStatus("CREATED");
        producer.sendOrderEvent(event);
        return ResponseEntity.ok("订单事件已发送: " + event.getOrderId());
    }
}

11.7 ack-mode 配置说明

ack-mode 说明
record 每条记录处理完后确认(最安全,性能低)
batch 每批 poll 处理完后确认(默认)
time 按时间间隔确认
count 按数量确认
manual 手动调用 acknowledgment.acknowledge()(下次 poll 时提交)
manual_immediate 手动调用后立即提交(推荐)

12. Kafka Streams 实战

12.1 Kafka Streams 简介

Kafka Streams 是 Kafka 内置的流处理库,无需额外集群,直接嵌入 Java 应用中。

核心概念:

  • Stream:无界、持续的记录流
  • Table:有状态的、可更新的数据集合(类似数据库表)
  • KTable:每个 Key 保留最新值
  • GlobalKTable:全局副本的 KTable
  • Windowed:基于时间窗口的聚合

12.2 添加依赖

xml 复制代码
<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-streams</artifactId>
    <version>3.7.0</version>
</dependency>

12.3 词频统计实战

java 复制代码
package com.example.kafka.streams;

import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.*;
import org.apache.kafka.streams.kstream.*;

import java.util.Arrays;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;

public class WordCountStream {

    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(StreamsConfig.APPLICATION_ID_CONFIG, "word-count-app");
        props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        props.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, 1000);

        // 构建流处理拓扑
        StreamsBuilder builder = new StreamsBuilder();

        // 1. 从 input-topic 读取文本流
        KStream<String, String> textLines = builder.stream("text-input-topic");

        // 2. 处理流
        KTable<String, Long> wordCounts = textLines
                // 将每行文本拆分为单词
                .flatMapValues(line -> Arrays.asList(line.toLowerCase().split("\\W+")))
                // 为每个单词生成 (word, 1) 的键值对
                .groupBy((key, word) -> word)
                // 按单词计数
                .count(Materialized.as("word-counts-store"));

        // 3. 将结果写入 output-topic
        wordCounts.toStream().to("word-count-output-topic",
                Produced.with(Serdes.String(), Serdes.Long()));

        // 构建并启动
        KafkaStreams streams = new KafkaStreams(builder.build(), props);

        // 优雅关闭
        CountDownLatch latch = new CountDownLatch(1);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            streams.close();
            latch.countDown();
        }));

        try {
            streams.start();
            latch.await();
        } catch (Exception e) {
            System.exit(1);
        }
        System.exit(0);
    }
}

12.4 窗口聚合实战

统计每 5 分钟内各用户的订单金额:

java 复制代码
package com.example.kafka.streams;

import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.*;
import org.apache.kafka.streams.kstream.*;

import java.time.Duration;
import java.util.Properties;

public class OrderAmountWindowStream {

    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(StreamsConfig.APPLICATION_ID_CONFIG, "order-amount-window-app");
        props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());

        StreamsBuilder builder = new StreamsBuilder();

        // 输入: userId -> "amount" (JSON字符串)
        KStream<String, String> orders = builder.stream("orders");

        // 5分钟滚动窗口,按用户聚合订单金额
        KTable<Windowed<String>, Double> windowedAmounts = orders
                // 将金额字符串转为 Double
                .mapValues(value -> Double.parseDouble(value))
                // 按用户分组
                .groupByKey(Grouped.with(Serdes.String(), Serdes.Double()))
                // 5分钟滚动窗口
                .windowedBy(TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(5)))
                // 聚合:求总金额
                .aggregate(
                        () -> 0.0,                    // 初始值
                        (key, value, aggregate) -> aggregate + value,  // 聚合函数
                        Materialized.with(Serdes.String(), Serdes.Double())
                );

        // 输出结果
        windowedAmounts.toStream().foreach((windowedKey, total) -> {
            System.out.printf("用户=%s, 窗口=%s ~ %s, 总金额=%.2f%n",
                    windowedKey.key(),
                    windowedKey.window().startTime(),
                    windowedKey.window().endTime(),
                    total);
        });

        KafkaStreams streams = new KafkaStreams(builder.build(), props);
        streams.start();
    }
}

12.5 流式 Join

将订单流与用户信息流 Join:

java 复制代码
StreamsBuilder builder = new StreamsBuilder();

// 订单流: orderId -> orderJson
KStream<String, String> orders = builder.stream("orders");

// 用户信息表: userId -> userInfoJson
KTable<String, String> users = builder.table("users");

// 按订单中的 userId Join 用户信息
KStream<String, String> enrichedOrders = orders
        .selectKey((orderId, order) -> extractUserId(order))  // 重新按userId分组
        .join(users, (order, user) -> {
            // 将订单和用户信息合并
            return mergeOrderWithUser(order, user);
        });

enrichedOrders.to("enriched-orders");

12.6 窗口类型对比

窗口类型 说明 场景
Tumbling Window(滚动) 固定大小、不重叠 每5分钟统计一次
Hopping Window(跳跃) 固定大小、可重叠 每1分钟统计过去5分钟数据
Session Window(会话) 基于活动间隔 用户会话分析

13. 性能调优

13.1 生产者调优

java 复制代码
Properties props = new Properties();

// === 吞吐量优化 ===

// 批次大小(默认16KB,调大到64KB~128KB)
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 65536);

// 批次等待时间(默认0,设为5~20ms让消息凑批)
props.put(ProducerConfig.LINGER_MS_CONFIG, 10);

// 发送缓冲区(默认32MB,大数据量可调大)
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 67108864); // 64MB

// 压缩(lz4 综合性能最佳,zstd 压缩率最高)
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "lz4");

// === 延迟优化 ===

// 减小 linger.ms(立即发送,不等待凑批)
props.put(ProducerConfig.LINGER_MS_CONFIG, 0);

// 减小 batch.size
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);

13.2 消费者调优

java 复制代码
Properties props = new Properties();

// === 吞吐量优化 ===

// 单次拉取最大记录数(默认500,调大到1000~2000)
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 1000);

// 最小拉取字节数(默认1B,调大减少小批次)
props.put(ConsumerConfig.FETCH_MIN_BYTES_CONFIG, 1024 * 10); // 10KB

// 最大等待时间(配合 fetch.min.bytes,攒够数据或超时返回)
props.put(ConsumerConfig.FETCH_MAX_WAIT_MS_CONFIG, 500);

// 最大拉取字节数(默认50MB)
props.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG, 52428800);

// === 并发优化 ===

// 增加分区数和消费者数
// 增加消费者 concurrency(Spring Boot 中 listener.concurrency)

13.3 Broker 端调优

properties 复制代码
# server.properties

# 网络线程数(处理请求,默认3)
num.network.threads=8

# IO 线程数(磁盘读写,默认8)
num.io.threads=16

# Socket 缓冲区
socket.send.buffer.bytes=1048576
socket.receive.buffer.bytes=1048576
socket.request.max.bytes=104857600

# 日志刷盘策略(权衡性能与可靠性)
# 方式一:按消息条数刷盘
log.flush.interval.messages=10000
# 方式二:按时间刷盘
log.flush.interval.ms=1000

# 日志段大小(默认1GB,大日志段减少文件数)
log.segment.bytes=1073741824

# 日志保留时间(默认7天)
log.retention.hours=168

# 压缩清理(针对 compact 策略的 topic)
log.cleaner.threads=4

13.4 JVM 调优

bash 复制代码
# Kafka Broker JVM 推荐(16GB 内存机器)
export KAFKA_HEAP_OPTS="-Xms8g -Xmx8g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
export KAFKA_JVM_PERFORMANCE_OPTS="-server -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:+ExplicitGCInvokesConcurrent -Djava.awt.headless=true"

13.5 调优决策矩阵

目标 生产者 消费者 Broker
高吞吐 ↑batch.size, ↑linger.ms, lz4压缩 ↑max.poll.records, ↑fetch.min.bytes ↑num.io.threads
低延迟 ↓linger.ms=0, ↓batch.size ↑fetch.min.bytes ↑num.network.threads
高可靠 acks=all, ↑retries, 幂等 手动提交, read_committed ↑replication.factor, ↑min.insync.replicas

14. 监控与运维

14.1 JMX 指标监控

Kafka 通过 JMX 暴露丰富的监控指标:

bash 复制代码
# 启动时开启 JMX
export JMX_PORT=9999
kafka-server-start.sh server.properties

# 使用 jconsole / jvisualvm 连接
# 或使用命令行工具
kafka-run-class.sh kafka.tools.JmxTool \
  --object-name kafka.server:type=BrokerTopicMetrics,name=MessagesInPerSec \
  --jmx-url service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi \
  --attributes OneMinuteRate \
  --reporting-interval 2000

14.2 核心监控指标

14.2.1 Broker 指标
指标 说明 告警阈值
BytesInPerSec 每秒入站字节 -
BytesOutPerSec 每秒出站字节 -
MessagesInPerSec 每秒消息数 -
UnderReplicatedPartitions 未同步分区数 > 0
OfflinePartitionsCount 离线分区数 > 0(严重)
ActiveControllerCount 活跃Controller数 必须 = 1
RequestHandlerAvgIdlePercent 请求处理空闲率 < 0.3
DiskUsage 磁盘使用率 > 85%
14.2.2 生产者指标
指标 说明
record-send-rate 每秒发送记录数
record-error-rate 每秒错误记录数
request-latency-avg 平均请求延迟
batch-size-avg 平均批次大小
compression-rate-avg 平均压缩率
14.2.3 消费者指标
指标 说明 告警阈值
records-lag-max 最大消费延迟 需根据业务设定
records-consumed-rate 每秒消费记录数 -
fetch-latency-avg 平均拉取延迟 -
commit-latency-avg 平均提交延迟 -

14.3 监控方案

复制代码
Kafka (JMX) ──→ JMX Exporter ──→ Prometheus ──→ Grafana
                                          ↑
                                    告警: Alertmanager

Prometheus + Grafana 部署(docker-compose 补充):

yaml 复制代码
services:
  jmx-exporter:
    image: bitnami/jmx-exporter:latest
    ports:
      - "5556:5556"
    environment:
      JMX_PORT: 9999
      TARGET_HOST: kafka

14.4 常用运维命令

bash 复制代码
# === Topic 管理 ===

# 增加 Topic 分区数(只能增加)
kafka-topics.sh --alter --bootstrap-server localhost:9092 \
  --topic order-events --partitions 6

# 修改 Topic 配置
kafka-configs.sh --alter --bootstrap-server localhost:9092 \
  --entity-type topics --entity-name order-events \
  --add-config retention.ms=86400000  # 保留1天

# === 消费者组管理 ===

# 查看所有消费者组
kafka-consumer-groups.sh --list --bootstrap-server localhost:9092

# 查看消费者组详情(Lag 消费延迟)
kafka-consumer-groups.sh --describe --group order-consumer-group \
  --bootstrap-server localhost:9092

# 重置消费者位移到最早
kafka-consumer-groups.sh --reset-offsets --group order-consumer-group \
  --topic order-events --to-earliest --execute \
  --bootstrap-server localhost:9092

# 重置位移到指定时间
kafka-consumer-groups.sh --reset-offsets --group order-consumer-group \
  --topic order-events --to-datetime 2025-06-19T00:00:00.000 --execute \
  --bootstrap-server localhost:9092

# 重置位移到指定 offset
kafka-consumer-groups.sh --reset-offsets --group order-consumer-group \
  --topic order-events:0:100 --to-offset 100 --execute \
  --bootstrap-server localhost:9092

# === 查询消息 ===

# 按时间查询 offset
kafka-get-offsets.sh --bootstrap-server localhost:9092 \
  --topic order-events --time -1  # -1=最新, -2=最早

# 查看指定分区的消息(需配合工具)
kafka-console-consumer.sh --bootstrap-server localhost:9092 \
  --topic order-events --partition 0 --offset 100 --max-messages 10

14.5 消费延迟(Lag)处理

复制代码
# 消费者组 Lag 示例输出
TOPIC           PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG
order-events    0          10000           10500           500
order-events    1          9800            9800            0
order-events    2          9900            10300           400

Lag 持续增长的处理方案:

  1. 增加消费者数量(确保 < 分区数)
  2. 增大 max.poll.records
  3. 多线程处理(注意位移提交的线程安全)
  4. 增加分区数(需同步增加消费者)
  5. 优化业务处理逻辑(异步化、批量处理)

15. 最佳实践与常见问题

15.1 Topic 设计最佳实践

  1. 命名规范<环境>.<域>.<事件类型>,如 prod.order.events
  2. 分区数:提前规划,宁多勿少(但不超过 Broker 承受能力)
  3. 副本数:生产环境至少 3 副本
  4. min.insync.replicas:设为 2(配合 acks=all)
  5. 保留策略 :按业务需求设置 retention.msretention.bytes

15.2 生产者最佳实践

java 复制代码
// 1. 使用异步发送
producer.send(record, callback);

// 2. 合理设置重试,防止消息丢失
props.put(ProducerConfig.RETRIES_CONFIG, Integer.MAX_VALUE);
props.put(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, 120000); // 2分钟超时

// 3. 开启幂等性(默认已开启)
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);

// 4. 有 Key 保证同业务实体消息有序
new ProducerRecord<>("orders", userId, orderJson);

// 5. 使用 try-with-resources 确保关闭
try (KafkaProducer<K, V> producer = new KafkaProducer<>(props)) {
    // ...
}

// 6. 关闭前 flush
producer.flush();
producer.close(Duration.ofSeconds(10));

15.3 消费者最佳实践

java 复制代码
// 1. 关闭自动提交,手动控制位移
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);

// 2. 合理设置 max.poll.interval.ms(处理时间上限)
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 300000);

// 3. 处理失败的消息(死信队列模式)
try {
    processMessage(record);
    ack.acknowledge();
} catch (Exception e) {
    // 发送到死信队列
    deadLetterProducer.send(new ProducerRecord<>("dlq-order-events",
            record.key(), record.value()));
    // 确认原消息(跳过)
    ack.acknowledge();
}

// 4. 使用多线程消费(注意位移安全)
// 推荐方案:每个线程一个消费者实例

15.4 多线程消费者实现

java 复制代码
package com.example.kafka.consumer;

import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;

/**
 * 多线程消费者:每个线程独立消费
 * 推荐做法:一个消费者对应一个处理线程
 */
public class MultiThreadConsumer {

    public static void main(String[] args) {
        int numConsumers = 3;
        ExecutorService executor = Executors.newFixedThreadPool(numConsumers);

        for (int i = 0; i < numConsumers; i++) {
            executor.submit(new ConsumerWorker("order-consumer-group", i));
        }

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            executor.shutdown();
            try {
                executor.awaitTermination(10, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }));
    }

    static class ConsumerWorker implements Runnable {
        private final String groupId;
        private final int id;

        ConsumerWorker(String groupId, int id) {
            this.groupId = groupId;
            this.id = id;
        }

        @Override
        public void run() {
            Properties props = new Properties();
            props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
            props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
            props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
            props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
            props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
            props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");

            try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
                consumer.subscribe(Collections.singletonList("order-events"));

                while (true) {
                    ConsumerRecords<String, String> records =
                            consumer.poll(Duration.ofMillis(1000));

                    for (ConsumerRecord<String, String> record : records) {
                        System.out.printf("[Consumer-%d] partition=%d, offset=%d, value=%s%n",
                                id, record.partition(), record.offset(), record.value());
                    }

                    if (!records.isEmpty()) {
                        consumer.commitSync();
                    }
                }
            }
        }
    }
}

15.5 常见问题排查

Q1: 消费者 Lag 持续增大

原因:消费速度 < 生产速度

解决

  1. 增加消费者实例(需确保分区数足够)
  2. 增大 max.poll.records
  3. 优化业务处理逻辑(批量、异步)
  4. 多线程处理 + 分区级位移管理
Q2: 消息丢失

排查方向

复制代码
生产者端                          消费者端
  │                                │
  ├─ acks=0/1? → 改为 all          ├─ 自动提交位移?
  ├─ 未重试? → 设置 retries        │   → 处理未完成就提交了
  ├─ 缓冲区满丢消息?               ├─ poll 后异步处理?
  │   → 增加 buffer.memory         │   → 异步处理失败丢失
  └─ Producer 关闭未 flush?        └─ → 改为同步处理或回调提交
      → 添加 producer.flush()
Q3: 消息重复

原因:生产者重试 / 消费者 Rebalance 重复消费

解决

  1. 生产者开启幂等性 enable.idempotence=true
  2. 消费者实现幂等处理(数据库唯一约束 / Redis 去重)
  3. 使用事务实现 Exactly-Once
java 复制代码
// 消费者幂等处理示例
public void processMessage(ConsumerRecord<String, String> record) {
    String messageId = extractMessageId(record.value());

    // 使用 Redis 去重
    if (redis.setIfAbsent("msg:processed:" + messageId, "1", 24, TimeUnit.HOURS)) {
        // 首次处理
        doBusinessLogic(record.value());
    } else {
        // 已处理过,跳过
        log.info("消息已处理过,跳过: {}", messageId);
    }
}
Q4: Rebalance 频繁

原因:消费者频繁上下线 / 心跳超时 / 处理超时

解决

  1. 调大 session.timeout.msmax.poll.interval.ms
  2. 减小 max.poll.records,降低单次处理量
  3. 使用 CooperativeStickyAssignor 减少分区迁移
  4. 确保消费者处理逻辑稳定,不长时间阻塞
Q5: 消息顺序性保证

场景:同一订单的状态变更必须有序(创建→支付→发货)

解决方案

java 复制代码
// 生产者:使用 orderId 作为 Key,保证同一订单到同一分区
producer.send(new ProducerRecord<>("order-events", orderId, orderJson));

// 同一分区内消息严格有序
// 注意:开启幂等性时 max.in.flight.requests.per.connection <= 5 仍保证有序
// 如果未开启幂等性,重试可能导致乱序,需设为 1

⚠️ Kafka 只保证单分区内有序,跨分区不保证。如果需要全局有序,只能用单分区(牺牲并发)。

Q6: 如何实现延迟队列

Kafka 原生不支持延迟队列,可通过以下方式实现:

复制代码
方案:多级延迟 Topic

Producer ──→ delay-30s-topic (retention=30s)
                 │ (30秒后过期)
                 ↓
            delay-60s-topic (retention=60s)
                 │
                 ↓
            delay-5min-topic (retention=5min)
                 │
                 ↓
            target-topic
java 复制代码
// 消费者消费延迟 Topic,通过 seek 到指定时间点的消息
consumer.subscribe(Collections.singletonList("delay-30s-topic"));
// 利用 Kafka 的时间索引特性
// 消息写入后 30 秒才被消费者读取(通过轮询 + 时间判断)

15.6 学习路径建议

复制代码
入门阶段
├── 理解消息队列基本概念
├── Docker 搭建本地 Kafka 环境
├── 命令行操作 Topic、生产/消费消息
└── Java 客户端基础生产者/消费者
    ↓
进阶阶段
├── 理解分区、副本、消费者组机制
├── 掌握序列化、分区策略
├── 掌握位移管理与精确提交
├── 掌握消息可靠性(acks、幂等、事务)
└── Spring Boot 集成
    ↓
高级阶段
├── Kafka Streams 流处理
├── 性能调优(生产者/消费者/Broker)
├── 监控运维(JMX、Prometheus、Grafana)
├── 故障排查与问题定位
└── Kafka 集群规划与容量评估

附录

A. Kafka 配置速查

生产者推荐配置
properties 复制代码
bootstrap.servers=localhost:9092
acks=all
retries=2147483647
enable.idempotence=true
max.in.flight.requests.per.connection=5
batch.size=65536
linger.ms=10
buffer.memory=67108864
compression.type=lz4
delivery.timeout.ms=120000
request.timeout.ms=30000
消费者推荐配置
properties 复制代码
bootstrap.servers=localhost:9092
group.id=my-consumer-group
enable.auto.commit=false
auto.offset.reset=earliest
max.poll.records=500
max.poll.interval.ms=300000
session.timeout.ms=45000
heartbeat.interval.ms=15000
fetch.min.bytes=10240
isolation.level=read_committed
Topic 推荐配置
properties 复制代码
# 3副本,最少2副本同步
replication.factor=3
min.insync.replicas=2
# 保留7天
retention.ms=604800000
# 单个日志段1GB
segment.bytes=1073741824

B. 常用 Kafka 命令速查

bash 复制代码
# Topic 管理
kafka-topics.sh --create --bootstrap-server localhost:9092 --topic <topic> --partitions <n> --replication-factor <n>
kafka-topics.sh --list --bootstrap-server localhost:9092
kafka-topics.sh --describe --topic <topic> --bootstrap-server localhost:9092
kafka-topics.sh --alter --topic <topic> --partitions <n> --bootstrap-server localhost:9092
kafka-topics.sh --delete --topic <topic> --bootstrap-server localhost:9092

# 生产/消费
kafka-console-producer.sh --topic <topic> --bootstrap-server localhost:9092
kafka-console-consumer.sh --topic <topic> --from-beginning --bootstrap-server localhost:9092
kafka-console-consumer.sh --topic <topic> --group <group> --bootstrap-server localhost:9092

# 消费者组
kafka-consumer-groups.sh --list --bootstrap-server localhost:9092
kafka-consumer-groups.sh --describe --group <group> --bootstrap-server localhost:9092
kafka-consumer-groups.sh --reset-offsets --group <group> --topic <topic> --to-earliest --execute --bootstrap-server localhost:9092

# 配置管理
kafka-configs.sh --describe --entity-type topics --entity-name <topic> --bootstrap-server localhost:9092
kafka-configs.sh --alter --entity-type topics --entity-name <topic> --add-config retention.ms=86400000 --bootstrap-server localhost:9092

# Offset 查询
kafka-get-offsets.sh --topic <topic> --time -1 --bootstrap-server localhost:9092

C. 推荐学习资源


结语 :Kafka 作为分布式消息队列的事实标准,在大数据和微服务架构中扮演着关键角色。掌握 Kafka 不仅是学会使用 API,更重要的是理解其背后的分区、副本、消费者组等核心机制。希望本文的 Java 实战示例能帮助你在实际项目中游刃有余地使用 Kafka。记住:可靠性 > 性能 > 功能,在保证消息可靠的前提下追求性能,这才是工程实践的正确姿势。