springboot + kafka (二)kafka-client队列消息的发送和监听

本文介绍如何使用Kafka-Client版本1.0.2接入腾讯ckafka,并在SpringBoot应用中配置producer和consumer,实现消息的发送与监听。通过自定义配置文件,演示Restful方式发送消息及监听消费消息的方法。

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

1.kafka-client介绍

之前的博客单个队列消息的发送和监听是springboot+sping-kafka的单个队列的发送和监听。最近项目接到一个需求,就是接入腾讯ckafka,而ckafka最高支持到kafka版本V1.1.1。于是便使用了kafka-client的版本1.0.2,便于接入ckafka。而kafka-client是kafka官方调用kafka的客户端版本。

2.自定义配置文件

2.1 配置文件

此处为yml自定义配置文件,一些个性化需求可以修改。

kafka:
  #=============== provider  =======================
  bootstrap-servers: 127.0.0.1:9092
  session-out: 30000
  retry: 3
  # 每次批量发送消息的数量
  batch-size: 232840
  buffer-memory: 33554432
  username:
  password:
  ##=============== consumer  =======================
  consumer-groupId: user-group-0
  auto-commit-interval: 5000
  enable.auto.commit: true

2.2 配置producer 和consumer

此处由于调试的时候使用了sasl安全方式,此处方便两种方式调用,由于作者水平有限,可以自定义配置。字段意义后续博客讲解。此文主要介绍接入springboot,并启动时启动监听。

  • 引入自定义配置
    @Value("${kafka.bootstrap-servers}")
    private String servers;
    @Value("${kafka.session-out}")
    private String sessionOut;
    @Value("${kafka.retry}")
    private int retry;
    @Value("${kafka.batch-size}")
    private int batchSize;
    @Value("${kafka.buffer-memory}")
    private String bufferMemory;
    @Value("${kafka.password}")
    private String kafkaPassword;
    @Value("${kafka.username}")
    private String kafkaUserName;
    @Value("${kafka.enable.auto.commit}")
    private String enableAutoCommit;

    //    --------------consumer-----------------
    @Value("${kafka.consumer-groupId}")
    private String groupId;
    @Value("${kafka.auto-commit-interval}")
    private String autoCommitInterval;
  • 配置consumer
    @Bean
    @Scope("prototype")
    public KafkaConsumer kafkaConsumer() {
        Properties props = new Properties();
        props.put("bootstrap.servers", servers);
        props.put("group.id", groupId);
        props.put("auto.commit.interval.ms", autoCommitInterval);
        props.put("session.timeout.ms", sessionOut);
        if (!StringUtils.isEmpty(kafkaUserName) && !StringUtils.isEmpty(kafkaPassword)) {
            props.put("sasl.jaas.config",
                    "org.apache.kafka.common.security.plain.PlainLoginModule required username=" + kafkaUserName + " password=" + kafkaPassword + ";");
            props.put("authorizer.class.name", "kafka.security.auth.SimpleAclAuthorizer");
            props.put("security.protocol", "SASL_PLAINTEXT");
            props.put("sasl.mechanism", "PLAIN");
        }

        props.put("enable.auto.commit", "true");
        props.put("auto.offset.reset", "earliest");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        return new KafkaConsumer(props);
    }
  • 配置producer
    @Bean
    public KafkaProducer kafkaProducer() {
        Properties props = new Properties();
        props.put("bootstrap.servers", servers);
        props.put("session.timeout.ms", sessionOut);
        props.put("retries", retry);
        props.put("buffer.memory", bufferMemory);
        props.put("batch.size", batchSize);
        if (!StringUtils.isEmpty(kafkaUserName) && !StringUtils.isEmpty(kafkaPassword)) {
            props.put("sasl.jaas.config",
                    "org.apache.kafka.common.security.plain.PlainLoginModule required username=" + kafkaUserName + " password=" + kafkaPassword + ";");
            props.put("security.protocol", "SASL_PLAINTEXT");
            props.put("sasl.mechanism", "PLAIN");
            props.put("authorizer.class.name", "kafka.security.auth.SimpleAclAuthorizer");
        }

        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("acks", "1");
        props.put("linger.ms", 10);
        props.put("max.block.ms", 3000);
        return new KafkaProducer(props);

    }

}

