阿里云kafka使用springboot单个项目中同时消费不同topic

在SpringBoot项目中,尝试让一个应用同时消费Kafka的两个不同Topic时遇到了问题。当一个Topic使用默认消费方式,另一个指定了特定Partition时,出现提交Offset错误。经过排查,尝试了设置监听器ID、开启多线程和更改clientId,最终发现错误源于相同的groupId。通过为每个Topic分配不同的groupId,成功解决了问题,实现了正常消费。

本来是个简单的问题,但是复杂了。

两个topic 消费方式不一样,一个使用过的是默认方式,不指定partition,另外一个,指定了特殊的partition。报错:

11:10:32.888 [org.springframework.kafka.KafkaListenerEndpointContainer#1-0-C-1] 
ERROR o.a.k.c.c.i.ConsumerCoordinator - 
[Consumer clientId=futures_deal_group-1302249340-0, groupId=deal_group] 
Offset commit failed on partition topic-depth-6 at offset 1535688270: The coordinator is not aware of this member

11:10:32.888 [org.springframework.kafka.KafkaListenerEndpointContainer#1-0-C-1] 
WARN  o.a.k.c.c.i.ConsumerCoordinator - 
[Consumer clientId=futures_deal_group-1302249340-0, groupId=deal_group] 
Asynchronous auto-commit of offsets {topic-depth-6=OffsetAndMetadata{offset=1535688270, metadata=''}} 
failed: Commit cannot be completed since the group has already rebalanced and assigned the partitions to another member. This means that the time between subsequent c
alls to poll() was longer than the configured max.poll.interval.ms,
which typically implies that the poll loop is spending too much time message processing. 
You can address this either by increasing the session timeout or by reducing the maximum size of batches returned in poll() with max.poll.records.

大意就是提交的 offer 不是这个topic的,coordinator 不是这个topic

 

首先,单独跑任意一个topic 都可以正常运行,并能提交offersize 位置。

 

猜测一下 KafkaListenerEndpointContainer  报的错,就从他入手呗

可惜,spring-boot 里面并没有这个文件

那先找相近的来看看。从这开始,找  ConcurrentKafkaListenerContainerFactory  这个类

public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(new DefaultKafkaConsumerFactory<>(tickerConsumerConfigs()));
        factory.setConcurrency(1);
        factory.setBatchListener(true);
        factory.getContainerProperties().setPollTimeout(1500);
        return factory;
    }

找到父类  AbstractKafkaListenerContainerFactory,这里用到了 endpoint

public C createListenerContainer(KafkaListenerEndpoint endpoint) {
		C instance = createContainerInstance(endpoint);

		if (endpoint.getId() != null) {
			instance.setBeanName(endpoint.getId());
		}
		if (endpoint instanceof AbstractKafkaListenerEndpoint) {
			configureEndpoint((AbstractKafkaListenerEndpoint<K, V>) endpoint);
		}

		endpoint.setupListenerContainer(instance, this.messageConverter);
		initializeContainer(instance, endpoint);

		return instance;
	}

继续找,发现 KafkaListenerEndpoint 中有这样的说明,endpoint 的ID 在 factory 里面有用于区分其他的listener

public interface KafkaListenerEndpoint {

	/**
	 * Return the id of this endpoint.
	 * @return the id of this endpoint. The id can be further qualified
	 * when the endpoint is resolved against its actual listener
	 * container.
	 * @see KafkaListenerContainerFactory#createListenerContainer
	 */
	String getId();

	/**
	 * Return the groupId of this endpoint - if present, overrides the
	 * {@code group.id} property of the consumer factory.
	 * @return the group id; may be null.
	 * @since 1.3
	 */
	String getGroupId();

对于未知的错误是不是我们的两个topic 都用 KafkaListenerEndpointContainer  一个而导致的问题,加上 id 

同时考虑  开始多个线程,试试能否解决

线程id 变了,还有问题。

继续寻找,在  org.springframework.kafka.listener.ConcurrentMessageListenerContainer  发现

for (int i = 0; i < this.concurrency; i++) {
				KafkaMessageListenerContainer<K, V> container;
				if (topicPartitions == null) {
					container = new KafkaMessageListenerContainer<>(this, this.consumerFactory, containerProperties);
				}
				else {
					container = new KafkaMessageListenerContainer<>(this, this.consumerFactory,
							containerProperties, partitionSubset(containerProperties, i));
				}
				String beanName = getBeanName();
				container.setBeanName((beanName != null ? beanName : "consumer") + "-" + i);
				if (getApplicationEventPublisher() != null) {
					container.setApplicationEventPublisher(getApplicationEventPublisher());
				}
				container.setClientIdSuffix("-" + i);
				container.setGenericErrorHandler(getGenericErrorHandler());
				container.setAfterRollbackProcessor(getAfterRollbackProcessor());
				container.setRecordInterceptor(getRecordInterceptor());
				container.setEmergencyStop(() -> {
					stop(() -> {
						// NOSONAR
					});
					publishContainerStoppedEvent();
				});
				if (isPaused()) {
					container.pause();
				}
				container.start();
				this.containers.add(container);
			}
		}

container 的生成跟clientId 有关?为每个 consumer 新增一个clientId 试试,依然报错

 

是不是果真如猜测的,是因为某个bean是公用的导致,提交offer 报错吗?

本地debug 时,ConcurrentKafkaListenerContainerFactory  、DefaultKafkaConsumerFactory  其实都不是一个类

 

在本地单独调试  topic2 时,服务器单独消费 topic1  居然又报错了,并且kafka 管理平台中观察topic1 的消费点位不变化了,与同时启动,2个consumer 的情况一样。

如此可以推断这个报错与 同时消费两个topic的 groupId 有关系。

修改代码 使用不同的 groupId,程序正常~~~~!!!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

### 使用Kafka消费消费特定Topic的数据 要使Kafka消费者能够从指定的主题(Topic)中读取消息,需遵循一系列配置和编程实践。创建一个基本的Kafka消费者实例涉及设置必要的属性并订阅感兴趣的Topic。 #### 配置消费者属性 首先定义一组用于初始化消费者的配置项: ```properties bootstrap.servers=localhost:9092 # Kafka服务器地址列表 group.id=my-consumer-group # 消费者组ID key.deserializer=org.apache.kafka.common.serialization.StringDeserializer # 键反序列化器类名 value.deserializer=org.apache.kafka.common.serialization.StringDeserializer # 值反序列化器类名 auto.offset.reset=earliest # 当没有初始偏移量或当前偏移不再可用时,重置偏移策略 enable.auto.commit=true # 是否自动提交已处理的消息偏移量 max.poll.interval.ms=300000 # 控制poll()调用之间允许的最大时间间隔[^4] ``` 上述`max.poll.interval.ms`参数指定了消费者两次轮询之间的最长时间间隔,在此期间如果未发起新的请求,则认为该消费者已经失效并将触发再平衡操作。 #### 编写Java代码实现消费者逻辑 下面是一个简单的例子展示如何编写一段程序来启动一个Kafka消费者,并让它监听来自给定主题的新消息: ```java import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.KafkaConsumer; import java.time.Duration; import java.util.Collections; import java.util.Properties; public class SimpleKafkaConsumer { public static void main(String[] args){ Properties props = new Properties(); // 设置消费者配置... props.put("bootstrap.servers", "localhost:9092"); props.put("group.id", "my-consumer-group"); props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) { // 订阅到具体的topic上 consumer.subscribe(Collections.singletonList("your-topic-name")); while(true){ var records = consumer.poll(Duration.ofMillis(100)); for (ConsumerRecord<String, String> record : records) System.out.printf("offset=%d, key=%s, value=%s%n", record.offset(), record.key(), record.value()); } } catch(Exception e){ e.printStackTrace(); } } } ``` 这段代码展示了怎样通过`subscribe()`方法告诉Kafka我们要关注哪个主题以及当接收到新记录时应执行的操作。这里采用了一个无限循环配合`poll()`函数的方式持续获取最新发布的消息直到手动终止进程为止。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值