springboot kafka 动态创建 topic 批量消费监听

该博客介绍了一种使用SpringBoot动态监听Kafka主题的解决方案。通过建立KafkaClient和线程池ConsumerPool,实现了单线程按需监听特定主题,并精确控制消息确认与异常重试。KafkaAutoTableHandler定时扫描并启动消费者监听符合条件的主题。
摘要由CSDN通过智能技术生成

问题分析

spring boot 已经对kafka 进行了很好的封装集成,只需要找配置文件中配置相应的配置参数,再配合
@KafkaListener 注解即可监听kafka 消息,但如果想动态监听某一类消息而不是固定的某几个topic 呢?
虽然@KafkaListener 提提供了topicPattern 可以实现简单的正则表达式配置, 一方面如果是没有固定规则的topic 不能监听扩展不友好,另一方面一个 KafkaConsumer 监听多个topic ,批量拉取消息时一批
消息存在多个topic消息,不好做消息的ack ,offsets 提交控制

需要解决的问题

目前的需求需要满足以下几点:

  • 消息topic 需要动态监听,可以通过业务代码(参数)控制需要监听的topic
  • 单个线程只监听一个topic
  • 准确控制消息的 ack ,异常消费重试

解决方案

设计方案 通过建立一个 Kafka client ,用于定时任务扫描卡fkaka 注册的所有topic
维护一个客户端线程池 Consumer pool ,以topic ,group 为 标识维护一个 Consumer 或者一组(看业务可以加分区等创建多个) 。如果池已存在那么久不创建消费者监听,不存在则动态创建一个
在这里插入图片描述

Consumer topic

Consumer topic 为一个异步线程实现

/***
* 
* @author wangxiaobo
*
*/
@Log4j2
@Data
public class KfakaConsumerRunnable extends Thread {

   private Map<String, Object> consumerConfigs;
   private String topicName;
   private String groupId;
   private ConsumerMessageHelper consumerMessageHelper;

   @Override
   public void run() {
   	log.error("注册开始KfakaConfigRunnable{} ", topicName);
   	if(StringUtils.isNotBlank(groupId)) {
   		consumerConfigs.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
   	}
   	KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(consumerConfigs);

   	consumer.subscribe(Collections.singletonList(topicName));
   	try {
   		while (true) {
   			// 从服务器开始拉取数据
   			ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1500));
   			if (records.isEmpty()) {
   				Thread.sleep(5000);
   				continue;
   			}
   			// do bussiness
   			consumerMessageHelper.hbaseCommonWrite(records, topicName);
   			consumer.commitSync();
   			/*
   			 * consumer.commitAync(((offsets, exception) -> { if (exception == null) {
   			 * offsets.forEach((topicPartition, metadata) -> { log.info(topicPartition +
   			 * " -> offset=" + metadata.offset()); }); } else {
   			 * log.info("消息确认错误,重置偏移",exception); // 如果出错了,同步提交位移
   			 * consumer.commitSync(offsets); } }));
   			 */
   		}
   	} catch (Exception e) {
   		log.error("KfakaConfigRunnable 消费异常:{} ", topicName, e);
   	} finally {
   		//异常先关闭
   		consumer.close();
   		KfakaConfig.KFAKACONSUMERRUNNABLE_POOL.remove(topicName);
   	}
   }
}

kafak 通用配置 设置

 /***
 * 
 * @author wangxiaobo
 *
 */
@Configuration
@Log4j2
public class KfakaConfig {

	public static Map<String, KfakaConsumerRunnable> KFAKACONSUMERRUNNABLE_POOL = new ConcurrentHashMap<String, KfakaConsumerRunnable>();

	@Value("${spring.kafka.bootstrap-servers}")
	private String bootstrapServers;
	
	@Value("${spring.profiles.active}")
	private String active;

	public Map<String, Object> consumerConfigs() {
		 Map<String, Object> propsMap = new  HashMap<String, Object>();

		propsMap.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);