3. Restful 方式发送消息

由于要多次发送消息,保证kafkaPruducer不关闭,此处注掉close。此处简单示例,正确开发应该使用controller对外暴露接口

   @Autowired
   private KafkaProducer kafkaProducer;

   @RequestMapping("/send/{topic}/{message}")
   public String sendMessage(@PathVariable("topic") String topic, @PathVariable("message") String message) {
       asynSendRecord(topic, message);
       return message;
   }

   //异步发送消息
   public void asynSendRecord(String topic, Object message) {
       ProducerRecord<String, Object> record = new ProducerRecord<>(topic, message);
       log.info("record:" + record.value());
       kafkaProducer.send(record, (recordMetadata, e) -> {
           if (e == null) {
               log.info("消息发送: offset: {} timestamp:{}  topic:{}  partition: {} ", recordMetadata.offset(), recordMetadata.timestamp(), recordMetadata.topic(), recordMetadata.partition());
               log.info("消息发送成功");
           } else {
               log.error(String.format("消息发送失败: {}", e.getMessage()));
           }
       });

	// kafkaProducer.close();
   }

4. 监听消费messages

    @Autowired
    private KafkaMessageService kafkaMessageService;

    public void onMessage(KafkaConsumer kafkaConsumer, List<String> topic) {
        kafkaConsumer.subscribe(topic);
        log.info("队列开始监听:topic {}", topic);
        try {
            while (true) {
                ConsumerRecords<String, String> records = kafkaConsumer.poll(1000);
                for (ConsumerRecord<String, String> record : records) {
                    log.info("partition:{} offset = {}, key = {}, value = {}",record.partition(), record.offset(), record.key(), record.value());
                    try {
                        String messageData = new String(record.value().getBytes(), StandardCharsets.UTF_8);
                        log.info("{}解析处理内容为:{}", LOGGER_MSG, messageData);
                        handle(record.topic(), messageData);
                    } catch (Exception e) {
                        log.error("消息处理异常");
                    }
                }
            }
        } finally {
		// kafkaConsumer.close();
        }
    }

5.SpringBoot 启动时加载监听

5.1 Consumer和Listener对象注入

此处实现 org.springframework.boot.ApplicationRunner 接口,在 重写run方法中,启动监听topic的功能。
当考虑到启动监听的时候我是这样注入spring 消费服务和监听业务方法的。

    @Autowired
    KafkaListenMessageHandler kafkaInvoiceHandler;
    @Autowired
    KafkaConsumer kafkaConsumer;

后来由于业务中有多个topic,而我又都写入到一个Handler中,此处需要原型模式来实现多实例。所以在 KafkaConsumer和KafkaListenMessageHandler中添加注解 @Scope(“prototype”),KafkaListenMessageHandler就是监听消费的实体对象。

  • KafkaConsumer对象
  	@Bean
    @Scope("prototype")
    public KafkaConsumer kafkaConsumer() {...}
  • KafkaListenMessageHandler对象
  	@Bean
    @Scope("prototype")
	public class KafkaListenMessageHandler {...}

5.2 线程启动时调用Listener

一开始我是这样做的,k-a,k-b就是需要监听的队列。而启动的时候kafkaConsumer和kafkaInvoiceHandler也应该是多实例。以下为错误示例:

new Thread(() -> kafkaListenMessageHandler.onMessage(kafkaConsumer,Arrays.asList("k-a"))).start();
new Thread(() -> kafkaListenMessageHandler.onMessage(kafkaConsumer,Arrays.asList("k-b"))).start();

