为了账号安全,请及时绑定邮箱和手机立即绑定

Kafka重复消费项目实战教程

标签:
杂七杂八
概述

本文介绍了Kafka重复消费问题的产生原因及影响,提供了通过幂等性和事务支持来避免重复消费的解决方案。通过一个金融交易系统的案例,展示了如何在实践中应用这些解决方案,确保消息的唯一性与系统性能。文章还提供了项目调试与优化的方法,帮助读者构建一个稳定高效的Kafka重复消费项目实战。

Kafka简介与安装

Kafka是什么

Apache Kafka 是一种分布式的、高吞吐量的发布-订阅模型消息系统。它最初由LinkedIn公司开发,后来成为 Apache 顶级项目。Kafka 被设计用来处理实时数据流,包括日志聚合、监控数据收集和流处理等场景。

Kafka的特点与应用场景

主要特点

  • 高吞吐量:Kafka 能够处理每秒数百万条消息。
  • 持久性:消息被持久化在磁盘上,并且订阅者可以在任意时间点订阅。
  • 分布性:Kafka 支持多副本,可以在多个节点之间分发负载。
  • 可扩展性:Kafka 非常容易扩展,只需添加更多的代理(broker)即可。
  • 高可用性:通过复制消息到多个代理来实现数据的高可用性。

应用场景

  • 日志聚合:将应用程序或服务器的日志文件发送到 Kafka,进行集中处理。
  • 流处理:实时处理数据流,例如金融交易数据。
  • 网站活动跟踪:收集网站的用户行为数据,进行实时分析。
  • 运营监控:收集服务器的运行时间数据,进行实时监控。
  • 数据集成:从多个数据源收集数据,然后将其传递给多个消费者。

Kafka的安装与配置

安装步骤

  1. 下载 Kafka:从 Apache Kafka 官方网站下载最新版本的 Kafka。
  2. 安装 Java:Kafka 需要 Java 环境,确保已经安装了 Java。
  3. 配置环境变量:将 Kafka 的 bin 目录添加到 PATH 环境变量中。
  4. 启动 Kafka
    • 配置 Zookeeper(若未安装),并启动 Zookeeper。
    • 启动 Kafka 服务器。

示例代码

# 下载 Kafka
wget https://downloads.apache.org/kafka/2.8.0/kafka_2.13-2.8.0.tgz

# 解压文件
tar -xzf kafka_2.13-2.8.0.tgz

# 进入 Kafka 目录
cd kafka_2.13-2.8.0

# 启动 Zookeeper
bin/zookeeper-server-start.sh config/zookeeper.properties

# 启动 Kafka 服务器
bin/kafka-server-start.sh config/server.properties
Kafka核心概念讲解

主题(Topic)

主题(Topic)是 Kafka 中数据的一个分类,它是一个逻辑上的日志流名称。生产者向一个主题发送消息,消费者从这个主题中读取消息。例如,可以创建一个名为 user_logs 的主题来存放用户日志。

示例代码

# 创建主题
bin/kafka-topics.sh --create --topic user_logs --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1

消息(Message)

消息(Message)是发送到 Kafka 主题的最小数据单元。每条消息由一个键和一个值组成,通常键用于分区(Partition)和消息的排序。

生产者(Producer)

生产者(Producer)是往 Kafka 主题中发送消息的应用程序。生产者将消息发送到特定主题,并可以选择将消息发送到特定的分区。

示例代码

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

public class MessageProducer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);

        for (int i = 0; i < 100; i++) {
            producer.send(new ProducerRecord<String, String>("user_logs", Integer.toString(i), "Message " + Integer.toString(i)));
        }

        producer.close();
    }
}

消费者(Consumer)

消费者(Consumer)是从 Kafka 主题中读取消息的应用程序。消费者订阅一个或多个主题,并从这些主题中读取消息。

示例代码

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

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

