kafka消息丢失

Kafka 消息丢失与消费精确一次性-阿里云开发者社区 (aliyun.com)

消息丢失分三层:

1.上游: product (生产者)----> broker(中转站)

为什么丢?

 Producer (生产者)发数据给 Broker(中转站) 后,

1. 发生网络抖动 通信中断,那 消息就会丢失;

2. 发送的消息 本身不符合要求,如大小超过Broker端的承受能力等

3. broker端的ack参数,可能导致 副本没有同步上

如何解决?

  1. Producer要用带 回调通知 的方法 发送消息,即producer.send(msg, callback)。回调方法callback可以告诉我们 消息是否真的提交成功了,一旦出现消息发送失败的情况,可以使用代码进行补救。
  2. Producer如果发送消息失败,通过重试解决,若Broker端的ack 没成功发送给Producer(如网络抖动),Producer此时也会进行重试,再次发送原来的消息。这可能会导致消息重复发送。
  3. 消息不合格,则将消息格式进行调整

kafka 的 ack 的三种机制

request.required.acks 有三个值 0 1 -1(all)
0:生产者不会等待 broker 的 ack,(只发而已,不管 broker是否接收到;)
1:服务端会等待 ack 值 , leader 副本确认接收到消息后发送 ack , 但是如果 leader挂掉后他不确保是否复制完成新 leader 也会导致数据丢失。(只确保leader副本 接收到;)
-1(all):服务端会等所有的 follower 的副本受到数据后才会受到 leader 发出的ack,这样数据不会丢失

中游:broker->broker、broker->磁盘

Broker 集群间 同步 和持久化消息到磁盘;

为什么丢?

面试|Kafka常见面试问题总结 - 知乎 (zhihu.com)

Kafka 的复制机制分区的多副本 是 可靠性的核心。

Kafka 在发生崩溃时 分区的消息  有 备份。

1.Topic 副本因子个数:replication.factor >= 3
2.同步副本列表(ISR):min.insync.replicas = 2
3.禁用unclean选举:unclean.leader.election.enable=false

  • 副本

topic有分区(可以一个分区,也可以3个分区), 消息在分区里,

为了防止broker交换机宕机,要给 每个分区里的数据  备份几份,这就是 副本。

通过replication.factor参数实现,>=3就是 给每个分区弄3个备份。一般来说,副本设为3可以满足大部分的使用场景。

每个副本 分布在 不同的Broker上,因为如果这些Broker在同一个机架上,如果故障,分区就会不可用。所以把Broker分布在不同的机架上,可以使用broker.rack参数配置Broker所在机架的名称。

副本的优点是:假如broker 宕机时,仍能从副本里 读取写入数据。所以,可用性、可靠性和更少的故障。

缺点:占用磁盘空间

副本有两种类型:领导者副本(Leader Replica)和追随者副本(Follower Replica),

每个分区创建时  要选举一个副本作为领导者副本,其余的副本自动变为追随者副本。

所有的读写请求 都必须发往 leader 领导者副本所在的 Broker 处理(=所有的请求都必须由领导者副本来处理),

追随者副本  不对外提供服务(=不处理客户端请求; =不响应消费者和生产者的读写请求 ),它唯一的任务就是从领导者副本 异步拉取消息,并写入到自己的提交日志中,从而实现与领导者副本的同步。

  • 同步副本列表 ISR

In-sync replica(ISR)称之为同步副本,ISR中的副本都是与Leader进行同步的副本,所以不在该列表的follower会被认为与Leader是不同步的。

白话:ISR里 只保留消息可靠的副本,那些消息落后的副本会被移出ISR列表里;

broker端有个参数replica.lag.time.max.ms, 表示 默认在10秒内 ,follower副本 滞后 Leader副本。

这就意味着,只要follower副本 落后于leader副本的时间间隔不超过10秒,就可以认为该follower副本与leader副本是同步的,所以哪怕当前follower副本落后于Leader副本几条消息,只要在10秒之内赶上Leader副本,就不会被踢出出局。

