微服务 消息中间件kafka消息丢失问题

1. kafka消息丢失概述

1.1 kafka概述

kafka是一个分布式的基于发布订阅模式的消息中间件,主要应用于实时数据处理领域。

目前随着分布式微服务架构盛行,对系统高性能,高可用,高并发提出了更高的要求,消息中间件作为分布式微服务中异步,解耦,削峰的重要组件,变得越来越重要。

kafka官方文档总共九章,分别从入门,API,配置,设计,实施,运维,安全,连接,流九个方面详细阐述了卡夫卡应用设计架构。

kafka官方文档说明:https://kafka.apache.org/documentation/#semantics

1.2 kafka架构

Kafka 的整个架构非常简洁,是分布式的架构,主要由 Producer、Broker、Consumer 三部分组成,整个消息的流程也是根据此三个模块进行的。这里多说一句新版本kafka不依赖与zk了,但是目前大部分使用的仍然是老版本,zk主要就是配置信息以及选举的作用,zk有个很不友好的设计就是通过原子广播协议选举的时候不对外提供服务,这样就不能提供高可用特性。
在这里插入图片描述

1.3 kafka问题

消息中间件通用问题,消息丢失,消息堆积,重复消费,顺序消费问题。

消息丢失一般围绕生产者,消息组件,消费者三个环节展开,本文也是根据这三个维度进行分析探讨,不足之处还敬请指正。

消费堆积问题一般通过增加消费者扩容,或者异步消费解决。

重复消费需要消费者做幂等性,根据全局流水或者业务主键进行判断。

顺序消费问题这个kafka暂不支持,kafka同一个partation才是顺序的,但是实际生产环境中同时存在多个partation导致无法顺序消费,同步消费性能又很差,所以后续在研究。

2. kafka消息传递语义

所谓的消息传递语义是 Kafka 提供的 Producer 和 Consumer 之间的消息传递过程中消息传递的保证性。主要分为三种。详见kafka官方文档4.6章节详细说明

at most once:最多一次。消息可能会丢失,但是不会重复。
at least once:最少一次。消息不会丢失,但是可能会重复,
exactly once:精确一次。消息只会被精确处理一次,不会丢失重复。

1.首先当 Producer 向 Broker 发送数据后,会进行 commit,如果 commit 成功,由于 Replica 副本机制的存在,则意味着消息不会丢失,但是 Producer 发送数据给 Broker 后,遇到网络问题而造成通信中断,那么 Producer 就无法准确判断该消息是否已经被提交(commit),这就可能造成 at least once 语义。

2.在 Kafka 0.11.0.0 之前, 如果 Producer 没有收到消息 commit 的响应结果,它只能重新发送消息,确保消息已经被正确的传输到 Broker,重新发送的时候会将消息再次写入日志中;而在 0.11.0.0 版本之后, Producer 支持幂等传递选项,保证重新发送不会导致消息在日志出现重复。为了实现这个, Broker 为 Producer 分配了一个ID,并通过每条消息的序列号进行去重。也支持了类似事务语义来保证将消息发送到多个 Topic 分区中,保证所有消息要么都写入成功,要么都失败,这个主要用在 Topic 之间的 exactly once 语义。

其中启用幂等传递的方法配置:enable.idempotence = true。
启用事务支持的方法配置:设置属性 transcational.id = "指定值"。

3.从 Consumer 角度来剖析, 我们知道 Offset 是由 Consumer 自己来维护的, 如果 Consumer 收到消息后更新 Offset, 这时 Consumer 异常 crash 掉, 那么新的 Consumer 接管后再次重启消费,就会造成 at most once 语义(消息会丢,但不重复)。

4.如果 Consumer 消费消息完成后, 再更新 Offset, 如果这时 Consumer crash 掉,那么新的 Consumer 接管后重新用这个 Offset 拉取消息, 这时就会造成 at least once 语义(消息不丢,但被多次重复处理)。

分析总结:默认 Kafka 提供 「at least once」语义的消息传递,允许用户通过在处理消息之前保存 Offset 的方式提供 「at most once」 语义。如果我们可以自己实现消费幂等,理想情况下这个系统的消息传递就是严格的「exactly once」, 也就是保证不丢失、且只会被精确的处理一次,但是这样是很难做到的。

3. kafka消息丢失问题分析

从 Kafka 整体架构图我们可以得出消息传递过程中涉及到三个环节可能存在消息丢失的情况。
1.Producer 端发送消息给 Kafka Broker 端。
2.Kafka Broker 将消息进行同步并持久化数据。
3.Consumer 端从 Kafka Broker 将消息拉取并进行消费。

以上三个环节每一个步骤都可能出现丢失数据的情况,那么kafka怎么才能保证消息不丢失呢。

Kafka 只对已提交的消息做最大限度的持久化保证不丢失。

