消息队列三|Kafka 如何做到高可用?

164 阅读10分钟

我们在之前的文章中,聊到了redis的高可用机制。

本篇文章中我们聊聊Kafka如何做到高可用

副本?

说到Kafka的高可用保障,自然都会想到副本。

什么是副本呢?副本是分布式系统对数据和服务提供的一种冗余方式。

Kafka 从0.8版本开始为分区引入了多副本的机制,通过增加副本数量来提升数据容灾的能力。并且通过多副本机制实现故障的自动转移,在kafka 集群中某个broker节点失效的情况下仍然能保持服务可用。

副本相关的有AR,ISR,HW,LEO 几个概念,下面我们一一介绍一下。

什么是AR,ISR:

分区中的所有副本统称为AR,而ISR是指与leader保持同步的副本集合,leader副本也是这个集合的一员。

什么是HW,LEO?

LEO标识每个分区中最后一条消息的下一个位置,分区的每个副本都有自己的LEO,ISR中最小的LEO即为HW,LEO对消费这不可见,消费者只能拉取HW之前的消息。如下图,HW=min(5,2,3)=2。

从生产者发出的消息首先会被写入分区的leader副本,不过还需要等待ISR集合中的所有副本同步完之后才能被认为已经提交,之后才会重新更新分区的HW,进而消费者可以消费到这条消息。

副本如何同步数据?

说到数据同步,不管是哪一个中间件,redis也好,kafka也好,或者其他的中间件产品,都要去解决两个在同步数据过程中可能带来的两个问题。

1 数据丢失的问题

2 数据不一致的问题

我们用一张图来表示ISR集合中Follower副本同步leader副本的过程。

关于数据丢失的问题

在第4步骤中,我特意将流程细分成2个步骤,图4.1 展示Follower副本同步leader副本过程,Follower向leader副本发送FetchRequest请求,并带有LEO的信息,leader 副本选取其中最小值作为新的HW,即min(10,4,5)=4。图4.2的过程展示leader之后连同消息和HW一起返回FetchRespone给follower副本。

在这个过程中,如果已经执行了图4.1的过程,但是还没执行图4.2的过程时,假设 relpica1,relpica2就都宕机了,之后relpica1重启成功,relpica1重启之后会根据之前的HW=2进行日志截断。此时如果leader 副本也宕机了,那么relpica1就被选为leader,之后原先的leader 也重启了,就成为follower,因为follower的HW不可能大于leader的HW,因为也会做一个日志截断,跟新的leader保持一致,即follower的HW=2。这样一来,相比之前的老leader是HW=4,我们发现就有两条数据丢失了。

关于数据不一致的问题

如果已经执行了图4.1的过程,但是还没执行图4.2的过程时,假设 relpica1,relpica2就都宕机了,之后relpica1重启成功,relpica1重启之后会根据之前的HW=2进行截断。此时如果leader 副本也宕机了,那么relpica1就被选为leader,生产者写入两条新的数据后,此时,新leader的HW=4。之后原先的leader 也重启了,就成为follower,由于老leader的HW也是为4,因此无需调整,这个过程就会发现有两条数据不一致了。

为了解决以上的两个问题,kafka 在0.11.0.0引入了Leader Epoch 的概念,epoch我们翻译为纪元,初始值为0,每当leader 变更一次,leader epoch的值就会加1,相当于为leader增设了一个版本号。

如何解决数据丢失的问题:

还是上面数据丢失的例子,已经执行了图4.1的过程,但是还没执行图4.2的过程时,假设 relpica1,relpica2就都宕机了,之后relpica1重启成功,relpica1重启之后,这时候不是直接根据HW进行截断,而是向leader发送请求,请求会带上leader epoch,因为leader未变更过,leader epoch和leader 的leader epoch是一样的,因此也就不需要截断日志了。此时 leader 副本宕机了,那么relpica1就被选为leader,leader epoch 也由0变更为1,之后不管老leader 有没有恢复,新leader的数据也不会出现丢失了。

这个过程,可能有人会有疑问,只要relpica1重启后不直接截断,向leader发送请求,就不会有数据丢失的情况,那跟leader epoch 没有什么关系呀。

还是上面数据丢失的例子,已经执行了图4.1的过程,但是还没执行图4.2的过程时,假设 relpica1,relpica2就都宕机了,之后relpica1重启成功,relpica1重启之后,这时候不是直接根据HW进行截断,而是向leader发送请求,请求会带上leader epoch。此时发现原先的leader已经被变更过。leader epoch已经由0增加为1了。那么relpica1和leader的leader epoch 不同了。怎么办呢?这个时候leader epoch 就派上用场了。 由于relpica1在未宕机前向leader发送同步数据请求,都会带上leader epoch,leader 也会保存一份relpica1【leader epoch->LEO】的配置,那么relpica1就能找到宕机前对应的LEO,数据拉取回来和自身现有的LEO对比,发现是相同的,因此也就不需要截断日志了。直接继续常规的同步数据流程即可。