public class MessageConsumer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "test");
        props.put("enable.auto.commit", "true");
        props.put("auto.commit.interval.ms", "1000");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);
        consumer.subscribe(Arrays.asList("user_logs"));

        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
            for (ConsumerRecord<String, String> record : records) {
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
            }
        }
    }
}
创建Kafka项目

准备开发环境

  1. 安装 Java:确保安装了 Java 8 或以上版本。
  2. 安装 Maven:使用 Maven 来管理项目依赖。
  3. 安装 IDE:推荐使用 IntelliJ IDEA 或 Eclipse。
  4. 配置 Kafka:确保 Kafka 已经正确安装并运行。
  5. 创建新的 Maven 项目:在 IDE 中创建一个新的 Maven 项目。

示例代码

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>kafkaExample</artifactId>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.apache.kafka</groupId>
      <artifactId>kafka-clients</artifactId>
      <version>2.8.0</version>
    </dependency>
  </dependencies>
</project>

创建一个简单的Kafka项目

  1. 创建生产者:编写一个简单的生产者类,向 Kafka 主题发送消息。
  2. 创建消费者:编写一个简单的消费者类,从 Kafka 主题读取消息。
  3. 创建启动类:编写一个启动类,启动生产者和消费者。

示例代码

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

public class SimpleProducer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);

        for (int i = 0; i < 100; i++) {
            producer.send(new ProducerRecord<String, String>("user_logs", Integer.toString(i), "Message " + Integer.toString(i)));
        }

        producer.close();
    }
}
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

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

public class SimpleConsumer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "test");
        props.put("enable.auto.commit", "true");
        props.put("auto.commit.interval.ms", "1000");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);
        consumer.subscribe(Arrays.asList("user_logs"));

        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
            for (ConsumerRecord<String, String> record : records) {
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
            }
        }
    }
}
public class KafkaExample {
    public static void main(String[] args) {
        Thread producerThread = new Thread(new Runnable() {
            @Override
            public void run() {
                SimpleProducer.main(new String[]{});
            }
        });

        Thread consumerThread = new Thread(new Runnable() {
            @Override
            public void run() {
                SimpleConsumer.main(new String[]{});
            }
        });

        producerThread.start();
        consumerThread.start();
    }
}
Kafka重复消费问题解析

重复消费产生的原因

重复消费通常发生在以下几种情况中:

  • 消费者重启:当消费者在接收到消息后还没有处理完消息就崩溃了,然后重启后会重新消费之前的消息。
  • 手动提交:如果消费者使用手动提交来控制消息的消费,但提交失败或延迟,可能导致消息被重复消费。
  • 消费者故障恢复:在某些情况下,消费者可能会在处理消息时发生故障,导致消息被重复处理。

重复消费带来的影响

  • 数据不一致:如果消息被重复处理,可能会导致数据的不一致性。
  • 性能下降:重复处理消息会增加系统负载,降低整体性能。
  • 资源浪费:重复处理消息会浪费服务器资源,比如 CPU 和内存。
解决Kafka重复消费问题的方法

通过幂等性实现重复消费保护

幂等性是指多次执行相同的操作,结果是一样的。在 Kafka 中,可以通过幂等生产者来避免重复消息的发送。

示例代码

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

public class IdempotentProducer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("enable.idempotence", "true"); // 启用幂等性

        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);

        for (int i = 0; i < 100; i++) {
            producer.send(new ProducerRecord<String, String>("user_logs", Integer.toString(i), "Message " + Integer.toString(i)));
        }

        producer.close();
    }
}

使用事务支持避免重复消费

Kafka 从 0.11 版本开始支持事务,事务可以帮助确保消息的一致性,避免消息被重复消费。

示例代码

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

public class TransactionalProducer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("transactional.id", "transaction-id"); // 设置事务ID

        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);

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

        try {
            producer.beginTransaction(); // 开始事务
            for (int i = 0; i < 100; i++) {
                producer.send(new ProducerRecord<String, String>("user_logs", Integer.toString(i), "Message " + Integer.toString(i)));
            }
            producer.commitTransaction(); // 提交事务
        } catch (Exception e) {
            producer.abortTransaction(); // 中断事务
        } finally {
            producer.close();
        }
    }
}

