一次kafka消息丢失问题处理
背景&现象
生产微服务架构环境,kafka消息消费服务架构如下:
当服务B接口出现宕机或者B接口调用超时,kafka消息消费端服务A出现异常,异常发生后未执行手动提交offset操作。待服务B恢复后,消费端A服务也恢复正常,但之前消费异常的消息在broker自动变为已消费,实际未消费(数据库中无处理消息的业务数据)。
问题原因
问题定位
在灰度环境复刻了一样的操作,100%复现此问题。增加了debug打印日志,确认了业务代码在异常后,仍继续消费服务端产生的消息,只不过会抱异常,具体业务代码如下:
@KafkaListener(containerFactory = "kafkaBatchListener", topics = {CAMERA_DEV_FAULT})
public void alarmListener(List<ConsumerRecord<?, ?>> records, Acknowledgment ack) throws Exception{
try{
//1.处理
for (ConsumerRecord consumerRecord : records) {
Long startTime = (new Date()).getTime();
log.info("recv message offset" + String.valueOf(consumerRecord.offset())+ " | " + consumerRecord.partition() +
" | " + consumerRecord.topic() + " | " + consumerRecord.value());
JSONObject msg = JSONObject.fromObject(consumerRecord.value());
uCameraAlarmProcessor.process(msg);
// List ss = new ArrayList();
// System.out.println("=========================="+ss.get(5).toString());
Long endTime = (new Date()).getTime();
log.info("recv message offset222 = " + consumerRecord.offset() + "| oprTime = " + (endTime-startTime));
}
//2.消费提交偏移量
ack.acknowledge();
log.info("OK records {}",records.size());
} catch (DuplicateKeyException e){
ack.acknowledge();
log.info("Exception 1");
e.printStackTrace();
} catch (Exception e){
log.info("Exception 2"+e.getMessage());
throw e;
}
}
spring-kafka配置信息如下:
private Map<String, Object> consumerConfigs() {
Map<String, Object> props = new HashMap<>(10);
props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitInterval);
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, autoCommit);
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords);
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset);
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, sessionTimeout);
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, maxPollInterval);
props.put(ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, maxPartitionFetchBytes);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.GROUP_ID_CONFIG,groupId);
props.put("security.protocol", "SASL_PLAINTEXT");
props.put("sasl.mechanism", "SCRAM-SHA-512");
props.put("sasl.jaas.config",
"org.apache.kafka.common.security.scram.ScramLoginModule required username=\""+username+"\" password=\""+password+"\";");
return props;
}
private ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
//批量消费
factory.setBatchListener(batchListener);
//如果消息队列中没有消息,等待timeout毫秒后,调用poll()方法。
// 如果队列中有消息,立即消费消息,每次消费的消息的多少可以通过max.poll.records配置。
//手动提交无需配置
factory.getContainerProperties().setPollTimeout(pollTimeout);
//设置提交偏移量的方式, MANUAL_IMMEDIATE 表示消费一条提交一次;MANUAL表示批量提交一次
// factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
// factory.getContainerProperties().setCommitLogLevel(LogIfLevelEnabled.Level.INFO);
// factory.getContainerProperties().setLogContainerConfig(true);
factory.getContainerProperties().setAckMode(AbstractMessageListenerContainer.AckMode.MANUAL_IMMEDIATE);
return factory;
}
结合的spring-kafka源码如下:
设置客户端消费线程记录的offse位置,此处是在消息处理之前进行更新,待消息处理结束后的回掉进行提交broker。消息处理内部如果发生异常并被捕获的话,此处拿不到回掉,所以只会记录offset变化。
消费工厂初始化消费容器,每个容器的start和stop都是对象锁同步,每个容器绑定一个消费者线程,这里执行一个消息分区的消息拉取,提交等操作。
消息拉取后,提交给业务消费者实现,等待结果回掉,并提交offset.
详细原因思考
当服务C接口出现宕机或者接口超时情况时,kafka消息处理服务业务逻辑内部出现处理异常,虽然未执行手动提交offset的操作,但是内部抛出的异常被catch住然后继续消费产生的新消息,并且消费端继续的offset一只在增加,只是为提交broker。
此时一旦下游服务恢复,kafka消费端会直接把本地存储的最新offset提交到broker,服务端在收到同一分区offset后会把之前的offset重置,并把小于此offset的消息全部状态置为已消费,导致之前处理失败的offset消息丢失的情况。示意图如下:
解决方案
高清问题原因后,我们可以针对具体原因进行解决,解题思路有2种
死信队列
这样就是把处理异常的消息写入死信队列,待服务恢复后重新消费私信队列中的异常消息,这种方案有2点问题:
1、需要写新的处理逻辑死信队列逻辑代码,并定义新的topic
2、此方案适合无唯一主键的消息处理
异常监听器
解决业务消息处理异常时不要自增本地的offset,这样即使服务恢复后本地客户端仍旧从异常的消息节点开始消费,消费成功时才能提交offset。
弊端是服务在异常时会进入死循环模式,即同一分区一只在消费一条消息;
优点是只需要引入spring-kafka异常监听器对异常消息进行处理即可,不需要引入复杂的代码和更多的消息topic。
我是采用第二种解决方案,经过测试无误后发布到生产环境,代码如下:
首先增加未知异常抛出逻辑,其次定义异常处理errorHandler。
异常处理类
@Component
public class ErrorListenner {
private static final Logger log= LoggerFactory.getLogger(ErrorListenner.class);
@Bean
public ConsumerAwareListenerErrorHandler consumerAwareErrorHandler() {
return new ConsumerAwareListenerErrorHandler() {
@Override
public Object handleError(Message<?> message, ListenerExecutionFailedException e, Consumer<?, ?> consumer) {
log.info("consumerAwareErrorHandler receive : "+message.getPayload().toString());
LinkedList<ConsumerRecord> records = (LinkedList)message.getPayload();
ConsumerRecord consumerRecord = records.get(0);
Long offset = consumerRecord.offset();
String topic = consumerRecord.topic();
Integer partition = consumerRecord.partition();
// Map<TopicPartition, OffsetAndMetadata> offsetsToReset = new HashMap<>();
// 设置每个分区的偏移量
TopicPartition topicPartition = new TopicPartition(topic, partition);
// OffsetAndMetadata offsetAndMetadata = new OffsetAndMetadata(offset, "manual_immediate");
// offsetsToReset.put(topicPartition, offsetAndMetadata);
log.info("重置客户端offset位置 topic {} partition {} {}",topic, partition,offset);
// acknowledgment.acknowledge();
consumer.seek(topicPartition,offset);
// consumer.commitSync(offsetsToReset);
return null;
}
};
}
}
一次kafka消息丢失问题处理的更多相关文章
- Kafka消息丢失
1.Kafka消息丢失的情况: (1)auto.commit.enable=true,消费端自动提交offersets设置为true,当消费者拉到消息之后,还没有处理完 commit interval ...
- 实际业务处理 Kafka 消息丢失、重复消费和顺序消费的问题
关于 Kafka 消息丢失.重复消费和顺序消费的问题 消息丢失,消息重复消费,消息顺序消费等问题是我们使用 MQ 时不得不考虑的一个问题,下面我结合实际的业务来和你分享一下解决方案. 消息丢失问题 比 ...
- 一文了解清楚kafka消息丢失问题和解决方案
前言 今天分享一下kafka的消息丢失问题,kafka的消息丢失是一个很值得关注的问题,根据消息的重要性,消息丢失的严重性也会进行放大,如何从最大程度上保证消息不丢失,要从生产者,消费者,broker ...
- 【消息队列面试】11-14:kafka高可靠、高吞吐量、消息丢失、消费模式
十一.kafka消息高可靠的解决方案 1.高可靠=避免消息丢失 解决消息丢失的问题 2.如何解决 (1)保证消息发送是可靠的(发成功了/落到partition) a.ack参数 发送端,采用ack机制 ...
- Kafka无消息丢失配置
Kafka到底会不会丢数据(data loss)? 通常不会,但有些情况下的确有可能会发生.下面的参数配置及Best practice列表可以较好地保证数据的持久性(当然是trade-off,牺牲了吞 ...
- kafka消息会不会丢失
转载:https://baijiahao.baidu.com/s?id=1583469327946027281&wfr=spider&for=pc 消息发送方式 想清楚Kafka发送的 ...
- Kafka leader副本选举与消息丢失场景讨论
如果某个broker挂了,leader副本在该broker上的分区就要重新进行leader选举.来简要描述下leader选举的过程 1.4.1 KafkaController会监听ZooKeeper的 ...
- kafka系列八、kafka消息重复和丢失的场景及解决方案分析
消息重复和丢失是kafka中很常见的问题,主要发生在以下三个阶段: 生产者阶段 broke阶段 消费者阶段 一.生产者阶段重复场景 1.根本原因 生产发送的消息没有收到正确的broke响应,导致pro ...
- Kafka设计解析(十一)Kafka无消息丢失配置
转载自 huxihx,原文链接 Kafka无消息丢失配置 目录 一.Producer端二.Consumer端 Kafka到底会不会丢数据(data loss)? 通常不会,但有些情况下的确有可能会发生 ...
- Kafka在高并发的情况下,如何避免消息丢失和消息重复?kafka消费怎么保证数据消费一次?数据的一致性和统一性?数据的完整性?
1.kafka在高并发的情况下,如何避免消息丢失和消息重复? 消息丢失解决方案: 首先对kafka进行限速, 其次启用重试机制,重试间隔时间设置长一些,最后Kafka设置acks=all,即需要相应的 ...
随机推荐
- Linux查找当前目录下包含部分内容的文件,并且copy到指定路径的简单方法
1 获取文件列表 find . -name "*.data" |xargs grep -i 'yearvariable' | uniq | awk '{print $1}' |cu ...
- 除了Adobe之外,还有什么方法可以将Excel转为PDF?
前言 Java是一种广泛使用的编程语言,它在企业级应用开发中发挥着重要作用.而在实际的开发过程中,我们常常需要处理各种数据格式转换的需求.今天小编为大家介绍下如何使用葡萄城公司的的Java API 组 ...
- OpenIM集群(非k8s)部署文档
自行部署etcd/zookeeper/mysql/kafka/mongo/redis集群,可以根据此性能评估服务器需求. 以下是针对一台华为云主机s3的压测数据:8核16G内存,普通磁盘(非SSD)( ...
- Windows 堆管理机制 [2] Windows 2000 – Windows XP SP1版本
2.Windows 2000 – Windows XP SP1 2.1 环境准备 环境 环境准备 虚拟机 32位Windows 2000 SP4 调试器 OllyDbg.WinDbg 编译器 VC6. ...
- 关于git pull机制和游戏开发热更新思考
前言 今天由于网速很慢,在git pull更新时我观看了git pull的日志,让我联想到和我现在从事的游戏开发中的热更热有一定的相似性,把思绪记录下来. git pull 日志 使用tortoi ...
- 4.6 Windows驱动开发:内核遍历进程VAD结构体
在上一篇文章<内核中实现Dump进程转储>中我们实现了ARK工具的转存功能,本篇文章继续以内存为出发点介绍VAD结构,该结构的全程是Virtual Address Descriptor即虚 ...
- 1.1 C++ STL 字符串构造函数
String 字符串操作容器是C++标准中实现的重要容器,其主要用于对字符串的高效处理,它和C风格中的string.h并不是同一个库,两个库有极大的差距,C库中的string.h主要面向过程提供一些处 ...
- C/C++ 病毒破坏手法总结
针对注册表恶意修改: #include <stdio.h> #include <Windows.h> // 禁用系统任务管理器 void RegTaskmanagerForbi ...
- 字节码编程,Javassist篇四《通过字节码插桩监控方法采集运行时入参出参和异常信息》
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 字节码编程插桩这种技术常与 Javaagent 技术结合用在系统的非入侵监控中,这样 ...
- Linux服务器Crontab定时任务配置
1.检查linux系统是否有crontab rpm -qa | grep crontab 2.如果未安装进行安装 yum -y install vixie-cron yum -y install cr ...