		propsMap.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);

		propsMap.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1000);

		propsMap.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 100000);

		propsMap.put(ConsumerConfig.REQUEST_TIMEOUT_MS_CONFIG, 110000);

		propsMap.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);

		propsMap.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);

		propsMap.put(ConsumerConfig.GROUP_ID_CONFIG, "kafka2");

		propsMap.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");

		propsMap.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 1000);
		propsMap.put(ConsumerConfig.FETCH_MAX_WAIT_MS_CONFIG, 5000);
		// 每个批次获取数
		return propsMap;
	}

	@Bean
	public AdminClient getAdminClient() {
		Properties properties = new Properties();
		properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
		properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
		properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
		AdminClient adminClient = AdminClient.create(properties);
		return adminClient;
	}
}

timer 触发实现

@Component
public class KafkaAutoTableHandler{
	
	@Autowired
	AdminClient adminClient;
	
	@Autowired
	KfakaConfig kfakaConfig;
	@Autowired
	ConsumerMessageHelper consumerMessageHelper; 

    
    public void initTopicListener() throws Exception{
        ListTopicsResult result = adminClient.listTopics();
        KafkaFuture<Set<String>> names = result.names();
        for (String topic : names.get()) {
			if(topic.startsWith(HbaseConfig.PREFIX_TABLE) &&
					!KfakaConfig.KFAKACONSUMERRUNNABLE_POOL.containsKey(topic)) {
				KfakaConsumerRunnable consumerRunnable = new KfakaConsumerRunnable();
				consumerRunnable.setConsumerConfigs(kfakaConfig.consumerConfigs());
				consumerRunnable.setTopicName(topic);		
				consumerRunnable.setConsumerMessageHelper(consumerMessageHelper);
				KfakaConfig.KFAKACONSUMERRUNNABLE_POOL.put(topic,consumerRunnable);
				consumerRunnable.start();
			}
		}
    }
}

ConsumerMessageHelper 业务处理

这个根据自己的业务实现编写即可

运行效果输出

2021-08-13 08:19:20.188 [Thread-17] INFO  com.xiaobo.kafka.ConsumerMessageHelper - topic3,size:2
2021-08-13 08:19:20.191 [Thread-38] INFO  com.xiaobo.kafka.ConsumerMessageHelper - topic2,:62
2021-08-13 08:19:20.193 [Thread-125] INFO  com.xiaobo.kafka.ConsumerMessageHelper - topic1,size:29
2021-08-13 08:19:20.202 [Thread-125] INFO  com.xiaobo.kafka.ConsumerMessageHelper - topic1,size:17
2021-08-13 08:19:20.191 [Thread-38] INFO  com.xiaobo.kafka.ConsumerMessageHelper - topic2,:162
2021-08-13 08:19:20.188 [Thread-17] INFO  com.xiaobo.kafka.ConsumerMessageHelper - topic3,size:7
2021-08-13 08:19:20.207 [Thread-125] INFO  com.xiaobo.kafka.ConsumerMessageHelper - topic1,size:19
2021-08-13 08:19:20.212 [Thread-125] INFO  com.xiaobo.kafka.ConsumerMessageHelper - topic1,size:17
2021-08-13 08:19:20.217 [Thread-125] INFO  com.xiaobo.kafka.ConsumerMessageHelper - topic1,size:21