后续感觉使用线程池比较好,修改成了线程池模式。线程的个数可以根据业务调整。

public static ExecutorService executorService = Executors.newFixedThreadPool(2);
@Override
public void run(ApplicationArguments args) {
    log.info("监听服务启动!");
    executorService.execute(() -> {
        KafkaListenMessageHandler kafkaListenMessageHandler = SpringBeanUtils.getBean(KafkaListenMessageHandler.class);
        kafkaListenMessageHandler.onMessage(SpringBeanUtils.getBean("kafkaConsumer"), Arrays.asList("k-a"));
    });
    executorService.execute(() -> {
        KafkaListenMessageHandler kafkaListenMessageHandler = SpringBeanUtils.getBean("kafkaInvoiceHandler");
        kafkaListenMessageHandler.onMessage(SpringBeanUtils.getBean("kafkaConsumer"), Arrays.asList("k-b"));
    });
}

5.3 获取对象SpringUtils#getBean方法

@SuppressWarnings("unchecked")
@Component
public class SpringBeanUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringBeanUtils.applicationContext = applicationContext;
    }
    public static <T> T getBean(String beanName) {
        if (applicationContext.containsBean(beanName)) {
            return (T) applicationContext.getBean(beanName);
        } else {
            return null;
        }
    }
    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }
}

以上为所有内容,由于比较晚,许多内容没有详细解释,如有疑问,可以评论。下文第三篇准备完成上文承诺的多队列监听消费。第四篇准备完成队列的自动创建和config的修改。

### Spring Boot 整合 Flume, Kafka Spark Streaming 架构设计 #### 1. 数据流概述 在该架构中,Flume 被用于收集来自不同源头的日志数据并将其传输到 Kafka 中。Kafka 则作为一个高吞吐量的消息队列来存储这些日志消息直到被处理。而 Spark Streaming 扮演着消费者的角色,它会订阅特定主题下的消息来进行实时分析工作[^1]。 #### 2. 组件间交互流程描述 - **Flume Agent**: 配置好 source、channel 及 sink 后启动 agent 实例;source 接收应用程序产生的原始日志文件或其他形式输入的数据,并通过 channel 发送到指定位置即 Kafka 的 topic 上。 - **Kafka Broker**: 创建相应的 topics 来接收由 flume 发送过来的信息片段,在此期间可以设置副本因子以及分区数量以提高系统的可靠性性能表现[^5]。 - **Spring Boot Application with Embedded Consumer Logic**: - 使用 `@KafkaListener` 注解监听目标 Topic 并消费其中的内容; - 对获取到的消息执行初步过滤或转换操作以便后续传递给 spark streaming 进行更深入地加工处理。 - **Spark Streaming Job Submission via REST API Provided by Spring Boot App** - 开发者可以在 spring boot 应用程序内部定义一套 restful api ,允许外部调用来提交新的 spark job 或管理现有作业的状态 (start/stop/retrieve logs etc.) ; - 当收到请求时,则按照预设参数构建命令字符串并通过 ProcessBuilder 类型对象去运行实际的 shell 命令完成任务部署过程[^3]。 ```bash # Example of submitting a Spark Streaming application through command line which could be invoked programmatically within the Spring Boot app. ./spark-submit \ --class com.example.SparkJobClass \ --master yarn-client \ --num-executors 4 \ --executor-memory 2G \ --total-executor-cores 8 \ /path/to/application-jar-file.jar \ <args> ``` #### 3. 技术选型考量因素 为了确保整个体系结构具备良好的扩展能力与维护便利性,在技术栈的选择方面需综合考虑如下几个维度: - **兼容性**:所选用的技术组件之间是否存在良好协作关系?比如版本匹配度如何? - **社区支持程度**:是否有活跃开发者群体提供帮助文档技术指导资源? - **成本效益比**:硬件设施投入产出比例是否合理? ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值