背景&现象

生产微服务架构环境,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消息丢失问题处理的更多相关文章

  1. Kafka消息丢失

    1.Kafka消息丢失的情况: (1)auto.commit.enable=true,消费端自动提交offersets设置为true,当消费者拉到消息之后,还没有处理完 commit interval ...

  2. 实际业务处理 Kafka 消息丢失、重复消费和顺序消费的问题

    关于 Kafka 消息丢失.重复消费和顺序消费的问题 消息丢失,消息重复消费,消息顺序消费等问题是我们使用 MQ 时不得不考虑的一个问题,下面我结合实际的业务来和你分享一下解决方案. 消息丢失问题 比 ...

  3. 一文了解清楚kafka消息丢失问题和解决方案

    前言 今天分享一下kafka的消息丢失问题,kafka的消息丢失是一个很值得关注的问题,根据消息的重要性,消息丢失的严重性也会进行放大,如何从最大程度上保证消息不丢失,要从生产者,消费者,broker ...

  4. 【消息队列面试】11-14:kafka高可靠、高吞吐量、消息丢失、消费模式

    十一.kafka消息高可靠的解决方案 1.高可靠=避免消息丢失 解决消息丢失的问题 2.如何解决 (1)保证消息发送是可靠的(发成功了/落到partition) a.ack参数 发送端,采用ack机制 ...

  5. Kafka无消息丢失配置

    Kafka到底会不会丢数据(data loss)? 通常不会,但有些情况下的确有可能会发生.下面的参数配置及Best practice列表可以较好地保证数据的持久性(当然是trade-off,牺牲了吞 ...

  6. kafka消息会不会丢失

    转载:https://baijiahao.baidu.com/s?id=1583469327946027281&wfr=spider&for=pc 消息发送方式 想清楚Kafka发送的 ...

  7. Kafka leader副本选举与消息丢失场景讨论

    如果某个broker挂了,leader副本在该broker上的分区就要重新进行leader选举.来简要描述下leader选举的过程 1.4.1 KafkaController会监听ZooKeeper的 ...

  8. kafka系列八、kafka消息重复和丢失的场景及解决方案分析

    消息重复和丢失是kafka中很常见的问题,主要发生在以下三个阶段: 生产者阶段 broke阶段 消费者阶段 一.生产者阶段重复场景 1.根本原因 生产发送的消息没有收到正确的broke响应,导致pro ...

  9. Kafka设计解析(十一)Kafka无消息丢失配置

    转载自 huxihx,原文链接 Kafka无消息丢失配置 目录 一.Producer端二.Consumer端 Kafka到底会不会丢数据(data loss)? 通常不会,但有些情况下的确有可能会发生 ...

  10. Kafka在高并发的情况下,如何避免消息丢失和消息重复?kafka消费怎么保证数据消费一次?数据的一致性和统一性?数据的完整性?

    1.kafka在高并发的情况下,如何避免消息丢失和消息重复? 消息丢失解决方案: 首先对kafka进行限速, 其次启用重试机制,重试间隔时间设置长一些,最后Kafka设置acks=all,即需要相应的 ...

随机推荐

  1. SQLSERVER 数据库根据LCK_M_S对应的waitsorce 查看被锁的表信息的简单方法

    公司一个开发大牛召冠总搞过一个 DMSQLMONITOR 工具 能够识别Oracle以及SQLSERVER 数据库的锁和事务等问题, 非常好用 今天环境出现了不可用的情况, 所以这边着急进行一下问题分 ...

  2. pytest-xdist分布式

    使用pytest框架运行每条case的时候,都是上一条运行结束才会运行下一条,要是有成千上百条case 且每条运行2s那就是2*总条数,会浪费大量的时间和人力.为了节约时间和人力成本,pytest提供 ...

  3. bean的一生

    你曾读spring源码 "不知所云"."绞尽脑汁"."不知所措"嘛 那这篇文章可能会对你有所帮助,小编尝试用简单.易懂的例子来模拟sprin ...

  4. MySQL查询语句(1)

    连接数据库 mysql -hlocalhost -uroot -proot DQL-介绍 DQL英文全称是Data Query Language(数据查询语言),数据查询语言,用来查询数据库中表的记录 ...

  5. echarts 设置legend样式

    设置legend样式 legend: { x: 'center', data: ['班车', '包车'], icon: "circle", // 这个字段控制形状 类型包括 cir ...

  6. 玩一玩golang汇编

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 因为只是玩一玩,所以走的路线是:用C写代码,把C编译成AT ...

  7. LINUX安装和配置

    本篇文章为本人从零开始学习linux的学习心得,其中包含了 部署虚拟环境安装linux系统 .其中若有错误之处,请读者积极指出,让本人与读者共同进步. 第一章 部署虚拟环境安装linux系统及配置网路 ...

  8. 修改windows电脑键盘按键映射

    改键的需求 买了一把61键的小键盘,有些按钮没有,比如Home.End.四个方向键,这些键需要按Fn+XX来实现,所以上网查了一下键盘按键修改的方法,即把按键给改了,比如把右边的Ctrl改成方向键. ...

  9. 特定领域知识图谱融合方案:文本匹配算法之预训练Simbert、ERNIE-Gram单塔模型等诸多模型【三】

    特定领域知识图谱融合方案:文本匹配算法之预训练模型SimBert.ERNIE-Gram 文本匹配任务在自然语言处理中是非常重要的基础任务之一,一般研究两段文本之间的关系.有很多应用场景:如信息检索.问 ...

  10. Flask Echarts 实现历史图形查询

    Flask前后端数据动态交互涉及用户界面与服务器之间的灵活数据传递.用户界面使用ECharts图形库实时渲染数据.它提供了丰富多彩.交互性强的图表和地图,能够在网页上直观.生动地展示数据.EChart ...