### 回答1: Spring Boot Kafka 批量消费是指通过 Spring Boot 框架集成 Kafka,实现一次性消费多条消息的功能。在 Kafka 中,批量消费可以提高消费效率,减少网络开销,提高系统的吞吐量。Spring Boot Kafka 批量消费可以通过配置 Kafka 消费者的批量拉取大小和批量处理大小来实现。同时,还可以使用 Kafka批量消费器来实现批量消费。 ### 回答2: Spring Boot是一款非常流行的Java框架,其中集成了Kafka,支持快速搭建Kafka生产者和消费者应用。而在Kafka消费者应用中,有时会需要批量消费消息,以提高性能。 批量消费是指一次性从Kafka服务器获取多个消息,然后一次性处理它们,而不是逐个处理。这种方式可以减少网络传输和处理的时间,提高处理效率,特别是在大数据量的场景下非常有用。 Spring Boot提供了多种方式来实现Kafka批量消费。其中一种方式是通过@EnableKafka注解来启用Kafka消费者,然后手动创建一个ConcurrentKafkaListenerContainerFactory,通过该工厂类来设置属性,如批量消费配置。 例如: ``` @Configuration @EnableKafka public class KafkaConfig { @Bean public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() { ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(consumerFactory()); //设置批量消费 factory.setBatchListener(true); return factory; } @Bean public ConsumerFactory<String, String> consumerFactory() { return new DefaultKafkaConsumerFactory<>(consumerConfigs()); } @Bean public Map<String, Object> consumerConfigs() { Map<String, Object> propsMap = new HashMap<>(); propsMap.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); propsMap.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); propsMap.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "100"); propsMap.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "15000"); propsMap.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); propsMap.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); propsMap.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, "10"); //设置每次批量获取的消息数量 return propsMap; } @Bean public Listener listener() { return new Listener(); } } ``` 以上配置已经开启了批量消费模式。在Listener类中,只需要添加@KafkaListener注解即可实现批量消费: ``` @Component public class Listener { @KafkaListener(topics = "test", containerFactory="kafkaListenerContainerFactory") public void batchListener(List<String> data) { for(String d : data) { System.out.println(d); } } } ``` 上述batchListener方法的参数列表类型为List<String>,因此Spring Boot自动将多条消息打包成list传递到batchListener方法中,实现了批量消费。 除了通过ConcurrentKafkaListenerContainerFactory手动设置批量消费,还可以通过直接定义@KafkaListener相关参数来实现: ``` @KafkaListener( topics = "test", groupId = "foo", containerFactory = "kafkaListenerContainerFactory", concurrency = "3", //设置并发处理的线程数 autoStartup = "false") public void batchListener(List<String> data) { for(String d : data) { System.out.println(d); } } ``` 总结一下,Spring Boot集成Kafka批量消费主要有两种实现方式:手动配置ConcurrentKafkaListenerContainerFactory或直接在@KafkaListener注解中设置参数。通过这种方式,能够提高消费者处理效率,适用于大数据量的场景。 ### 回答3: Spring Boot是一个轻量级的Java框架,它提供了丰富的功能和易于使用的编程模型,使得开发者可以快速构建、部署和运行应用程序。Kafka则是一个开源的分布式消息系统,它提供了高效、可靠和可扩展的消息传递机制,可以帮助开发者构建大规模的实时数据处理和消息系统。 在使用Spring BootKafka进行消息处理时,很多时候需要处理大量的批量数据,例如从数据库中读取数据并批量写入到Kafka中。这时候,如何进行批量消费就成为了一个非常重要的问题。 针对这个问题,Spring BootKafka提供了多种解决方案,主要包括以下几种: 1. 手动提交offset:通过手动控制offset的提交,可以实现批量消费。当处理完一批消息后,手动将offset提交到Kafka中,下次再从提交的offset开始继续消费下一批消息即可。这种方式可以提高消费的效率和吞吐量。需要注意的是,如果在消费过程中出现异常或者程序挂掉,需要通过重新启动程序并从上次提交的offset开始重新消费消息。 2. 使用BatchListener:BatchListener是Spring Kafka提供的一个可以实现批量消费的特性。通过在注解中设置batchSize参数,即可指定每一批次需要处理的消息数量。当消息数量达到batchSize时,Spring Kafka会自动调用一次BatchListener进行批量消费。需要注意的是,如果在生产环境中,需要适当地调整batchSize的大小,避免因批量消息过大导致程序内存溢出等问题。 3. 使用Kafka Consumer API:如果需要对批量消费的逻辑和流程进行更加灵活的控制,可以直接使用Kafka Consumer API。通过在Kafka Consumer API中使用poll()方法,可以实现按照批量方式获取消息。当消息数量达到一定阈值时,就可以进行批量处理。需要注意的是,使用Kafka Consumer API需要自己控制offset的提交和消费异常的处理等问题,相对比较复杂。 综上所述,Spring BootKafka提供了多种实现批量消费的解决方案,选择合适的方式可以提高消息处理的效率和稳定性。需要根据实际情况进行选择和调整。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值