首先Producer 将消息提交到kafka中的Broker,只有当ISR中所有flower都确认收到消息后反馈给Leader,此时Leader才会给Producer确认收到消息,避免了Leader收到后,在同步到副本之前挂掉,消息丢失的场景。这也就是kafka对已经提交的消息做最大限度的持久化保证不丢失。

这里还有个误区需要注意,Leader收到消息会同步ISR中的flower并不是所有flower,这样既保证了时效性又保证了数据的可靠性,也算是一个这种的处理设计方式吧。

4. Producer端消息丢失分析

4.1 Producer消息发送流程

1)首先我们要知道一点就是 Producer 端是直接与 Broker 中的 Leader Partition 交互的,所以在 Producer 端初始化中就需要通过 Partitioner 分区器从 Kafka 集群中获取到相关 Topic 对应的 Leader Partition 的元数据 。

2)待获取到 Leader Partition 的元数据后直接将消息发送过去。

3)Kafka Broker 对应的 Leader Partition 收到消息会先写入 Page Cache,定时刷盘进行持久化(顺序写入磁盘)。

4) Follower Partition 拉取 Leader Partition 的消息并保持同 Leader Partition 数据一致,待消息拉取完毕后需要给 Leader Partition 回复 ACK 确认消息。

5)待 Kafka Leader 与 Follower Partition 同步完数据并收到所有 ISR 中的 Replica 副本的 ACK 后,Leader Partition 会给 Producer 回复 ACK 确认消息。

根据以及消息发送流程可以得出:Producer 端为了提升发送效率,减少IO操作,发送数据的时候是将多个请求合并成一个个 RecordBatch,并将其封装转换成 Request 请求「异步」将数据发送出去(也可以按时间间隔方式,达到时间间隔自动发送),所以 Producer 端消息丢失更多是因为消息根本就没有发送到 Kafka Broker 端。

4.2 Producer 端消息丢失场景

网络原因:由于网络抖动导致数据根本就没发送到 Broker 端。
数据原因:消息体太大超出 Broker 承受范围而导致 Broker 拒收消息。

4.3 Producer消息确认机制

Kafka Producer 端也可以通过配置来确认消息是否生产成功,配置参数:request.required.acks。

request.required.acks=0
他表示只要发送就认为成功,并不进行消息结合搜是否成功的ACK确认,不能保证消息是否发送成功,实际生产环境无法使用,不能保证数据可靠性。

request.required.acks=1
他表示当Leader接收成功时就进行ack确认,只要leader存活就可以保证消息不丢失,也能保证吞吐量,但是可用性不高,一旦leader收到消息,确认ack接收成功,同步flower之前挂掉就会导致数据丢失,实际生产环境根据业务场景慎用。

request.required.acks=-1或者all
他表示leader和ISR列表中的所有flower接收完成后,进行ack确认,可以最大限度的保证消息不丢失,同时也保证了系统的高可用性,因为有同步flower的操作所以性能稍逊于等于1的情况,但是提高了可用性,比较推荐的配置方式。

4.4 Producer端消息丢失解决方案

1.更换调用方式
弃用调用发后即焚的方式,使用带回调通知函数的方法进行发送消息,即 Producer.send(msg, callback), 这样一旦发现发送失败, 就可以做针对性处理。
(1)网络抖动导致消息丢失,Producer 端可以进行重试。
(2)消息大小不合格,可以进行适当调整,符合 Broker 承受范围再发送。
通过以上方式可以保证最大限度消息可以发送成功。

2 ACK 确认机制
需要将 request.required.acks 设置为 -1/ all,-1/all 表示有多少个副本 Broker 全部收到消息,才认为是消息提交成功的标识。
针对 acks = -1/ all , 这里有两种非常典型的情况:
(1)数据发送到 Leader Partition, 且所有的 ISR 成员全部同步完数据, 此时,Leader Partition 异常 Crash 掉,那么会选举新的 Leader Partition,数据不会丢失。
(2)数据发送到 Leader Partition,部分 ISR 成员同步完成,此时 Leader Partition 异常 Crash, 剩下的 Follower Partition 都可能被选举成新的 Leader Partition,会给 Producer 端发送失败标识, 后续会重新发送数据,数据可能会重复。

因此通过上面分析,我们还需要通过其他参数配置来进行保证:
replication.factor >= 2
min.insync.replicas > 1

3 重试次数 retries
该参数表示 Producer 端发送消息的重试次数。
需要将 retries 设置为大于0的数, 在 Kafka 2.4 版本中默认设置为Integer.MAX_VALUE。另外如果需要保证发送消息的顺序性,配置如下:
retries = Integer.MAX_VALUE
max.in.flight.requests.per.connection = 1

4 重试时间 retry.backoff.ms
该参数表示消息发送超时后两次重试之间的间隔时间,避免无效的频繁重试,默认值为100ms, 推荐设置为300ms。

5. Broker端消息丢失分析

5.1 broker端持久化流程

生产者发送消息到kafka broker端持久化流程。

