前言
最近有个项目存在kafka积压情况,上去看了下,的确积压挺厉害。
看了下代码,spring boot 是1.5.13.RELEASE版本,kafka使用的是spring boot的自动配置,@KafkaListener每次处理一条数据,每次逻辑中存在多次数据库操作。
准备修改下逻辑,@KafkaListener批量处理数据,合并逻辑,并且批量操作数据库,提高处理速度。
原有逻辑
1.pom添加配置
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
2.application.yml添加配置
spring:
kafka:
bootstrap-servers: 127.0.0.1:9092
#生产者的配置,大部分我们可以使用默认的,这里列出几个比较重要的属性
producer:
#每批次发送消息的数量
batch-size: 16
#设置大于0的值将使客户端重新发送任何数据,一旦这些数据发送失败。注意,这些重试与客户端接收到发送错误时的重试没有什么不同。允许重试将潜在的改变数据的顺序,如果这两个消息记录都是发送到同一个partition,则第一个消息失败第二个发送成功,则第二条消息会比第一条消息出现要早。
retries: 0
#producer可以用来缓存数据的内存大小。如果数据产生速度大于向broker发送的速度,producer会阻塞或者抛出异常,以“block.on.buffer.full”来表明。这项设置将和producer能够使用的总内存相关,但并不是一个硬性的限制,因为不是producer使用的所有内存都是用于缓存。一些额外的内存会用于压缩(如果引入压缩机制),同样还有一些用于维护请求。
buffer-memory: 33554432
#key序列化方式
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
#消费者的配置
consumer:
concurrency: 10
#Kafka中没有初始偏移或如果当前偏移在服务器上不再存在时,默认区最新 ,有三个选项 【latest, earliest, none】
auto-offset-reset: earliest
#是否开启自动提交
enable-auto-commit: false
ack-mode: MANUAL_IMMEDIATE
#自动提交的时间间隔
# auto-commit-interval: 100
#key的解码方式
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
#value的解码方式
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
group-id: test-consumer-group
3.java监听逻辑
@KafkaListener(topics = { "topic" })
public void listenPlayEnt(ConsumerRecord<?, ?> record) {
// 处理逻辑
....
}
简单思路
spring kafka实现
由于spring boot 自动配置KafkaAutoConfiguration和KafkaProperties中没有找到批量操作相关的,转向查看依赖实现spring-kafka。
从 官方文档 https://docs.spring.io/spring-kafka/reference/html/_reference.html 中我们可以看出来批量需要以下代码实现。
@Configuration
@EnableKafka
public class KafkaConfig {
@Bean
KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<Integer, String>>
kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<Integer, String> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
factory.setConcurrency(3);
# 这个就是用来控制是否批量处理
factory.setBatchListener(true);
factory.getContainerProperties().setPollTimeout(3000);
return factory;
}
@Bean
public ConsumerFactory<Integer, String> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(consumerConfigs());
}
@Bean
public Map<String, Object> consumerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, embeddedKafka.getBrokersAsString());
...
return props;
}
}
但是,我人比较懒,不想大幅度修改kafka相关的配置,想要沿用spring boot kafka自动配置逻辑。
具体的过程不说了,简单分析下KafkaAutoConfiguration逻辑,可以看到差别主要是在ConcurrentKafkaListenerContainerFactory这个bean。
spring boot kafka实现
1.重写KafkaProperties对象
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@ConfigurationProperties(prefix = "spring.kafka")
@Configuration("KafkaPropertiesExtra")
@Primary
public class KafkaPropertiesExtra extends KafkaProperties {
private final ConsumerExtra consumerExtra = new ConsumerExtra();
public static class ConsumerExtra {
private Boolean batchListener = false;
public Boolean getBatchListener() {
return batchListener;
}
public void setBatchListener(Boolean batchListener) {
this.batchListener = batchListener;
}
}
public ConsumerExtra getConsumerExtra() {
return this.consumerExtra;
}
}
2.自定义ConcurrentKafkaListenerContainerFactory这个bean
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.kafka.ConcurrentKafkaListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
@Configuration
public class KafkaConfig {
@Autowired KafkaPropertiesExtra kafkaProperties;
@Bean
public ConcurrentKafkaListenerContainerFactory<?, ?> kafkaListenerContainerFactory(
ConcurrentKafkaListenerContainerFactoryConfigurer configurer,
ConsumerFactory<Object, Object> kafkaConsumerFactory) {
ConcurrentKafkaListenerContainerFactory<Object, Object> factory = new ConcurrentKafkaListenerContainerFactory<Object, Object>();
configurer.configure(factory, kafkaConsumerFactory);
// 补充批量处理参数
factory.setBatchListener(kafkaProperties.getConsumerExtra().getBatchListener());
return factory;
}
}
3.修改application.yml,加上批量相关的配置
spring:
kafka:
......
consumer:
......
# 一次性最多抓取多少条数据
max-poll-records: 100
# 超时时间 毫秒
poll-timeout: 3000
max-poll-interval-ms: 5000
consumer-extra:
# 是否批量处理
batch-listener: true
4.修改java监听器逻辑,改成list
@KafkaListener(topics = { "topic" })
public void listenPlayEnt(List<ConsumerRecord<?, ?>> records) {
// 处理逻辑
....
}
至此,就基于spring boot的KafkaAutoConfiguration实现了kafka批量消费逻辑。