可以看出ISR是一个动态的,所以即便是为分区配置了3个副本,还是会出现同步副本列表中只有一个副本的情况(其他副本由于不能够与leader及时保持同步,被移出ISR列表)。如果这个同步副本变为不可用,我们必须在可用性一致性之间作出选择(CAP理论)。

Kafka 对可靠性保证的定义,消息只有在被写入到所有同步副本之后才被认为是已提交的。但如果这里的“所有副本”只包含一个同步副本,那么在这个副本变为不可用时,数据就会丢失。如果要确保已提交的数据被写入不止一个副本,就需要把最小同步副本数量设置为大一点的值。对于一个包含3 个副本的主题分区,如果min.insync.replicas=2 ,那么至少要存在两个同步副本才能向分区写入数据。

如果进行了上面的配置,此时必须要保证ISR中至少存在两个副本,如果ISR中的副本个数小于2,那么Broker就会停止接受生产者的请求。尝试发送数据的生产者会收到NotEnoughReplicasException异常,消费者仍然可以继续读取已有的数据。

  • 禁用unclean选举

选择一个同步副本列表中的分区作为leader 分区的过程称为clean leader election。注意,这里要与在非同步副本中选一个分区作为leader分区的过程区分开,在非同步副本中选一个分区作为leader的过程称之为unclean leader election

由于ISR是动态调整的,所以会存在ISR列表为空的情况,

通常来说,非同步副本落后 Leader 太多,因此,如果选择这些副本作为新 Leader,就可能出现数据的丢失。毕竟,这些副本中保存的消息远远落后于老 Leader 中的消息。在 Kafka 中,选举这种副本的过程可以通过Broker 端参数 unclean.leader.election.enable控制是否允许 Unclean 领导者选举。开启 Unclean 领导者选举可能会造成数据丢失,但好处是,它使得分区 Leader 副本一直存在,不至于停止对外提供服务,因此提升了高可用性。反之,禁止 Unclean Leader 选举的好处在于维护了数据的一致性,避免了消息丢失,但牺牲了高可用性。分布式系统的CAP理论说的就是这种情况。

不幸的是,unclean leader election的选举过程仍可能会造成数据的不一致,因为同步副本并不是完全同步的。由于复制是异步完成的,因此无法保证follower可以获取最新消息。比如Leader分区的最后一条消息的offset是100,此时副本的offset可能不是100,这受到两个参数的影响:

replica.lag.time.max.ms:同步副本滞后与leader副本的时间
zookeeper.session.timeout.ms:与zookeeper会话超时时间

简而言之,如果我们允许不同步的副本成为leader,那么就要承担丢失数据和出现数据不一致的风险。如果不允许它们成为leader,那么就要接受较低的可用性,因为我们必须等待原先的首领恢复到可用状态。

关于unclean选举,不同的场景有不同的配置方式。对数据质量和数据一致性要求较高的系统会禁用这种unclean的leader选举(比如银行)。如果在可用性要求较高的系统里,比如实时点击流分析系统, 一般不会禁用unclean的leader选举。

下游 broker->消费者

5秒内,自动提交偏移量;宕机时,重复消费;

解决:false参数设置关闭自动,在代码里 消费完,加上一行代码 手动提交ack.acknowledge()

白话 总结:

1.上游:生产者失败重试次数,为0 消息丢失,为1可能消息重复发送;

回调callback 配合 ack,知道发送成功了没有;

topic 可以有一个分区,也可以有3个分区,(这里决定了以后消费的顺序性),

为了防止 broker宕机丢数据, 所以给每个分区的数据 都 副本备份3份,分散在不同的broker机器上,

好处是 可用性高,坏处是占用磁盘空间;

副本又分为 leader和follow,所有的生产、消费 都是leader实现的,follow仅仅是为了宕机时 可用。

2.中游:

1 生产者和leader之间的同步、2 leader和follow之间的同步、3leader和磁盘的同步

1.牵扯到ack

