0%

消息队列之kafka可靠性的保证

[TOC]

概述

kafka 的可靠性保证和 tcp 的可靠性保证有一致的地方,就是都是基于消息的确认和重传来实现的。当然 tcp 还有滑动窗口,拥塞避免等特性。

为保证 producer 发送的数据,能可靠的发送到指定的 topic,topic 的每个 partition 收到。producer 发送的数据后,broker 都需要向 producer 发送 ack(acknowledgement 确认收到),如果 producer 收到 ack,就会进行下一轮的发送,否则重新发送数据。

常见的两种保证

副本数据同步策略

方案 优点 缺点
半数以上完成同步,broker 发送 ack 延迟低 选举新的leader时,如果要容忍 n 个节点的故障,那么系统需要 2n + 1个节点
全部完成同步,broker 发送 ack 选举新的leader节点时,容忍 n 台节点的故障,需要 n+1 个副本 延迟高

kafka 选择了第二种方案,原因如下

  • 第二种做数据镜像时,同样的容错下,第二种方案需要的节点更少。而kafka的每一个节点都存储这大量的数据,第一种方案会造成大量的数据冗余
  • 第二种的劣势是网络延迟高,但是网络延迟对kafka的影响较小

kakfa 的优化(ISR 同步副本列表)

第二种存在的单节点问题

采用第二种方案之后, 设想以下场景: leader 收到数据, 所有follower 都开始同步数据,但有一个follower ,因为某种故障,迟迟不能与leader进行同步,那leader就要一直等下去,直到它完成同步,才能发送ack。那么一个节点的性能,就会影响整个数据镜像集群的性能了。

解决

Leader 维护了一个动态的in-sync replica set(ISR 同步副本列表) ,意为和leader保持同步的follower 集合. 当ISR 中的 follower 完成数据的同步之后, Leader就会给follower 发送ack如果 follower 长时间未向 leader 同步数据,则该follower 将被 提出ISR 。

ack 机制

对于某些不太重要的数据,对数据的可靠性要求不要很高,能够容忍数据的少量丢失.所以没必要等ISR 中的follower 全部接受成功

所以Kafka 为用户提高了三种可靠性级别,当producer向leader发送数据时,可以通过request.required.acks参数来设置数据可靠性的级别:

request.required.acks = 1(默认)

这意味着producer在ISR中的leader已成功收到的数据并得到确认后发送下一条message。如果leader宕机了(因为只有 ISR 中的leader 确认了消息),则会丢失数据。

producer发送数据到leader,leader写本地日志成功,返回客户端成功;此时ISR中的副本还没有来得及拉取该消息,leader就宕机了,那么此次发送的消息就会丢失。

request.required.acks = 0

这意味着producer无需等待来自broker的确认而继续发送下一批消息。这种情况下数据传输效率最高,但是数据可靠性确是最低的。

request.required.acks = -1

producer需要等待ISR中的所有follower都确认接收到数据后才算一次发送完成,可靠性最高。但是这样也不能保证数据不丢失,比如当ISR中只有leader时(前面ISR那一节讲到,ISR中的成员由于某些情况会增加也会减少,最少就只剩一个leader),这样就变成了acks=1的情况。

acks=-1的情况下,数据发送到leader后 ,部分ISR的副本同步,leader此时挂掉。比如follower1h和follower2都有可能变成新的leader, producer端会得到返回异常,producer端会重新发送数据,数据可能会重复。

复制原理和同步方式

Kafka中topic的每个partition有一个预写式的日志文件,虽然partition可以继续细分为若干个segment文件,但是对于上层应用来说可以将partition看成最小的存储单元(一个有多个segment文件拼接的“巨型”文件),每个partition都由一些列有序的、不可变的消息组成,这些消息被连续的追加到partition中。

Kafka通过多副本机制实现故障自动转移,当Kafka集群中一个broker失效情况下仍然保证服务可用。在Kafka中发生复制时确保partition的日志能有序地写到其他节点上,N个replicas中,其中一个replica为leader,其他都为follower, leader处理partition的所有读写请求,与此同时,follower会被动定期地去复制leader上的数据。

LEO & HW

LEO: 指的是每个副本最大的offset ;

HW: 指的是消费者能见到的最大的offset .ISR 队列中最小的LEO;

follower 故障

follower 发生故障后会被临时提出ISR ,待该follower恢复后,follower会读取本地磁盘记录的上次的HW , 并将log文件高于HW的部分截掉,.从HW开始向leader进行同步,等该follower的LEO大于等于该Partition的HW,即follower 追上leader之后,就可以重新加入ISR 了。

leader 故障

leader 发生故障之后,会从ISR 中选出一个新的leader ,之后,为保证多个副本之间的数据一致性,其余的follower会先将各自的log文件高于HW的部分截掉,然后从新的leader同步数据。

可靠性的保证

消息传输保障

接下来讨论的是Kafka如何确保消息在producer和consumer之间传输。有以下三种可能的传输保障(delivery guarantee):

  • At most once: 消息可能会丢,但绝不会重复传输
  • At least once:消息绝不会丢,但可能会重复传输
  • Exactly once:每条消息肯定会被传输一次且仅传输一次

broker 的保证

Kafka的消息传输保障机制非常直观。当producer向broker发送消息时,一旦这条消息被commit,由于副本机制(replication)的存在,它就不会丢失。但是如果producer发送数据给broker后,遇到的网络问题而造成通信中断,那producer就无法判断该条消息是否已经提交(commit)。虽然Kafka无法确定网络故障期间发生了什么,但是producer可以retry多次,确保消息已经正确传输到broker中,所以目前Kafka实现的是at least once。

consumer

consumer 最好的方法就是,在引入去重机制后,实现 at least once 的消费方式,即先取数据,然后做业务逻辑,最后commit。

消息去重

如上一节所述,Kafka在producer端和consumer端都会出现消息的重复,这就需要去重处理。

Kafka文档中提及GUID(Globally Unique Identifier)的概念,通过客户端生成算法得到每个消息的unique id,同时可映射至broker上存储的地址,即通过GUID便可查询提取消息内容,也便于发送方的幂等性保证,需要在broker上提供此去重处理模块,目前版本尚不支持。

针对GUID, 如果从客户端的角度去重,那么需要引入集中式缓存,必然会增加依赖复杂度,另外缓存的大小难以界定。

不只是Kafka, 类似RabbitMQ以及RocketMQ这类商业级中间件也只保障at least once, 且也无法从自身去进行消息去重。所以我们建议业务方根据自身的业务特点进行去重,比如业务消息本身具备幂等性,或者借助Redis等其他产品进行去重处理。

参考

KafKa 数据可靠性保证!

Kafka 数据可靠性深度解读