kafka相关使用以及线上遇到的问题和解决方法
项目选用kafka原因
kafka保证有序:在一个分组下,分区只能被一个消费者消费,一个消费者可以消费多个分区;发送一批消息时指定相同的消息key,kafka根据key计算所属分区,key相同所属分区也相同
一、正式环境生产者及消费者初始配置
applaction.yml配置:
producer:
retries: 0
batch-size: 16384
buffer-memory: 33554432
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
consumer:
group-id: event_upload
auto-offset-reset: earliest
enable-auto-commit: true
auto-commit-interval: 100
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
消费者监听
/**
* 监听上报
* @param record
*/
@KafkaListener(id="eventUploadID",topics={"eventUpload"})
public void listen0(ConsumerRecord<String, String> record) {
if (record != null) {
String content = record.value();
// TODO 业务代码
// ...
}
}
以上可知,项目刚上线采用自动提交,每次重启有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
二、生产环境遇到的问题
1.消费者接收到的日志,生产者中却不存在该条消息记录
问题具体描述如下:
因项目中生产者和消费者的消息都会被记录在数据库中,消费者中的消息在生产者数据库中并不存在
原因
因在实际使用中kafka发送消息是在业务处理中间发送的,当发送消息后业务抛异常回滚就造成了消息错发的现象
解决方法
刚开始想使用kafka事务,但发现在发送完消息后业务逻辑发生异常回滚后kafka事务不起效果,后来定位是因为在发送消息后的业务中有新建线程的操作,只要有新建线程的操作就会存在kafka事务不起效果的情况,放弃使用kafka事务。
因考虑到业务代码较复杂,想要直接修改代码在完成业务之后统一提交消息工作量太大,采用以下方法解决来实现这种效果:
自定义方法注解,使用AOP拦截有此注解的方法,在执行方法前注册事务监听器,在事务内发送的消息都存放在池中,事务正常提交发送事务内所有消息,回滚则清空池
kafka事务应用场景:在完成业务流程后发送多个消息,在发送多个消息时一个消息失败其他消息也失败
2.上线几天后发现kafka消费缓慢,并且出现了重复消费的问题
原因
因消费端的消费效率或网络宽带、数据量引起了kafka消费迟缓,当时怀疑是poll消息间隔超过了默认的时间间隔,并且因采用的是自动提交,消费端未能顺利提交偏移量,kafka服务器认为没有消费又重新发送一遍消息导致了重复消费
解决方法
1、将提交方式改为手动提交,并且使用MANUAL_IMMEDIATE模式(手动调用Acknowledgment.acknowledge()后立即提交),在消费者接收到消息时立刻调用Acknowledgment.acknowledge()提交偏移量
2、调大max.poll.interval.ms(每次poll间隔超时时间)值,将其改为1200000(20分钟),可根据具体的业务来定
3、记录kafka的分区及偏移量以便维护数据
优化后applaction.yml consumer的配置:
consumer:
group-id: event_upload
auto-offset-reset: earliest
enable-auto-commit: false
auto-commit-interval: 100
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
listener:
ack-mode: MANUAL_IMMEDIATE # 手动调用Acknowledgment.acknowledge()后立即提交
properties:
max-poll-interval-ms: 1200000
消费者监听
/**
* 监听上报
* @param record
*/
@KafkaListener(id="eventUploadID",topics={"eventUpload"})
public void listen0(ConsumerRecord<String, String> record, Acknowledgment ack) {
ack.acknowledge();
if (record != null) {
String content = record.value();
// TODO 业务代码
// ...
}
}
3.在放假后第一天上班发现线上kafka又出现消费缓慢的情况,并且堆积了较多消息
经排查日志存在报错:Offset commit cannot be completed since the consumer is not part of an active group for auto partition assignment; it is likely that the consumer was kicked out of the group.
原因
由于取出的一批消息数量太大,consumer在session.timeout.ms时间之内没有消费完成引起kafka rebalance
解决方法
session.timeout.ms改为10分钟,max.poll.records改为10(默认500)
优化后applaction.yml的配置:
producer:
retries: 0
batch-size: 16384
buffer-memory: 33554432
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
consumer:
group-id: event_upload
auto-offset-reset: earliest
enable-auto-commit: false
auto-commit-interval: 100
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
max-poll-records: 10
listener:
ack-mode: MANUAL_IMMEDIATE # 手动调用Acknowledgment.acknowledge()后立即提交
properties:
max-poll-interval-ms: 1200000
session-timeout-ms: 600000