生产者 发到broker,有ack的3种机制;

同步给leader;

all:同步给所有follow;-->消息的可靠性

2.牵扯到落后follow

broker端有个参数:默认10秒内,follow跟不上leader的同步,就被移出ISR列表;

常识:3副本里的数据 并不都是完全一致的;

ISR列表里 才是可靠 一致性的;

所以,选举时 其实 是使用ISR列表维护的副本;

参数min.insync.replicas=2 ,维护2个可靠性的副本,brker宕机时,从中选取。避免选取落后的副本 导致数据丢失。

3.刷盘丢失

数据在pagecache内存里,减少 每次poll的数据量,让数据尽快的落盘;

重复消费

1.生产者的失败重试,导致重复

2. partition balace机制。消费端 从分区里 消费消息,如果默认5分钟内 没办法处理完这一批消息的话,触发rebalce机制 导致offset自动提交失败;重新rebalance后,消费端 还是从之前没有提交的offset的位置开始去消费,导致重复消费。

1.双方商量好,key value结构,key时唯一的;

设计一个合理的主键ID  随机时间戳(精确到微妙)+随机数;或者自增id,   确保ID全局唯一;在写入数据库时,因为全局唯一,如果这条数据已经存在,会被覆盖 或 判断丢弃;优化:放到一个有序set里,set去重 唯一性,再批量入库;

2.避免触发balance:如

1.异步处理消息

2. 超时时间 加大一点

3.减少一次性从broker上拉取数据的条数

顺序消费

Kafka/RocketMQ 多线程消费时如何保证消费顺序? - 豆奶特 (dounaite.com)

单分区的话,其实写入是有序的;

多分区的话, 如果多线程 怎么保证顺序

kafka 的消费类 KafkaConsumer 不是线程安全的

一、这个消费模型有很大问题,

从中看出每个 KafkaConsumer 会负责固定的分区,因此没办法提升单个分区的消费能力,

如果一个主题分区 数量很多,只能通过增加 KafkaConsumer 实例 提高消费能力,这样一来线程数量过多,导致项目 Socket 连接开销巨大,项目中一般不用该线程模型去消费。

还原kafka快照 kafka回退机制_还原kafka快照

2、单 KafkaConsumer 实例 + 多 worker 线程

把消息消费逻辑  放入单独的线程中去处理,不需要创建多个 KafkaConsumer 实例就可进行多线程消费,根据消费的负载情况动态调整 worker 线程

还原kafka快照 kafka回退机制_java_02

但 由于消费逻辑是用多线程消费的,因此不能保证其消息的消费顺序,

可以引入阻塞队列的模型,一个 woker 线程对应一个阻塞队列,线程不断轮训从阻塞队列中获取消息进行消费,对具有相同 key 的消息进行取模,并放入相同的队列中,实现顺序消费,

还原kafka快照 kafka回退机制_java_03

但上两个消费线程模型,有个问题:

在消费过程中,如果 Kafka 消费组reblance,此时的分区被分配给其它消费组了,

因此在消费前,还需要主动进行判断此分区是否被分配给其它消费者处理,并且还需要锁定该分区在消费当中不能被分配到其它消费者中(但 kafka 目前做不到这一点)。

参考 RocketMQ 的做法:

在消费前主动调用 ProcessQueue#isDropped 方法判断队列是否已过期,并且对该队列进行加锁处理(向 broker 端请求该队列加锁)。

RocketMQ

RocketMQ 不像 Kafka 那么“原生”,RocketMQ 本身的消费模型就是单 consumer 实例 + 多 worker 线程模型,可以从以下方法观摩 RocketMQ 的消费逻辑:


org.apache.rocketmq.client.impl.consumer.PullMessageService#run


RocketMQ 会为  每个队列 分配一个 PullRequest,并将其放入 pullRequestQueue,PullMessageService 线程会不断轮询从 pullRequestQueue 队列中取出 PullRequest 去拉取消息,接着将拉取到的消息给到 ConsumeMessageService 处理,ConsumeMessageService 有两个子接口:


