Kafka入门:从零开始掌握消息队列

前言

在当今的分布式系统架构中,消息队列已经成为不可或缺的核心组件。Apache Kafka作为一款高吞吐量、低延迟的分布式消息系统,被广泛应用于大数据处理、日志收集、流式处理等场景。

一、Kafka是什么?

Apache Kafka是一个分布式流处理平台,最初由LinkedIn公司开发,后来贡献给了Apache基金会。Kafka具有以下核心特性:

  • 高吞吐量:每秒可以处理百万级的消息
  • 低延迟:毫秒级的端到端延迟
  • 高可用性:通过副本机制保证数据不丢失
  • 可扩展性:支持水平扩展,轻松应对业务增长
  • 持久化:消息持久化到磁盘,支持消息回溯

Kafka应用场景

  1. 消息队列:实现应用解耦、异步处理、流量削峰
  2. 日志收集:集中收集应用日志,支持实时分析
  3. 流式处理:与Flink配合实现实时数据处理
  4. 事件溯源:记录业务状态变更历史
  5. 分布式事务:实现最终一致性事务

二、Kafka核心概念

2.1 基本架构

Kafka集群由多个Broker组成,生产者(Producer)向Kafka发送消息,消费者(Consumer)从Kafka消费消息。以下是Kafka的基本架构:

  • Broker:Kafka服务节点,负责存储和转发消息
  • Topic:消息的主题,逻辑上的消息分类
  • Partition:分片,Topic被分割成多个分区,实现并行处理
  • Replica:副本,每个分区可以有多个副本,提供容错能力
  • Producer:生产者,负责发送消息到Kafka
  • Consumer:消费者,从Kafka拉取并处理消息
  • Consumer Group:消费者组,实现消息的负载均衡

2.2 分区与副本机制

**分区(Partition)**是Kafka实现高吞吐量的关键。一个Topic可以分为多个分区,每个分区是一个有序的消息队列。分区的好处包括:

  • 并行处理:多个生产者可以并行写入不同分区
  • 提高吞吐量:多个消费者可以并行消费不同分区
  • 负载均衡:分区分布在不同的Broker上,均衡负载

**副本(Replica)**机制保证数据的高可用性:

  • Leader副本:处理所有读写操作
  • Follower副本:从Leader同步数据,Leader故障时可晋升为新Leader
  • ISR(In-Sync Replicas):与Leader保持同步的副本集合

2.3 消息传递语义

Kafka支持三种消息传递语义:

  1. 最多一次(At Most Once)

    • 消息可能丢失,但绝不会重复
    • 适用场景:可以容忍数据丢失,如日志收集、监控数据
    • 配置:enable.auto.commit=true
  2. 至少一次(At Least Once)

    • 消息不会丢失,但可能重复
    • 适用场景:不能容忍数据丢失,如订单处理、支付系统
    • 配置:enable.auto.commit=false,手动提交偏移量
  3. 精确一次(Exactly Once)

    • 消息既不丢失也不重复,每条消息只处理一次
    • 适用场景:金融交易、库存管理等对数据准确性要求极高的场景
    • 配置:enable.idempotence=true,使用事务API

三、Kafka生产者详解

3.1 生产者工作流程

Kafka生产者发送消息的完整流程:

  1. 创建生产者:配置bootstrap.servers、序列化器等参数
  2. 构建消息记录:创建ProducerRecord对象
  3. 序列化:将Java对象序列化为字节数组
  4. 分区器选择:根据消息键选择目标分区
  5. 压缩处理:可选择压缩算法减少网络传输
  6. 批量缓存:消息进入缓冲区,等待批量发送
  7. 发送到Broker:将消息发送到Kafka集群
  8. 等待确认:根据acks配置等待Broker确认
  9. 处理回调:异步处理发送结果

3.2 生产者核心配置

java 复制代码
// 创建生产者配置
Properties props = new Properties();

// Kafka集群地址(必填)
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());

