kafka相关使用以及线上遇到的问题和解决方法

  • 项目选用kafka原因
  • 一、正式环境生产者及消费者初始配置
  • 二、生产环境遇到的问题
  • 1.消费者接收到的日志,生产者中却不存在该条消息记录
  • 原因
  • 解决方法
  • 2.上线几天后发现kafka消费缓慢,并且出现了重复消费的问题
  • 原因
  • 解决方法
  • 3.在放假后第一天上班发现线上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