// 并发消息消费逻辑实现类 org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService; // 顺序消息消费逻辑实现类 org.apache.rocketmq.client.impl.consumer.ConsumeMessageOrderlyService;


其中,ConsumeMessageConcurrentlyService 内部有一个线程池,用于并发消费,同样地,如果需要顺序消费,那么 RocketMQ 提供了 ConsumeMessageOrderlyService 类进行顺序消息消费处理。

经过对 Kafka 消费线程模型的思考之后,从 ConsumeMessageOrderlyService 源码中能够看出 RocketMQ 能够实现局部消费顺序,我认为主要有以下两点:

1)RocketMQ 会为每个消息队列建一个对象锁,这样只要线程池中有该消息队列在处理,则需等待处理完才能进行下一次消费,保证在当前 Consumer 内,同一队列的消息进行串行消费。

2)向 Broker 端请求锁定当前顺序消费的队列,防止在消费过程中被分配给其它消费者处理从而打乱消费顺序。

1)多分区的情况下:

如果想要保证 Kafka 在消费时要保证消费的顺序性,可以使用每个线程维护一个 KafkaConsumer 实例,并且是一条一条地去拉取消息并进行消费(防止重平衡时有可能打乱消费顺序);对于能容忍消息短暂乱序的业务(话说回来, Kafka 集群也不能保证严格的消息顺序),可以使用单 KafkaConsumer 实例 + 多 worker 线程 + 一条线程对应一个阻塞队列消费线程模型。

1)单分区的情况下:

由于单分区不存在重平衡问题,以上两个线程模型的都可以保证消费的顺序性。

另外如果是 RocketMQ,使用 MessageListenerOrderly 监听消费可保证消息消费顺序。

很多人也有这个疑问:既然 Kafka 和 RocketMQ 都不能保证严格的顺序消息,那么顺序消费还有意义吗?

一般来说普通的的顺序消息能够满足大部分业务场景,如果业务能够容忍集群异常状态下消息短暂不一致的情况,则不需要严格的顺序消息。

消费堆积

消费的速度 跟不上 broker发送的速度;或者 检查 消费失败重试的次数;

kafka-consumer-groups.sh命令 查看消费组 存在的滞后消费:

$ bin/kafka-consumer-groups.sh --bootstrap-server cdh02:9092 --describe --group my-group
TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET   LAG          CONSUMER-ID HOST CLIENT-ID
主题
分区    
当前offset   
   
LEO 滞后消息数      
消费者id     
主机  
客户端
id



一般情况下,如果运行良好,CURRENT-OFFSET的值会与LOG-END-OFFSET的值非常接近。通过这个命令可以查看哪个分区的消费出现了滞后。

解决:

1.生产端 几个分区,消费端 这里就用几个 消费实例;消费数 = 分区数

2.若某个分区的数量过多 导致的数据堆积

    2.1.优化 消费业务逻辑(收消息的OnMessage方法,不处理任何业务,把这消息放到一个内存队列就返回) 

   2.2 多线程消费,考虑消费顺序的问题

3.系统降级,关闭一些不重要业务,(减少发送方发送数据量),最低限度让系统还能正常运转,服务一些重要业务

消费异常

消费异常放进 死信队列

Spring-Kafka 提供消费重试的机制。

当消息消费失败的时候,Spring-Kafka 会通过消费重试机制,重新投递该消息给 Consumer ,让 Consumer 重新消费消息 。

默认情况下,Spring-Kafka 达到配置的重试次数时,【每条消息的失败重试时间,由配置的时间隔决定】Consumer 如果依然消费失败 ,那么该消息就会进入到死信队列。

Spring-Kafka 封装了消费重试和死信队列, 将正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),将存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。

我们在应用中可以对死信队列中的消息进行监控重发,来使得消费者实例再次进行消费,消费端需要做幂等性的处理。
 

hive

Hive 高频面试题 30 题 - 知乎 (zhihu.com)