SpringBoot Kafka动态指定消费组

场景分析

有一个websocket服务,消费来自kafka的消息,并且推送给指定的ws客户端。原本kafka配置是从配置文件中直接读取,当需要多实例启动时,并且想通过消费分组去让不同实例都消费topic中的数据时,配置文件显然就捉襟见肘了。

解决方案

考虑多实例部署时候生成不用的消费分组,每个实例启动后被分配在不通消费分组中。利用不通消费分组均能消费topic中消息的原理,实现kafka的订阅发布。
talk is cheap,show you the code

kafka配置
@Slf4j
@Component
public class KafkaConsumerConfig {
    @Value("${spring.kafka.bootstrap-servers}")
    private String BROKERS;
    @Value("${spring.kafka.consumer.enable-auto-commit}")
    private Boolean ENABLE_AUTO_COMMIT;
    @Value("${spring.kafka.consumer.auto-commit-interval-ms}")
    private String AUTO_COMMIT_INTERVAL_MS;
    @Value("${spring.kafka.consumer.session-timeout-ms}")
    private Integer SESSION_TIMEOUT_MS;
    @Value("${spring.kafka.consumer.auto-offset-reset}")
    private String AUTO_OFFSET_RESET;
    @Value("${spring.kafka.consumer.group-id}")
    private String GROUP_ID;
    @Value("${spring.kafka.consumer.max-poll-records}")
    private String MAX_POLL_RECORDS;

    /**缓存名称前缀*/
    private final String CACHE_GROUP_NAME_PREFIX = "CONSOLE:WSMSGCONSUMER:";
    /**消息推送消费者消费分组上限*/
    private final Integer WS_MAX_GROUP_VALUE = 6;

    private String CURRENT_INSTANCE_GROUP_ID;

    @Autowired
    private RedisOperate redisOperate;
	/** 线程池,为了实现分组名称续租服务*/
    private  ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);


	/**构建kafka监听工厂*/
    @Bean
    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<String, String>();
        factory.setConsumerFactory(consumerFactory());
        return factory;
    }

	/**通过redis限制获取的分组名称*/
    public String getSerializeGroupId(Integer currValue){
        if(currValue>WS_MAX_GROUP_VALUE){
            /**尽可能保证使用重复分组名称进行消费*/
            throw new RuntimeException("oversize WS_MAX_GROUP_VALUE");
        }
        String key = CACHE_GROUP_NAME_PREFIX.concat(currValue.toString());
        boolean b = redisOperate.setIfAbsent(key, currValue, 180, TimeUnit.SECONDS);
        if(b){
            startHoldingGroupName(key);
            return GROUP_ID.concat(currValue.toString());
        }else{
            currValue++;
            return getSerializeGroupId(currValue);
        }
    }


	/**初始化消费工厂配置 其中会动态指定消费分组*/
    private ConsumerFactory<String, String> consumerFactory() {
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, BROKERS);
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, ENABLE_AUTO_COMMIT);
        properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, AUTO_COMMIT_INTERVAL_MS);
        properties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, MAX_POLL_RECORDS);
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        /**多实例部署每个实例设置不同groupId 实现发布订阅*/
        CURRENT_INSTANCE_GROUP_ID = getSerializeGroupId(0);
        log.info("当前实例WsMsgConsumer group_id:{}",CURRENT_INSTANCE_GROUP_ID);
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, CURRENT_INSTANCE_GROUP_ID);
        properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, AUTO_OFFSET_RESET);
        return new DefaultKafkaConsumerFactory<String, String>(properties);
    }

    /**对当前实例持有的消费分组进行续租*/
    private void startHoldingGroupName(String groupKey){
        scheduledExecutorService.scheduleAtFixedRate(()->{
            log.info("startHoldingGroupName {}",groupKey);
            redisOperate.expire(groupKey,180);
        },0,120,TimeUnit.SECONDS);
    }



}
kafka消费者
@Component
@Slf4j
public class WsMsgConsumer {
	/**通过containerFactory指定到刚才初始化的kafkaListenerContainerFactory*/
    @KafkaListener(topics = "${spring.kafka.consumer.ws.task.push.topic}", containerFactory="kafkaListenerContainerFactory")
    public void WsMsgConsumer(ConsumerRecord<?, ?> record) {
        log.info("WsMsgConsumer==>{},kafka_record==>{}", record.topic(), record.toString());
        Optional<?> kafkaMessage = Optional.ofNullable(record.value());
        if (kafkaMessage.isPresent()) {
            try {
                String message = (String) kafkaMessage.get();
                JSONObject jsonObject = JSON.parseObject(message);
                WxMsgDTO wxMsgDTO = JSON.toJavaObject(jsonObject, WxMsgDTO.class);
                publishMessage(wxMsgDTO.getStatus().toString(),wxMsgDTO.getUserId(),wxMsgDTO.getStoreId());
            }catch (Exception e){
                log.error("WsMsgConsumer,消费失败----"+ CommonUtil.getTrace(e));
            }

        }
    }
}

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Spring Boot可以通过使用Kafka提供的API来指定offset消费消息。 首先,我们需要添加Kafka客户端的依赖。可以在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> ``` 接下来,我们需要配置Kafka的连接信息。可以在application.properties文件(或application.yml)中添加以下配置: ``` spring.kafka.bootstrap-servers=127.0.0.1:9092 spring.kafka.consumer.group-id=your-group-id spring.kafka.consumer.auto-offset-reset=earliest ``` 其中,`spring.kafka.bootstrap-servers`指定Kafka服务器的地址和端口,`spring.kafka.consumer.group-id`指定消费的ID,`spring.kafka.consumer.auto-offset-reset`指定消费者在消费消息时的起始位置,这里设置为最早的offset。 然后,我们可以编写一个Kafka消费者来指定offset进行消费。可以创建一个Spring Bean来实现Kafka的消息监听器: ```java @Component public class KafkaConsumer { @KafkaListener(topics = "your-topic-name") public void listen(ConsumerRecord<String, String> record) { // 处理消息逻辑 System.out.println("Received message: " + record.value()); } } ``` 在上述代码中,`@KafkaListener`注解指定了要监听的topic名称。当有新的消息到达时,会调用`listen`方法进行处理。 如果需要指定offset进行消费,可以在`listen`方法中添加`@Header`注解,来获取消息的offset值: ```java @KafkaListener(topics = "your-topic-name") public void listen(ConsumerRecord<String, String> record, @Header(KafkaHeaders.OFFSET) long offset) { // 获取消息的offset值 System.out.println("Received message at offset " + offset + ": " + record.value()); } ``` 以上就是使用Spring Boot和Kafka指定offset消费消息的基本步骤。通过上述配置和代码,我们可以实现具有指定offset功能的Kafka消息消费

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码大师麦克劳瑞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值