kafka多线程消费

本文详细介绍Kafka集群的搭建过程,包括Zookeeper集群的配置,以及如何使用多线程进行高效的消息消费。同时,提供了从Zookeeper获取offset的方法,确保消息消费的顺序性和效率。此外,还展示了如何通过修改pom.xml文件引入Kafka和Fastjson依赖。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、zookeeper集群搭建:zookeeper安装以及使用_燕少༒江湖的博客-优快云博客_zookeeper

2、kafka集群搭建:kafka集群搭建以及遇到的异常_燕少༒江湖的博客-优快云博客

3、kafka生成消息:kafka-producer生产者案例_燕少༒江湖的博客-优快云博客_kafkaproducer单例

4、kafka多线程消费:offset从zookeeper中得到,一个线程对应一个partition,这样消费速度很快,而且消息的顺序可控,线程数量和partition一样,多了浪费资源,少了效率很低,也可以不通过zookeeper来消费,kafka0.9以后的版本就可以将offset记录到对应消费group到对应的broker上。

5、pom.xml

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

    <groupId>com.cn.dl</groupId>
    <artifactId>kafka-consumer1</artifactId>
    <version>1.0</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka_2.10</artifactId>
            <version>0.8.2.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.43</version>
        </dependency>
    </dependencies>

</project>

6、KafkaConsumterMain

package com.dl.cn;

import java.io.IOException;
import java.util.Properties;

/**
 * Created by tiger on 2018/8/20.
 */
public class KafkaConsumterMain {
    public static void main(String[] args) throws IOException {
        String topic = "user-info";
        int threadNum = 2;
        Properties properties = ReadPropertiesUtils.readConfig("config.properties");
        KafkaConsumterServer kafkaConsumterDemo = new KafkaConsumterServer(topic,threadNum,properties);
        kafkaConsumterDemo.consumer();
    }
}

7、KafkaConsumterServer

package com.dl.cn;

import kafka.consumer.ConsumerConfig;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by tiger on 2018/8/20.
 */
public class KafkaConsumterServer {
    private String topic;
    private Properties properties;
    private int threadNum;

    public KafkaConsumterServer(String topic,int threadNum,Properties properties) {
        this.topic = topic;
        this.threadNum = threadNum;
        this.properties = properties;
    }
    /**
     * 创建固定线程池消费消息
     * 线程和partition一对一
     * */
    public void consumer() {
        Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
        topicCountMap.put(topic, new Integer(threadNum));
        ConsumerConfig consumerConfig = new ConsumerConfig(properties);
        ConsumerConnector consumer = kafka.consumer.Consumer.createJavaConsumerConnector(consumerConfig);
        Map<String, List<KafkaStream<byte[], byte[]>>> consumerMap = consumer.createMessageStreams(topicCountMap);
        List<KafkaStream<byte[], byte[]>> streams = consumerMap.get(topic);
        //创建固定数量的线程池
        ExecutorService executor = Executors.newFixedThreadPool(threadNum);
        for (KafkaStream stream : streams) {
            executor.submit(new KafkaConsumerThread(stream));
        }
    }
}

8、KafkaConsumerThread

package com.dl.cn;

import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
import kafka.message.MessageAndMetadata;

/**
 * Created by tiger on 2018/8/20.
 */
public class KafkaConsumerThread implements Runnable{
    private KafkaStream<byte[], byte[]> stream;

    public KafkaConsumerThread(KafkaStream<byte[], byte[]> stream) {
        this.stream = stream;
    }

    @Override
    public void run() {
        ConsumerIterator<byte[], byte[]> it = stream.iterator();
        while (it.hasNext()) {
            MessageAndMetadata<byte[], byte[]> mam = it.next();
            System.out.println(Thread.currentThread().getName() + ">>>partition[" + mam.partition() + "],"
                    + "offset[" + mam.offset() + "], " + new String(mam.message()));
        }
    }
}

9、ReadPropertiesUtils

package com.dl.cn;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * Created by tiger on 2018/8/18.
 */