实践案例分析

假设有一个金融交易系统,需要确保每个交易只处理一次。如果一个交易被重复处理,可能会导致账户余额错误。通过使用幂等性或事务支持,可以避免这种情况。

使用幂等性

幂等性可以通过设置 enable.idempotence 属性来启用。这会确保同一个键的消息只会被发送一次。

使用事务支持

事务支持可以确保一组消息要么全部被提交,要么全部被回滚。这可以避免部分消息被提交而其他消息被回滚的情况。

示例代码

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

public class FinancialTransactions {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("transactional.id", "financial-transaction-id"); // 设置事务ID

        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);

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

        try {
            producer.beginTransaction(); // 开始事务
            for (int i = 0; i < 100; i++) {
                producer.send(new ProducerRecord<String, String>("financial_transactions", Integer.toString(i), "Transaction " + Integer.toString(i)));
            }
            producer.commitTransaction(); // 提交事务
        } catch (Exception e) {
            producer.abortTransaction(); // 中断事务
        } finally {
            producer.close();
        }
    }
}
项目实战演练

构建一个完整的Kafka重复消费项目案例

构建一个完整的 Kafka 重复消费项目案例,包括生产者、消费者和事务支持。

步骤

  1. 创建生产者:发送带有事务支持的金融交易消息。
  2. 创建消费者:消费金融交易消息,并确保消息只被处理一次。
  3. 测试重复消费:通过重启消费者来测试重复消费情况。

示例代码

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

public class FinancialTransactionsProducer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("transactional.id", "financial-transaction-id");

        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);

        producer.initTransactions();

        try {
            producer.beginTransaction();
            for (int i = 0; i < 100; i++) {
                producer.send(new ProducerRecord<String, String>("financial_transactions", Integer.toString(i), "Transaction " + Integer.toString(i)));
            }
            producer.commitTransaction();
        } catch (Exception e) {
            producer.abortTransaction();
        } finally {
            producer.close();
        }
    }
}
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

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

public class FinancialTransactionsConsumer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "financial-transactions-group");
        props.put("enable.auto.commit", "false");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);
        consumer.subscribe(Arrays.asList("financial_transactions"));

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

            for (ConsumerRecord<String, String> record : records) {
                handleTransaction(record);
            }

            consumer.commitSync();
        }
    }

    private static void handleTransaction(ConsumerRecord<String, String> record) {
        // 处理交易
        System.out.printf("Received transaction: key = %s, value = %s%n", record.key(), record.value());
    }
}

项目调试与优化

项目调试与优化是确保项目稳定运行和性能的重要步骤。

调试步骤

  1. 检查配置:确保 Kafka 和消费者的配置正确。
  2. 日志分析:检查日志文件,查找任何异常或错误。
  3. 测试重启:测试消费者在重启后的行为。

优化步骤

  1. 调整分区数:增加分区数可以提高吞吐量。
  2. 优化提交策略:调整提交策略,如使用批量提交。
  3. 资源监控:监控 CPU 和内存使用情况,确保资源使用合理。

示例代码


import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

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

public class OptimizedFinancialTransactionsConsumer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "financial-transactions-group");
        props.put("enable.auto.commit", "false");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        // 批量提交
        props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, "100");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);
        consumer.subscribe(Arrays.asList("financial_transactions"));

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

            for (ConsumerRecord<String, String> record : records) {
                handleTransaction(record);
            }

            consumer.commitSync();
        }
    }

    private static void handleTransaction(ConsumerRecord<String, String> record) {
        // 处理交易
        System.out.printf("Received transaction: key = %s, value = %s%n", record.key(), record.value());
    }
}
``

通过以上步骤,可以构建、调试和优化一个完整的 Kafka 重复消费项目,确保消息的一致性和系统性能。
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消