springboot+kafka中@KafkaListener如何动态指定多个topic

springboot+kafka中@KafkaListener如何动态指定多个topic

说明:本项目为springboot+kafak的整合项目,故其用了springboot中对kafak的消费注解@KafkaListener

首先,application.properties中配置用逗号隔开的多个topic。
在这里插入图片描述
方法:利用Spring的SpEl表达式,将topics 配置为:@KafkaListener(topics = “#{’${topics}’.split(’,’)}”)
在这里插入图片描述
运行程序,console打印的效果如下:
在这里插入图片描述
因为只开了一条消费者线程,所以所有的topic和分区都分配给这条线程。
如果你想开多条消费者线程去消费这些topic,添加@KafkaListener注解的参数concurrency的值为自己想要的消费者个数即可(注意,消费者数要小于等于你开的所有topic的分区数总和)
在这里插入图片描述
运行程序,console打印的效果如下:
在这里插入图片描述

总结一下大家问的最多的一个问题:

如何在程序运行的过程中,改变topic,消费者能够消费修改后的topic?

ans: 经过尝试,使用@KafkaListener注解实现不了此需求,在程序启动的时候,程序就会根据@KafkaListener的注解信息初始化好消费者去消费指定好的topic。如果在程序运行的过程中,修改topic,不会让此消费者修改消费者的配置再重新订阅topic的。

不过我们可以有个折中的办法,就是利用@KafkaListener的topicPattern参数来进行topic匹配。
具体如何操作的可以看下这位老哥的blog:
https://blog.youkuaiyun.com/songzehao/article/details/103091486

终极方法

思路:

  1. 不使用@KafkaListener,使用kafka原生客户端依赖,手动初始化消费者,开启消费者线程。
  2. 在消费者线程中,每次循环都从配置、数据库或者其他配置源获取最新的topic信息,与之前的topic比较,如果发生变化,重新订阅topic或者初始化消费者。

实现

  1. 加入kafka客户端依赖(本次测试服务端kafka版本:2.12-2.4.0)

    <dependency>
    	<groupId>org.apache.kafka</groupId>
    	<artifactId>kafka-clients</artifactId>
    	<version>2.3.0</version>
    </dependency>
    
  2. 代码

    @Service
    @Slf4j
    public class KafkaConsumers implements InitializingBean {
    
        /**
         * 消费者
         */
        private static KafkaConsumer<String, String> consumer;
        /**
         * topic
         */
        private List<String> topicList;
    
        public static String getNewTopic() {
            try {
                return org.apache.commons.io.FileUtils.readLines(new File("D:/topic.txt"), "utf-8").get(0);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 初始化消费者(配置写死是为了快速测试,请大家使用配置文件)
         *
         * @param topicList
         * @return
         */
        public KafkaConsumer<String, String> getInitConsumer(List<String> topicList) {
            //配置信息
            Properties props = new Properties();
            //kafka服务器地址
            props.put("bootstrap.servers", "192.168.9.185:9092");
            //必须指定消费者组
            props.put("group.id", "haha");
            //设置数据key和value的序列化处理类
            props.put("key.deserializer", StringDeserializer.class);
            props.put("value.deserializer", StringDeserializer.class);
            //创建消息者实例
            KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
            //订阅topic的消息
            consumer.subscribe(topicList);
            return consumer;
        }
    
        /**
         * 开启消费者线程
         * 异常请自己根据需求自己处理
         */
        @Override
        public void afterPropertiesSet() {
            // 初始化topic
            topicList = Splitter.on(",").splitToList(Objects.requireNonNull(getNewTopic()));
            if (org.apache.commons.collections.CollectionUtils.isNotEmpty(topicList)) {
                consumer = getInitConsumer(topicList);
                // 开启一个消费者线程
                new Thread(() -> {
                    while (true) {
                        // 模拟从配置源中获取最新的topic(字符串,逗号隔开)
                        final List<String> newTopic = Splitter.on(",").splitToList(Objects.requireNonNull(getNewTopic()));
                        // 如果topic发生变化
                        if (!topicList.equals(newTopic)) {
                            log.info("topic 发生变化:newTopic:{},oldTopic:{}-------------------------", newTopic, topicList);
                            // method one:重新订阅topic:
                            topicList = newTopic;
                            consumer.subscribe(newTopic);
                            // method two:关闭原来的消费者,重新初始化一个消费者
                            //consumer.close();
                            //topicList = newTopic;
                            //consumer = getInitConsumer(newTopic);
                            continue;
                        }
                        ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
                        for (ConsumerRecord<String, String> record : records) {
                            System.out.println("key:" + record.key() + "" + ",value:" + record.value());
                        }
                    }
                }).start();
            }
        }
    }
    
    

    说一下第72行代码:

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

    上面这行代码表示:在100ms内等待Kafka的broker返回数据.超市参数指定poll在多久之后可以返回,不管有没有可用的数据都要返回。

    在修改topic后,必须等到此次poll拉取的消息处理完,while(true)循环的时候检测topic发生变化,才能重新订阅topic.
    poll()方法一次拉取得消息数默认为:500,如下图,kafka客户端源码中设置的。
    在这里插入图片描述

    如果想自定义此配置,可在初始化消费者时加入
    在这里插入图片描述

  3. 运行结果(测试的topic中都无数据)
    在这里插入图片描述
    注意:KafkaConsumer是线程不安全的,不要用一个KafkaConsumer实例开启多个消费者,要开启多个消费者,需要new 多个KafkaConsumer实例。

    有什么问题欢迎提出来讨论和交流 q:281867465

<think>好的,我现在需要回答用户关于如何在Spring Kafka中使用@KafkaListener同时监听多个Topic的问题。首先,我需要回顾一下Spring Kafka的相关文档和示例,确保我的理解正确。 用户的问题很明确,他们想知道如何配置@KafkaListener来监听多个Topic。根据我之前的知识,@KafkaListener注解有一个topics参数,可以接受字符串数组,这样就可以指定多个Topic名称。例如,topics = {"topic1", "topic2"}。不过,我需要确认这一点是否正确,以及是否有其他方法可以实现。 接下来,我应该检查是否有其他参数或方法可以用于动态监听多个Topic,比如使用模式匹配或者从配置文件中读取Topic列表。例如,使用topicPattern参数来匹配符合正则表达式的Topic名称。这可能是一个更灵活的方法,特别是在Topic名称有规律变化的情况下。 另外,我需要考虑用户可能需要一个完整的示例,包括配置类和监听器方法的代码。可能需要提到如何在application.properties或application.yml中配置Kafka的相关属性,比如bootstrap servers、消费者组等。同时,要注意消费者组的配置,确保多个消费者实例正确工作。 我还应该提醒用户一些注意事项,比如确保Topic已经存在,或者Kafka配置正确,避免常见的错误。例如,如果消费者组ID没有正确设置,可能导致消息重复消费或无法消费的情况。此外,动态添加或移除Topic时的处理方式,可能需要更高级的配置或自定义逻辑。 现在,我需要确保所有提到的配置方法和示例代码都是正确的,并且符合最新的Spring Kafka版本。如果有不确定的地方,可能需要参考官方文档或可靠的来源。例如,引用中的内容提到@KafkaListener可以声明监听方法,并且支持多个Topic,这支持了我的思路。 最后,生成的相关问题需要涵盖用户可能进一步询问的方向,比如动态调整监听Topic、参数配置的细节或者异常处理的方法。这些问题应该有助于用户深入理解@KafkaListener的使用。 总结步骤: 1. 使用topics参数指定多个Topic名称。 2. 使用topicPattern参数通过正则表达式匹配多个Topic。 3. 提供配置类和监听器方法的代码示例。 4. 提到必要的Kafka配置项。 5. 注意事项和常见问题。 6. 生成相关问题,如动态调整、参数配置、异常处理等。</think>在 Spring Kafka 中,使用 `@KafkaListener` 监听多个 Topic 的配置方法如下: --- ### 1. **基础配置方法** 通过 `topics` 参数直接指定多个 Topic 名称,支持字符串数组: ```java @KafkaListener( topics = {"topic1", "topic2", "topic3"}, groupId = "my-consumer-group" ) public void listenMultipleTopics(ConsumerRecord<String, String> record) { System.out.println("Received message: " + record.value()); } ``` - **说明**:`topics` 接受多个 Topic 名称,消费者组 `groupId` 需统一[^1][^3]。 --- ### 2. **动态匹配 Topic(正则表达式)** 通过 `topicPattern` 参数使用正则表达式匹配多个 Topic: ```java @KafkaListener( topicPattern = "test.*", // 匹配以 "test" 开头的所有 Topic groupId = "pattern-group" ) public void listenPatternTopics(ConsumerRecord<String, String> record) { System.out.println("Matched topic: " + record.topic()); } ``` - **适用场景**:Topic 名称有固定前缀或规律性变化[^3]。 --- ### 3. **完整配置示例** #### 步骤 1:添加依赖(`pom.xml`) ```xml <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>2.8.5</version> </dependency> ``` #### 步骤 2:配置文件(`application.yml`) ```yaml spring: kafka: bootstrap-servers: localhost:9092 consumer: group-id: default-group auto-offset-reset: earliest key-deserializer: org.apache.kafka.common.serialization.StringDeserializer value-deserializer: org.apache.kafka.common.serialization.StringDeserializer ``` #### 步骤 3:监听器类 ```java @Component public class MultiTopicListener { @KafkaListener(topics = {"orders", "payments"}, groupId = "transaction-group") public void handleTransaction(ConsumerRecord<String, String> record) { // 处理业务逻辑 } @KafkaListener(topicPattern = "log_.+", groupId = "log-group") public void handleLogs(ConsumerRecord<String, String> record) { // 处理日志数据 } } ``` --- ### 4. **注意事项** - **Topic 存在性**:确保 Kafka 中已创建目标 Topic,否则会抛出 `UnknownTopicOrPartitionException`。 - **消费者组隔离**:不同监听方法建议使用不同 `groupId`,避免偏移量冲突。 - **并发配置**:通过 `concurrency` 参数设置并发消费者数量,如 `@KafkaListener(..., concurrency = 3)`。 --- ### 5. **动态增删 Topic 的高级用法** 若需运行时动态调整监听的 Topic,可结合 `KafkaListenerEndpointRegistry` 编程实现,例如: ```java @Autowired private KafkaListenerEndpointRegistry registry; public void updateTopics(List<String> newTopics) { registry.getListenerContainer("listenerId").pause(); // 暂停当前监听 // 更新 Topic 配置 registry.getListenerContainer("listenerId").resume(); // 重新启动 } ``` ---
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值