// 消息确认机制(重要)
// acks=0: 不等待确认,可能丢数据
// acks=1: 等待Leader确认(默认)
// acks=all: 等待所有副本确认,最安全但性能最低
props.put(ProducerConfig.ACKS_CONFIG, "all");

// 重试次数
props.put(ProducerConfig.RETRIES_CONFIG, 3);

// 批量发送大小(字节)
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);

// 发送等待时间(毫秒)
props.put(ProducerConfig.LINGER_MS_CONFIG, 10);

// 缓冲区大小(字节)
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);

// 开启幂等性,防止消息重复
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);

3.3 生产者示例代码

同步发送消息

java 复制代码
// 创建生产者
KafkaProducer<String, String> producer = new KafkaProducer<>(props);

// 创建消息记录
ProducerRecord<String, String> record =
    new ProducerRecord<>("demo-topic", "key1", "这是一条同步消息");

// 同步发送(会阻塞直到收到响应)
try {
    RecordMetadata metadata = producer.send(record).get();
    System.out.println("消息发送成功 - 分区: " + metadata.partition() +
                      ", 偏移量: " + metadata.offset());
} catch (InterruptedException | ExecutionException e) {
    System.err.println("消息发送失败: " + e.getMessage());
}

异步发送消息

java 复制代码
// 异步发送(不阻塞,通过回调处理结果)
producer.send(record, new Callback() {
    @Override
    public void onCompletion(RecordMetadata metadata, Exception exception) {
        if (exception == null) {
            // 发送成功
            System.out.println("消息发送成功 - 偏移量: " + metadata.offset());
        } else {
            // 发送失败
            System.err.println("消息发送失败: " + exception.getMessage());
        }
    }
});

发送JSON消息

java 复制代码
ObjectMapper mapper = new ObjectMapper();

// 创建业务对象
Map<String, Object> orderData = new HashMap<>();
orderData.put("orderId", "ORD20240112001");
orderData.put("userId", "U001");
orderData.put("amount", 299.99);
orderData.put("timestamp", System.currentTimeMillis());

// 转换为JSON
String jsonMessage = mapper.writeValueAsString(orderData);

// 发送JSON消息
ProducerRecord<String, String> record =
    new ProducerRecord<>("order-topic", orderData.get("orderId").toString(), jsonMessage);

producer.send(record, (metadata, exception) -> {
    if (exception == null) {
        System.out.println("订单消息发送成功: " + orderData.get("orderId"));
    }
});

事务消息

java 复制代码
// 初始化事务生产者
props.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "my-transactional-id");
KafkaProducer<String, String> producer = new KafkaProducer<>(props);

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

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

    // 发送多条消息(原子操作)
    producer.send(new ProducerRecord<>("order-topic", "order-1", "创建订单"));
    producer.send(new ProducerRecord<>("inventory-topic", "item-1", "扣减库存"));
    producer.send(new ProducerRecord<>("notification-topic", "user-1", "发送通知"));

    // 提交事务(所有消息都成功)或 回滚事务(任何消息失败)
    producer.commitTransaction();
    System.out.println("事务提交成功");
} catch (Exception e) {
    // 发生异常,回滚事务
    producer.abortTransaction();
    System.err.println("事务回滚: " + e.getMessage());
}

四、Kafka消费者详解

4.1 消费者工作流程

Kafka消费者消费消息的完整流程:

  1. 订阅主题:消费者订阅要消费的Topic
  2. 加入消费者组:通过协调器加入消费者组
  3. 分配分区:协调器为消费者分配分区
  4. 拉取消息:从分配的分区拉取消息
  5. 反序列化:将字节数组反序列化为Java对象
  6. 处理消息:执行业务逻辑
  7. 提交偏移量:将已消费的偏移量提交给Kafka
  8. 继续拉取:循环拉取新消息

4.2 消费者核心配置

java 复制代码
// 创建消费者配置
Properties props = new Properties();