如何解决数据不一致的问题:

如果已经执行了图4.1的过程,但是还没执行图4.2的过程时,假设 relpica1,relpica2就都宕机了,之后relpica1重启成功,此时如果leader 副本也宕机了,那么relpica1就被选为leader,生产者写入两条新的数据后,此时,新leader的HW=4。之后原先的leader 也重启了,就成为follower,向新leader请求同步数据,请求带上leader epoch=0,对比后,发现不一致,新leader的leader epoch=1,这时就会跟新leader之前维护的同一个leader epoch=0保存的数据去对比,发现新leader之前的leader epoch=0的HW=2,因此follower会以HW=2截断日志,之后继续常规的同步数据流程,这样就不会出现数据不一致的问题了。

多个副本消息的同步数据的一致性策略

通常只要leader 不宕机,我们就不需要关心follower的同步问题。不过一旦leader宕机,我们就要从follow选出一个新的leader。因此leader到follow的数据同步机制,需要保证的一个问题是:如果告知客户端已经成功提交了某条消息,那么就是leader宕机,也要保证新选举出来的leader中能够包含这条消息。 关于这样的同步机制,常见的做法是少数服从多数,与少数服从多数相关的一致性协议还是很多的,比如Zab,Raft等,即有2n+1个副本,提交给客户端之前,必须保证有n+1个副本同步完消息。 少数服从多数的优势在于系统的延迟取决于最快的几个节点。劣势是需要提供接近翻倍的副本数,要保证n+1个副本同步完成,就必须有2n+1个副本。这个跟目前国内很多互联网企业一直倡导的降本增效也有一定的冲突。

kafka采用的是ISR模型,只要保证ISR集合中的节点能保证同步,就可以告知生产端数据写入成功。优势在于副本数较少,甚至只配置ISR副本集合就可以,这样可以减少成本。劣势在于该方式取决于请求ISR集合中最慢的一个副本机器。如果出现ISR集合中有特别慢的机器,那么对客户端写入kafka的性能就有较大的影响。

应该怎么用

上面铺垫了那么多理论性的知识,那我们实际使用时,应该怎么做呢?这个问题也是一道常见的kafka面试题。

说说acks参数对消息持久化的影响?

首先这个acks参数,是在KafkaProducer,也就是生产者里设置的。

生产者往kafka写数据的时候,就可以设置这个acks参数。这个参数有三种常见的值可以设置,分别是:1,0 和 all(或者-1,-1和all是一样的效果)。

第一种,设置acks=0,这种模式是发后即忘的模式,只要把消息发送出去,不管那条数据有没有被写入kafka的磁盘,直接就认为这个消息发送成功了。

显然这种方式发送效率高,性能好,但是很可能出现消息丢失的情况,例如在消息还在发送途中,但是kafka短期宕机了,之后即使马上重启成功,这条消息也就丢失了。

第二种,设置 acks=1,意思是Partition Leader接收到消息并且写入本地磁盘了,就认为成功了,不管他其他的Follower有没有同步过去这条消息。

这种设置其实是kafka默认的设置,重要的事情说完三遍,这种设置其实是kafka默认的设置!这种设置其实是kafka默认的设置!

也就是说,默认情况下,你要是不管acks这个参数,只要Partition Leader写成功就算成功。但是这里有一个问题,万一Partition Leader刚刚接收到消息,Follower还没来得及同步数据过去,结果Leader所在的broker宕机了,此时也会导致这条消息丢失,因为生产端已经认为发送成功了。

最后一种情况,设置 acks=all,意思是,Partition Leader接收到消息之后,还必须要求ISR列表的Follower节点都同步完Leader的数据,才能告知生产端消息发送成功了。

如果Partition Leader刚接收到了消息,但是Follower没有收到消息,此时Leader宕机了,那么生产端会收到到这个消息没发送成功,它可以自己做一些处理,比如设置重试再次发送消息,或者对消息进行额外的持久化操作等。

总结

我们一开始直接来一波理论知识,对副本的一些概念进行了解释,之后讲解了副本如何同步数据,kafka如何做到数据不丢失,以及如何解决数据不一致的问题,之后又把其他中间件的同步策略和kafka的策略做了对比。目的都是为了更好的指导实践。我们在充分了理解上述的一些知识,之后稍微讲解一些生产端的acks的参数,大家应该就很容易掌握了。最后小编给个建议,在实际使用中,如果你的服务是允许消息少量丢失的,生产端可以使用acks=1的配置,如果你的服务对消息丢失是容忍度低的,最好使用acks=all的配置,至于ISR集合,一般配置三个副本即可,国内一些银行因为数据都是涉及到金额的处理,对消息丢失零容忍的,ISR集合配置5个副本。

最后,首发文章都在公众号,欢迎关注公众号: 程序员榕树