kafka动态配置topic

 之前使用@org.springframework.kafka.annotation.KafkaListener这个注解的时候,是在yml文件中配置,然后使用@KafkaListener(topics = {"${kafka.topic.a2b.name}"}),这样去单独监听某一个topic,生产者也固定在代码里定义变量读取配置文件。昨天改了个需求,希望以后通过配置文件去动态配置生产者和消费者的topic(不知道个数和topic名字),而不需要改代码。

一、踩坑

 刚开始的时候,由于考虑不充分(没有考虑到topic个数未知),想到@KafkaListener注解中的topics本身就是个字符串数组,于是想通过传入变量的形式。产生了以下两种方法:

1.传入变量方法一

  使用@Value注解提取配置文件中相关配置,@KafkaListener中传入变量

    public static String[] topicArr;
    @Value("${kafka.bootstrap.servers}")
    public void setTopicArr(String[] value){
        String topicArr = value;
    }
    @KafkaListener(topics= topicArr)

emmmm。。。结果可想而知,不行。

2.传入变量方法二

 还是传入变量,不过这次写了个动态配置的代码

    注解里这么写
    @KafkaListener(topics = "${topicName1}","${topicName2}","${topicName3}")
    提前将yml文件里添加
    topics: topicName1,topicName2,topicName3
    然后加载进来
    @Value("${kafka.topics}")
    public void setTopics(String value){
        topics = value;
    }
    动态配置代码:
    @Configuration
    public class KafkaTopicConfiguration implements InitializingBean {
        @Autowired
        private KafkaConfig kafkaconfig;
        @Override
        public void afterPropertiesSet() throws Exception {
            String[] topicArr = kafkaconfig.split(",");
            int i = 1;
            for(String topic : topicArr){
                String topicName = "topicName"+i;
                System.setProperty(topicName, topic);
            }
        }
    }

相比方法一,可行。但是未知topic数量呢。GG。

3.不用注解

 百度找到几个老哥的动态获取并创建topic的方法

https://www.cnblogs.com/gaoyawei/p/7723974.html
https://www.cnblogs.com/huxi2b/p/7040617.html
https://blog.csdn.net/qq_27232757/article/details/78970830

写了几版,各种各样的问题,还是我太菜。就想再看看有没有别的简单点的解决办法,没有了再回来搞这个。

4.正则匹配topic

 这期间又找到一个使用正则匹配topic的。直接贴链接

@KafkaListener(topicPattern = "showcase.*")
这里使用正则匹配topic,其中【*】之前得加上【.】才能匹配到。

中间模仿写了一版使用正则匹配的,其实也可以糊弄实现需求,除了topic取名的时候一定得规范以外,还得考虑到如果不想用某个topic了又得想怎么去避免他。
这种方法不太严谨,继续踩坑吧。

二、问题解决

 用蹩脚的英语google了一下,嗯?好多老哥们也是用的以上差不多的方法。然而最后在某个老哥github的issues中看到了解决办法。老哥的需求跟我差不多,感谢大佬,贴上最终问题解决方案。

1.kafka消费者监听配置

还是注解的形式
@KafkaListener(topics = "#{'${kafka.listener_topics}'.split(',')}")

读取yml文件中kafka.listener_topics的参数,然后根据“,”去split,得到一个topics数组。
这么做就可以根据配置文件动态的去监听topic。

2.yml配置文件

只列出topic相关部分(mqTypes是我用来判断使用哪个topic发送的)
    kafka:
      listener_topics: kafka-topic-a2b,kafka-topic-c2b
      consume:
        topic:
          - name: kafka-topic-a2b
            partitions: 12
            replication_factor: 2
          - name: kafka-topic-c2b
            partitions: 12
            replication_factor: 2
      product:
        topic:
          - name: kafka-topic-b2a
            partitions: 12
            replication_factor: 2
            mqTypes: type1
          - name: kafka-topic-b2c
            partitions: 12
            replication_factor: 2
            mqTypes: type1

3.yml参数解析

这里我将kafka的topic相关加载到bean中处理。
创建KafkaConsumerBean和KafkaProducerBean分别用来存储yml中生产者和消费者的topic相关参数

//KafkaConsumerBean
@Component
@ConfigurationProperties(prefix = "kafka.consume")
public class KafkaConsumerBean {
    private List<Map<String,String>> topic;
    public void setTopic(List<Map<String, String>> topic) {
        this.topic = topic;
    }
    public List<Map<String, String>> getTopic() {
        return topic;
    }
}

//KafkaProducerBean
@Component
@ConfigurationProperties(prefix = "kafka.product")
public class KafkaProducerBean {
    private List<Map<String,String>> topic;
    public void setTopic(List<Map<String, String>> topic) {
        this.topic = topic;
    }

    private Map<String,String> mqType2NameMap = new HashMap<String,String>();
    public List<Map<String, String>> getTopic() {
        return topic;
    }

    public String getTopic(String mqType){
        String name = mqType2NameMap.get(mqType);
        if(name != null){
            return name;
        }else{
            for(Map<String,String> topicProperty : topic){
                if (topicProperty.get("mqTypes").indexOf(mqType) >= 0){
                    name = topicProperty.get("name");
                    mqType2NameMap.put(mqType,name);
                    return name;
                }
            }
            return null;
        }

    }
}

4.创建topic

    List<Map<String,String>> producerTopicList = kafkaProducerBean.getTopic();
    for (Map<String,String> topicProperty : producerTopicList){
        KafkaClient.createTopic(topicProperty.get("name"),Integer.parseInt(topicProperty.get("partitions")),Integer.parseInt(topicProperty.get("replication_factor")));
    }
    List<Map<String,String>> consumerTopicList = kafkaConsumerBean.getTopic();
    for (Map<String,String> topicProperty : consumerTopicList){
        KafkaClient.createTopic(topicProperty.get("name"),Integer.parseInt(topicProperty.get("partitions")),Integer.parseInt(topicProperty.get("replication_factor")));
    }

三、总结

 上面解决问题的方法关键在于

@KafkaListener(topics = "#{'${kafka.listener_topics}'.split(',')}")

@KafkaListener这个注解会去读取spring的yml配置文件中

kafka:
      listener_topics: kafka-topic-a2b,kafka-topic-c2b

这块listener_topics配置信息,然后通过’,'分割成topic数组,KafkaListener注解中的 topics 参数,本身就是个数组,如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.kafka.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.messaging.handler.annotation.MessageMapping;

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@MessageMapping
@Documented
@Repeatable(KafkaListeners.class)
public @interface KafkaListener {
    String id() default "";

    String containerFactory() default "";

    String[] topics() default {};

    String topicPattern() default "";

    TopicPartition[] topicPartitions() default {};

    String group() default "";
}

 结合我之前的kafka文章,应该是可以拼出一套成型的。

猜你喜欢

转载自blog.csdn.net/weixin_43157082/article/details/86300101