Springboot快速整合Kafka consumer方案

前言

最近搞了一个多线程灵活配置kafka消费链接的演示demo,过程还比较有意思,由于有一阵子没搞kafka了简单记录一下。

@KafkaListener

在类或方法上使用@KafkaListener注解定义消费者是最简单明了的一种方式,适用于处理简单的单个topic或者少量topic的情况,毕竟使用注解无法灵活扩展消费者。

虽然topic支持从配置文件中读取,且支持多个topic读取,但在某些场景下比较笨重,比如需要根据前端交互增加kafka消费者,使用注解显然实现不了,不可能前端注册一个kafka链接后端服务新生成一个class,在这个场景下用这方案在技术评审上会被喷死。

typescript 复制代码
@Component
public class KafkaConsumer {

    @KafkaListener(topics = "myTopic",  groupId = "test1")
    public void consume(String message) {
        System.out.println("Received message: " + message);
    }
}

这种比较适合模块与模块之间,或者服务之间,约定好topic建立通信的场景,比较简单且实用。

KafkaConsumer

根据刚才的场景,产品交互上有一个注册kafka链接的地方,前端填写完注册信息后传递给后端,后端需要生成一个kafka消费者不断监听数据,这种场景如何实现。

首先有多个用户创建消费链接,显然是一个多线程的场景,我们需要一个池子去管理kafka的链接,又是多线程又是池化,首先想到的线程池去管理,我们把kafka的消费链接和client做成线程私有的,启动的时候用线程池提交,这样线程池中的每一个线程都是一个kafka client。

我们使用apache.kafka包提供的现成的org.apache.kafka.clients.consumer.KafkaConsumer

xml 复制代码
 <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
        <version>2.4.0</version>
</dependency>

首先定义一个class实现Runable接口,每次一提Runable就想起校招的时候问的最多的就是实现多线程有哪几种方式。。。。

typescript 复制代码
@Slf4j
@Component
public class KafkaConsumerTask implements Runnable {

    // 每个线程维护私有的KafkaConsumer实例
    @Getter
    private KafkaConsumer<String, String> consumer;

    @Getter
    private String topicName;


    /**
     * 封装必要信息
     * @param bootServer 生产者ip
     * @param groupId 分组信息
     * @param topic  订阅主题
     */
    public KafkaConsumerRunnable(String bootServer, String groupId, String topic) {
        topicName = topic;
        Properties props = new Properties();
        props.put("bootstrap.servers", bootServer);
        props.put("group.id", groupId);
        props.put("enable.auto.commit", "false");
        props.put("auto.commit.interval.ms", "100");  //自动提交时间间隔 毫秒单位
        props.put("session.timeout.ms", "30000");
        props.put("auto.offset.reset", "latest");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");  //键反序列化方式
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        this.consumer = new KafkaConsumer<>(props);
    }


    @Override
    public void run() {
        consumer.subscribe(Arrays.asList(topicName));
        Thread.currentThread().setName(topicName);
        try {
            while (true) {
                ConsumerRecords<String, String> records = consumer.poll(100);   // 本例使用100ms作为获取超时时间
                for (ConsumerRecord<String, String> record : records) {
                    String value = record.value();
                    log.info("线程 {} 消费kafka数据 -> {} \n 偏移量offset -> {} \n 分区partition -> {}",
                    
                    Thread.currentThread().getName(), value, record.offset(), record.partition());
                    
                    // 2、反序列化数据
                    HashMap resMap = JSON.parseObject(value, HashMap.class);
                    log.info("resMap -> {}", resMap);
                }
            }
        } catch (WakeupException) {
            log.info("consumer ");
        } catch (Exception e) {
            throw new HoraException("数据消费异常 -> ", e);
        } finally {
           consumer.commitAsync();
        }
    }

    // 退出后关掉consumer
    public void shutDown() {
        consumer.wakeup();
    }
}

代码贴出来结构就很明朗了,consumer每个线程私有变量,通过构造方法传参的方式初始化consumer,run方法里while true 去常驻监听数据,当需要退出后外部调用shutDown() 方法,内部consumer.wakeup() 方法调用后consumer会抛出异常跳出while循环结束任务。

提交任务时可以用线程池去提交,也可以自己搞个map维护链接都行,由于是个简单的演示demo,细节做的都不到位,还需要考虑下offset的维护,consumer链接挂了之后如何重新拉起,数据如何不丢失等等等问题。

不过工作又不是面试,xdm,我说的对吗。

相关推荐
AskHarries2 分钟前
Java字节码增强库ByteBuddy
java·后端
许野平2 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
齐 飞3 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
LunarCod3 小时前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发
码农派大星。4 小时前
Spring Boot 配置文件
java·spring boot·后端
杜杜的man5 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*5 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
llllinuuu5 小时前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s5 小时前
Golang--协程和管道
开发语言·后端·golang
为什么这亚子5 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算