// Kafka集群地址(必填)
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");

// 消费者组ID(必填)
// 同一组的消费者会共同消费消息,实现负载均衡
props.put(ConsumerConfig.GROUP_ID_CONFIG, "demo-group");

// 反序列化器配置(必填)
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
          StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
          StringDeserializer.class.getName());

// 自动提交偏移量
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");

// 自动提交间隔(毫秒)
props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");

// 偏移量重置策略
// earliest: 从最早的消息开始消费
// latest: 从最新的消息开始消费(默认)
// none: 如果没有之前的偏移量,则抛出异常
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");

// 单次poll最大记录数
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, "500");

// poll最大间隔时间(超过此时间消费者会被认为失效)
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, "300000");

// 会话超时时间
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000");

4.3 消费者示例代码

基本消费逻辑

java 复制代码
// 创建消费者
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

// 订阅主题
consumer.subscribe(Collections.singletonList("demo-topic"));

try {
    while (true) {
        // 拉取消息(最多等待1秒)
        ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));

        for (ConsumerRecord<String, String> record : records) {
            System.out.println("收到消息:");
            System.out.println("  分区: " + record.partition());
            System.out.println("  偏移量: " + record.offset());
            System.out.println("  键: " + record.key());
            System.out.println("  值: " + record.value());

            // 处理消息(业务逻辑)
            processMessage(record);
        }
    }
} finally {
    consumer.close();
}

手动提交偏移量

java 复制代码
// 关闭自动提交
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");

KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("demo-topic"));

try {
    while (true) {
        ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));

        for (ConsumerRecord<String, String> record : records) {
            try {
                // 处理消息
                processMessage(record);

                // 处理成功后,同步提交偏移量
                Map<TopicPartition, OffsetAndMetadata> offset =
                    Map.of(new TopicPartition(record.topic(), record.partition()),
                           new OffsetAndMetadata(record.offset() + 1));
                consumer.commitSync(offset);

            } catch (Exception e) {
                // 处理失败,不提交偏移量,下次会重新消费
                System.err.println("处理消息失败: " + e.getMessage());
            }
        }
    }
} finally {
    consumer.close();
}

消费JSON消息

java 复制代码
ObjectMapper mapper = new ObjectMapper();

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

    for (ConsumerRecord<String, String> record : records) {
        try {
            // 解析JSON消息
            Map<String, Object> orderData =
                mapper.readValue(record.value(), new TypeReference<Map<String, Object>>() {});

            String orderId = (String) orderData.get("orderId");
            Double amount = (Double) orderData.get("amount");

            // 处理订单
            System.out.println("处理订单: " + orderId + ", 金额: " + amount);

            // 提交偏移量
            consumer.commitSync();

        } catch (Exception e) {
            System.err.println("JSON解析失败: " + e.getMessage());
        }
    }
}

指定分区消费

java 复制代码
// 手动指定分区(不使用消费者组的自动分配)
TopicPartition partition0 = new TopicPartition("demo-topic", 0);
TopicPartition partition1 = new TopicPartition("demo-topic", 1);

consumer.assign(Arrays.asList(partition0, partition1));

// 查看分区位置
Set<TopicPartition> partitions = consumer.assignment();
System.out.println("当前分配的分区: " + partitions);

五、完整消息流转流程

下面是一个完整的Kafka消息流转示例,展示从订单创建到多系统消费的完整链路:

5.1 订单服务(生产者)

java 复制代码
public class OrderProducer {

    private final KafkaProducer<String, String> producer;

