报错消息:
org.apache.kafka.clients.consumer.CommitFailedException:
Commit cannot be completed since the group has already rebalanced and assigned the partitions to another member.
This means that the time between subsequent calls 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
解决方案:
析得知出现该异常是因为一次性poll拉取(默认500)消息后处理时间过长,导致两次拉取时间间隔超过了max.poll.interval.ms阈值(默认五分钟)。解决策略可以加大参数:max.poll.interval.ms或者减少一次性拉取的消息数量。 我这里是改了拉取消息数量和session.timeout.ms得以解决。
1> spring配置如下:max-poll-records
这个参数定义了poll()
方法最多可以返回多少条消息,默认值为500。注意这里的用词是"最多",也就是说如果在拉取消息的时候新消息不足500条,那有多少返回多少;如果超过500条,就只返回500。这个默认值是比较坑人的,如果你的消息处理逻辑比较重,比如需要查数据库,调用接口,甚至是复杂计算,那么你很难保证能够在5min内处理完500条消息,也就是说,如果上游真的突然大爆发生产了成千上万条消息,而平摊到每个消费者身上的消息达到了500的又无法按时消费完成的话就会触发rebalance, 然后这批消息会被分配到另一个消费者中,还是会处理不完,又会触发rebalance, 这样这批消息就永远也处理不完,而且一直在重复处理。
spring:
kafka:
consumer:
max-poll-records: 200
2> 我也改了spring.kafka.properties.session.timeout.ms
spring:
kafka:
properties:
session:
timeout:
ms: 120000
3> kafka 配置 max.poll.interval.ms
这个参数定义了两次poll()
之间的最大间隔,默认值为5分钟。如果超过这个间隔同样会触发rebalance。在多数情况下这个参数是导致rebalance消息重复的关键,即业务处理消息耗时太长。有人可能会疑惑,如果5分钟都没处理完消息那肯定时出了问题,其实不然。能否在5min内处理完还取决于你每次拉取了多少条消息,如果一次拿到了成千上万条的话,5min就够呛了。
假设你一次poll调用返回的消息数是N,你处理每条消息的平均时间是t0,那么你需要设置max.poll.interval.ms稍稍大于N * t0以保证poll调用间隔不会超过该阈值。
spring.kafka.properties.max.poll.interval.ms 两次poll的间隔默认5分钟
或者
kafka中config/server.properties -- 10分钟
max.poll.interval.ms=600000
4> spring配置config调整
configProps.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG,ConsumerConfig.DEFAULT_MAX_PARTITION_FETCH_BYTES*5);
configProps.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG,Integer.parseInt(sessionTimeout));
configProps.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG,Integer.parseInt(pollIntervalMs));
@Configuration
@EnableKafka
public class KafkaConsumerConfig {
@Value("${spring.kafka.bootstrap-servers}")
private String bootstrapServers;
@Value("${spring.kafka.consumer.group-id}")
private String groupId;
@Value("${spring.kafka.consumer.auto-offset-reset}")
private String autoOffset;
@Value("${spring.kafka.properties.max.poll.interval.ms}")
private String pollIntervalMs;
@Value("${spring.kafka.properties.session.timeout.ms}")
private String sessionTimeout;
@Value("${spring.kafka.consumer.max-poll-records}")
private String pollRecord;
@Bean
public ConcurrentKafkaListenerContainerFactory<String, LogReceive> basicKafkaListenerContainerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
configProps.put(ConsumerConfig.GROUP_ID_CONFIG,groupId);
configProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,autoOffset);
configProps.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG,ConsumerConfig.DEFAULT_MAX_PARTITION_FETCH_BYTES*5);
configProps.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG,Integer.parseInt(sessionTimeout));
configProps.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG,Integer.parseInt(pollIntervalMs));
configProps.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG,Integer.parseInt(pollRecord));
ConcurrentKafkaListenerContainerFactory<String, LogReceive> factory = new ConcurrentKafkaListenerContainerFactory<>();
ConsumerFactory<String,LogReceive> basicConsumerFactory =
new DefaultKafkaConsumerFactory<>(configProps,
new StringDeserializer(),
new JsonDeserializer<>(LogReceive.class));
factory.setConsumerFactory(basicConsumerFactory);
return factory;
}
}