public class ReadPropertiesUtils {
    /**
     * 读取properties配置文件
     * @param configFileName
     * @exception
     * @return
     * */
    public static Properties readConfig(String configFileName) throws IOException {
        Map<String,String> config = new HashMap<String, String>();
        InputStream in = ReadPropertiesUtils.class.getClassLoader().getResourceAsStream(configFileName);
        Properties properties = new Properties();
        properties.load(in);
        return properties;
    }
}

10、config.properties

11、测试结果:

线程1对应partition0,线程2对应partition1,两者互不干扰

12、

auto.offset.reset=smallest,意思是从topic最早数据开始消费
auto.offset.reset=largest,是从topic最新数据开始消费

在zk中可以看到消费组

比如在代码中用到tiger7777这个消费者组

在代码中看到线程2最后消费的消息offset=1755

线程1最后消费的消息offset=2243

zookeeper中记录的offset值

生产者不断生产数据,消费者不断消费数据

将tiger7777,中partition对应的offset的值更新为200,然后重新启动

消费者,发现消息从offset=200开始重新消费,而且发现只有一个线程在继续消费

<think>好的,我现在需要回答用户关于在Spring Boot中实现Kafka多线程消费的最佳实践。首先,我得回忆一下Kafka消费者的基本工作原理以及Spring Boot中的相关配置。Kafka消费者默认是单线程的,但有时候处理消息需要更高的吞吐量,这时候多线程消费就显得重要了。 用户提到的是Spring Boot,所以应该考虑Spring Kafka库。我记得Spring Kafka提供了ConcurrentKafkaListenerContainerFactory,这个可以用来配置并发消费者。可能需要设置并发数,比如通过concurrency属性来指定线程数。比如,如果设置concurrency=3,就会有3个消费者实例同时运行,每个在独立的线程中。这一点应该作为第一个步骤。 接下来,分区和消费者线程的关系。Kafka的一个分区只能被同一个消费者组中的一个消费消费。所以,如果主题的分区数是3,而设置并发数为3,这样每个线程处理一个分区,效率应该不错。用户提供的引用[2]中提到了创建分区数为3的Topic,这可能是一个例子,说明需要根据分区数来调整并发数,确保不超过分区数,否则会有线程空闲。这点需要注意,并可能需要提醒用户根据实际情况调整分区数和消费者线程数。 然后是消费者的配置,比如是否手动提交偏移量,自动提交可能带来重复消费或者丢失消息的问题。如果处理消息的时间较长,可能建议使用手动提交,确保消息处理完成后再提交偏移量。这需要配置enable.auto.commit为false,并设置AckMode为MANUAL或MANUAL_IMMEDIATE。这部分需要检查Spring Kafka的文档,确认配置方式。 另外,线程安全问题。KafkaConsumer本身不是线程安全的,所以每个线程必须有自己的KafkaConsumer实例。好在Spring Kafka的ConcurrentMessageListenerContainer已经处理了这一点,每个并发消费者都有自己的实例,所以用户不需要自己处理线程安全问题。这一点需要强调,避免用户自己尝试共享消费者实例导致问题。 还有错误处理,比如消息处理失败时的重试机制。可以使用RetryTemplate或者SeekToCurrentErrorHandler。RetryTemplate可以配置重试次数和退避策略,而SeekToCurrentErrorHandler会在重试失败后重新定位偏移量,重新消费消息。这部分可能需要详细说明如何配置,或者给出示例代码。 另外,消费者的poll超时时间、max.poll.records等参数也可能影响多线程消费的性能。比如,max.poll.records控制每次poll请求返回的最大记录数,如果处理每条消息耗时较长,可能需要调低这个值,避免处理超时导致消费者被踢出组。这部分参数也需要根据实际情况调整。 用户提供的引用[1]和引用[2]中,引用[2]是关于创建Topic的配置,而引用[1]看起来是关于KafkaTemplate的,可能和生产者有关,但用户的问题是关于消费者的,所以可能需要忽略引用[1],或者确认是否有相关内容。不过用户的问题明确是关于消费者的,所以可能主要参考引用[2]中的Topic配置,确保分区足够多,以支持多线程消费。 最后,总结最佳实践步骤:配置ConcurrentKafkaListenerContainerFactory,设置合适的并发数,调整分区数,配置手动提交和错误处理,注意参数调优。同时,给出示例代码,展示如何配置这些参数,比如在@KafkaListener注解中使用concurrency,或者在配置类中定义ContainerFactory。 可能还需要提到使用@Async或者其他异步处理机制,但需要注意与Spring Kafka的集成,比如在监听器方法中异步处理消息,但这样可能需要自己管理线程池,并确保偏移量提交的正确性。不过这可能属于更高级的用法,最佳实践可能还是推荐使用ConcurrentKafkaListenerContainerFactory的并发配置。 需要检查是否有遗漏的步骤或配置,比如消费者的反序列化器、groupId的配置,以及可能需要的其他Kafka属性,比如bootstrap.servers等,但通常在Spring Boot中这些可以通过application.properties配置,不需要在代码中显式设置,除非有特殊需求。 综上所述,回答应该分步骤,说明配置并发消费者、分区数调整、提交方式、错误处理、参数调优等,并给出代码示例,引用相关的引用内容,比如创建Topic的部分,以说明分区数的重要性。</think>在Spring Boot中实现Kafka多线程消费的核心在于利用`ConcurrentKafkaListenerContainerFactory`配置并发消费者,并结合合理的分区分配和线程管理。以下是分步指南和最佳实践: --- ### **1. 配置并发消费者** 通过`ConcurrentKafkaListenerContainerFactory`设置`concurrency`参数,指定消费者线程数。例如,若Topic有3个分区,可配置3个线程以充分利用分区并行性: ```java @Bean public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() { ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(consumerFactory()); factory.setConcurrency(3); // 线程数不超过分区数[^2] return factory; } ``` --- ### **2. 分区数与线程数的匹配** - **分区数需≥线程数**:每个线程绑定一个分区以实现并行消费。若分区不足,部分线程将闲置。可通过代码动态创建Topic并指定分区数: ```java @Bean public NewTopic initialTopic() { return new NewTopic("topic1", 3, (short) 1); // 3个分区 } ``` --- ### **3. 手动提交偏移量** 为避免消息丢失或重复消费,建议关闭自动提交并手动管理偏移量: ```properties # application.properties spring.kafka.consumer.enable-auto-commit=false spring.kafka.listener.ack-mode=MANUAL_IMMEDIATE ``` 监听器中手动提交: ```java @KafkaListener(topics = "topic1") public void listen(ConsumerRecord<String, String> record, Acknowledgment ack) { processMessage(record); ack.acknowledge(); // 手动提交 } ``` --- ### **4. 错误处理与重试机制** - **重试策略**:通过`RetryTemplate`定义重试逻辑: ```java factory.setRetryTemplate(new RetryTemplate()); ``` - **死信队列(DLQ)**:处理多次重试失败的消息: ```java factory.setRecoveryCallback(context -> { sendToDlq(context.getAttribute("record")); return null; }); ``` --- ### **5. 线程安全与资源隔离** - **每个线程独立Consumer实例**:Spring Kafka自动为每个线程创建独立的`KafkaConsumer`,无需额外处理线程安全问题。 - **避免阻塞操作**:若消息处理耗时较长,可结合`@Async`异步处理,但需确保偏移量提交的准确性。 --- ### **6. 性能调优参数** 在`application.properties`中优化参数: ```properties spring.kafka.consumer.max-poll-records=500 # 单次拉取最大消息数 spring.kafka.consumer.max-poll-interval-ms=300000 # 处理消息的最大时间窗口 spring.kafka.consumer.fetch-max-wait-ms=500 # 拉取请求等待时间 ``` --- ### **代码示例** 完整配置类示例: ```java @Configuration public class KafkaConfig { @Bean public ConsumerFactory<String, String> consumerFactory() { Map<String, Object> props = new HashMap<>(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(ConsumerConfig.GROUP_ID_CONFIG, "group1"); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); return new DefaultKafkaConsumerFactory<>(props); } @Bean public ConcurrentKafkaListenerContainerFactory<String, String> containerFactory() { ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(consumerFactory()); factory.setConcurrency(3); factory.getContainerProperties().setAckMode(AckMode.MANUAL_IMMEDIATE); return factory; } } ``` ---
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

燕少༒江湖

给我一份鼓励!谢谢!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值