    public OrderProducer() {
        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.ACKS_CONFIG, "all");
        props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);

        this.producer = new KafkaProducer<>(props);
    }

    /**
     * 创建订单并发送消息
     */
    public void createOrder(Order order) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            String orderJson = mapper.writeValueAsString(order);

            // 发送订单创建事件
            ProducerRecord<String, String> record =
                new ProducerRecord<>("order-events", order.getOrderId(), orderJson);

            producer.send(record, (metadata, exception) -> {
                if (exception == null) {
                    System.out.println("订单事件发送成功: " + order.getOrderId());

                    // 记录到数据库
                    saveOrderToDatabase(order);

                } else {
                    System.err.println("订单事件发送失败: " + exception.getMessage());
                    // 处理失败逻辑
                }
            });

        } catch (Exception e) {
            System.err.println("订单创建失败: " + e.getMessage());
        }
    }

    private void saveOrderToDatabase(Order order) {
        // 保存到数据库的逻辑
    }

    public void close() {
        producer.close();
    }
}

5.2 库存服务(消费者)

java 复制代码
public class InventoryConsumer {

    private final KafkaConsumer<String, String> consumer;

    public InventoryConsumer() {
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "inventory-group");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
                  StringDeserializer.class.getName());
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
                  StringDeserializer.class.getName());
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");

        this.consumer = new KafkaConsumer<>(props);
    }

    public void start() {
        consumer.subscribe(Collections.singletonList("order-events"));

        try {
            while (true) {
                ConsumerRecords<String, String> records =
                    consumer.poll(Duration.ofSeconds(1));

                for (ConsumerRecord<String, String> record : records) {
                    try {
                        // 解析订单消息
                        ObjectMapper mapper = new ObjectMapper();
                        Order order = mapper.readValue(record.value(), Order.class);

                        // 扣减库存
                        deductInventory(order);

                        // 处理成功,提交偏移量
                        consumer.commitSync();

                    } catch (Exception e) {
                        System.err.println("库存处理失败: " + e.getMessage());
                        // 发送到死信队列或记录日志
                    }
                }
            }
        } finally {
            consumer.close();
        }
    }

    private void deductInventory(Order order) {
        // 扣减库存的逻辑
        System.out.println("扣减库存 - 订单: " + order.getOrderId() +
                         ", 商品: " + order.getProductId());
    }

    public static void main(String[] args) {
        InventoryConsumer consumer = new InventoryConsumer();
        consumer.start();
    }
}

5.3 通知服务(消费者)

java 复制代码
public class NotificationConsumer {

    private final KafkaConsumer<String, String> consumer;

    public NotificationConsumer() {
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "notification-group");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
                  StringDeserializer.class.getName());
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
                  StringDeserializer.class.getName());
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");

        this.consumer = new KafkaConsumer<>(props);
    }

    public void start() {
        consumer.subscribe(Collections.singletonList("order-events"));

        try {
            while (true) {
                ConsumerRecords<String, String> records =
                    consumer.poll(Duration.ofSeconds(1));

                for (ConsumerRecord<String, String> record : records) {
                    try {
                        // 解析订单消息
                        ObjectMapper mapper = new ObjectMapper();
                        Order order = mapper.readValue(record.value(), Order.class);

                        // 发送通知
                        sendNotification(order);

                        // 处理成功,提交偏移量
                        consumer.commitSync();

                    } catch (Exception e) {
                        System.err.println("通知发送失败: " + e.getMessage());
                    }
                }
            }
        } finally {
            consumer.close();
        }
    }

    private void sendNotification(Order order) {
        // 发送邮件、短信、站内信等
        System.out.println("发送订单通知 - 用户: " + order.getUserId() +
                         ", 订单号: " + order.getOrderId());
    }

    public static void main(String[] args) {
        NotificationConsumer consumer = new NotificationConsumer();
        consumer.start();
    }
}

六、最佳实践

6.1 生产者配置

java 复制代码
// 生产环境推荐配置
Properties props = new Properties();

// 1. 多个Broker地址,防止单点故障
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
          "broker1:9092,broker2:9092,broker3:9092");

// 2. 使用acks=all确保消息不丢失
props.put(ProducerConfig.ACKS_CONFIG, "all");

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

// 4. 设置合理的重试次数
props.put(ProducerConfig.RETRIES_CONFIG, 3);