Producer
kafka Broker
OS Cache
磁盘

Kafka Broker 集群接收到数据后会将数据进行持久化存储到磁盘,为了提高吞吐量和性能,采用的是「异步批量刷盘的策略」,也就是说按照一定的消息量和间隔时间进行刷盘。首先会将数据存储到 「PageCache」 中,至于什么时候将 Cache 中的数据刷盘是由「操作系统」根据自己的策略决定或者调用 fsync 命令进行强制刷盘,如果此时 Broker 宕机 Crash 掉,且选举了一个落后 Leader Partition 很多的 Follower Partition 成为新的 Leader Partition,那么落后的消息数据就会丢失。

既然 Broker 端消息存储是通过异步批量刷盘的,那么这里就可能会丢数据的!!!

5.2 Broker 端消息丢失场景

1.由于Kafka中并没有提供同步刷盘的方式,所以说从单个 Broker 来看还是很有可能丢失数据的。

2.kafka 通过「多 Partition (分区)多 Replica(副本)机制」已经可以最大限度的保证数据不丢失,如果数据已经写入 PageCache 中但是还没来得及刷写到磁盘,此时如果所在 Broker 突然宕机挂掉或者停电,极端情况还是会造成数据丢失。

5.3 Broker 端消息丢失解决方案

1.unclean.leader.election.enable
该参数表示有哪些 Follower 可以有资格被选举为 Leader , 如果一个 Follower 的数据落后 Leader 太多,那么一旦它被选举为新的 Leader, 数据就会丢失,因此我们要将其设置为false,防止此类情况发生。

2.replication.factor
该参数表示分区副本的个数。建议设置 replication.factor >=3, 这样如果 Leader 副本异常 Crash 掉,Follower 副本会被选举为新的 Leader 副本继续提供服务。

3 min.insync.replicas
该参数表示消息至少要被写入成功到 ISR 多少个副本才算"已提交",建议设置min.insync.replicas > 1, 这样才可以提升消息持久性,保证数据不丢失。
另外我们还需要确保一下 replication.factor > min.insync.replicas, 如果相等,只要有一个副本异常 Crash 掉,整个分区就无法正常工作了,因此推荐设置成: replication.factor = min.insync.replicas +1, 最大限度保证系统可用性。

6. Consumer端消息丢失分析

6.1 Consumer消费流程

在这里插入图片描述

1)Consumer 拉取数据之前跟 Producer 发送数据一样, 需要通过订阅关系获取到集群元数据, 找到相关 Topic 对应的 Leader Partition 的元数据。

2)然后 Consumer 通过 Pull 模式主动的去 Kafka 集群中拉取消息。

3)在这个过程中,有个消费者组的概念(不了解的可以看上面链接文章),多个 Consumer 可以组成一个消费者组即 Consumer Group,每个消费者组都有一个Group-Id。同一个 Consumer Group 中的 Consumer 可以消费同一个 Topic 下不同分区的数据,但是不会出现多个 Consumer 去消费同一个分区的数据。

4)拉取到消息后进行业务逻辑处理,待处理完成后,会进行 ACK 确认,即提交 Offset 消费位移进度记录。

5)最后 Offset 会被保存到 Kafka Broker 集群中的 __consumer_offsets 这个 Topic 中,且每个 Consumer 保存自己的 Offset 进度。 

根据上图以及消息消费流程可以得出消费主要分为两个阶段:

获取元数据并从 Kafka Broker 集群拉取数据。
处理消息,并标记消息已经被消费,提交 Offset 记录。

既然 Consumer 拉取后消息最终是要提交 Offset, 那么这里就可能会丢数据的!!!

6.2 Consumer 端消息丢失场景

1.可能使用的「自动提交 Offset 方式」。

2.拉取消息后「先提交 Offset,后处理消息」,如果此时处理消息的时候异常宕机,由于 Offset 已经提交了, 待 Consumer 重启后,会从之前已提交的 Offset 下一个位置重新开始消费, 之前未处理完成的消息不会被再次处理,对于该 Consumer 来说消息就丢失了。

3.拉取消息后「先处理消息,在进行提交 Offset」, 如果此时在提交之前发生异常宕机,由于没有提交成功 Offset, 待下次 Consumer 重启后还会从上次的 Offset 重新拉取消息,不会出现消息丢失的情况, 但是会出现重复消费的情况,这里只能业务自己保证幂等性。

6.3 Consumer 端消息丢失解决方案

在剖析 Consumer 端丢失场景的时候,我们得出其拉取完消息后是需要提交 Offset 位移信息的,因此为了不丢数据,正确的做法是:拉取数据、业务逻辑处理、提交消费 Offset 位移信息。
我们还需要设置参数 enable.auto.commit = false, 采用手动提交位移的方式。
另外对于消费消息重复的情况,业务自己保证幂等性, 保证只成功消费一次即可。

参考文档
微服务 消息中间件Kafka详解
kafka官网文档

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值