// 5. 批量发送配置
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 32768);
props.put(ProducerConfig.LINGER_MS_CONFIG, 20);

// 6. 压缩配置(推荐使用snappy或lz4)
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");

// 7. 缓冲区配置
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 67108864);

// 8. 超时配置
props.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, 30000);
props.put(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, 120000);

6.2 消费者配置

java 复制代码
// 生产环境推荐配置
Properties props = new Properties();

// 1. 多个Broker地址
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
          "broker1:9092,broker2:9092,broker3:9092");

// 2. 关闭自动提交,改为手动提交
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");

// 3. 设置合理的超时时间
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 45000);
props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, 15000);
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 300000);

// 4. 单次拉取数量
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 500);

// 5. 偏移量重置策略
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");

// 6. 最小字节和最大字节配置
props.put(ConsumerConfig.FETCH_MIN_BYTES_CONFIG, 1024);
props.put(ConsumerConfig.FETCH_MAX_WAIT_MS_CONFIG, 500);

6.3 Topic配置

bash 复制代码
# 创建Topic的最佳实践
kafka-topics.sh --create \
  --bootstrap-server localhost:9092 \
  --topic order-events \
  --partitions 3 \
  --replication-factor 2 \
  --config retention.ms=604800000 \
  --config segment.bytes=1073741824 \
  --config cleanup.policy=delete

# 配置说明:
# --partitions: 分区数,建议至少3个,可根据消费者数量调整
# --replication-factor: 副本因子,生产环境建议至少2
# retention.ms: 消息保留时间(7天)
# segment.bytes: 分段大小(1GB)
# cleanup.policy: 清理策略(delete或compact)

6.4 监控指标

生产者监控指标:

  • record-send-rate: 消息发送速率
  • record-error-rate: 消息发送错误率
  • request-latency-avg: 请求平均延迟
  • io-wait-time-ns-avg: IO等待时间

消费者监控指标:

  • records-consumed-rate: 消息消费速率
  • records-lag-max: 最大消息延迟(积压量)
  • commit-latency-avg: 提交偏移量平均延迟
  • heartbeat-rate: 心跳速率

Broker监控指标:

  • UnderReplicatedPartitions: 副本不足的分区数
  • ActiveControllerCount: 活跃的Controller数量
  • RequestHandlerAvgIdlePercent: 请求处理平均空闲率

七、总结

Kafka作为一款强大的分布式消息系统,在现代微服务架构中扮演着重要角色。本文主要介绍了:

  1. Kafka的核心概念和架构设计
  2. 生产者和消费者的配置和使用
  3. 三种消息传递语义的实现方式
  4. 完整的Java代码示例
  5. 最佳实践
相关推荐
indexsunny4 小时前
互联网大厂Java面试实战:Spring Boot与微服务在电商场景的应用解析
java·spring boot·redis·微服务·kafka·gradle·maven
小辉笔记5 小时前
kafka原理总结
分布式·kafka
潇凝子潇1 天前
kafka之监控告警
分布式·kafka
潇凝子潇1 天前
Kafka 实现集群安全认证与加密机制
分布式·安全·kafka
indexsunny1 天前
互联网大厂Java求职面试实战:微服务与Spring Boot在电商场景中的应用
java·数据库·spring boot·微服务·kafka·hibernate·电商
潇凝子潇1 天前
Apache Kafka 跨集群复制实现方案
分布式·kafka·apache
oMcLin2 天前
如何在Oracle Linux 8.4上搭建并优化Kafka集群,确保高吞吐量的实时数据流处理与消息传递?
linux·oracle·kafka
码农水水2 天前
中国邮政Java面试:热点Key的探测和本地缓存方案
java·开发语言·windows·缓存·面试·职场和发展·kafka
掘金-我是哪吒2 天前
Kafka配套的Zookeeper启动脚本
分布式·zookeeper·云原生·kafka