【Kafka技术内幕】(三):Kafka生产者:高可用Producer和ISR机制

973 阅读6分钟

我是少侠露飞。学习塑造人生,技术改变世界。

文章目录

引言

上一篇我们介绍了Kafka的基本概念及工作流程,附传送门👇
【Kafka技术内幕】(二):Kafka简介
本节我们将侧重介绍Kafka的Producer,即生产者,将介绍Kafka是如何发送消息的,以及如何保证消息发送过程的可靠性的。

1 Producer的分区策略

说到Kafka的生产者,首先要想到的就是Producer的分区策略,为何要分区呢,大概有两点原因:

  1. 提高了消息队列在集群中的扩展性,一个Topic可以有多个Partition组成。
  2. 可以提高并发量,因为可以按照Partition为单位进行读写了。

那么Producer在发送消息的时候如何指定Partition呢?
所有要分区的数据都会被封装成一个ProducerRecord对象。然后会按照如下规则进行分区:

  • 指明partition时,直接将该值作为partition值。
  • 若未指明,但是有key的情况下,将key的hash值与该topic下可用的分区数取余得到partition值。
  • 若既未指明partition,也没有key时,在第一次调用时随机生成一个整数(后续每次调用都会在这个整数上自增),将该整数与topic下可用的分区数取余得到partition值,也就是常说的round-robin算法。

2 Producer的可靠性

生产者唯一的职责就是发送消息,但是既然有发送,可能就会出现消息丢失,那么Kafka是如何保证消息的不丢失呢?
Kafka为了保证数据的可靠性,在生产者层面做了两个实现,一是在存储层面,即为每个partition设置了副本,分leader和follower,发送的时候只发送到leader上,然后leader将消息数据同步到follower;二是在发送层面,Kafka要求只有当leader回复了ack确认收到之后才会完成该条消息的投递,否则会重试。

2.1 Producer的ISR机制

如下图所示,生产者把消息发送到服务器上的leader上,然后leader将消息同步给自己的所有follower。
在这里插入图片描述
Kafka默认当所有follower都完成同步消息后再返回给生产者ack。但考虑这样一种场景,当leader有若干个follower,其中每次同步的时候都有一个follower因为故障,迟迟不能与leader完成同步,那么leader就要一直等下去。这个问题如何解决呢?
事实上,leader通过一个同步副本ISR(in-sync replica set)机制解决了这个难题。ISR意味着和leader保持同步的follower集合,当follower从leader完成同步消息之后,leader就会向follower发送ack,如果follower长时间没有响应ack,则会被剔除ISR,该时间阈值由replica.lag.time.max.ms指定(默认10s)。当leader宕机之后,则会从ISR中选举出新的leader。

事实上,在更老版本的Kafka里,导致follower被踢出ISR,除了响应时间,还有和leader的差异信息条数。但是现在差异条数已经被取消了,原因主要是Kafka是按批次发送消息,假设一批次大小是12,然后差异条数的阈值是10,这样每次生产者给leader发送消息都会导致follower被踢出ISR,然后follower完成同步之后,又被重新选进ISR,这样往复很消耗Kafka和ZK的性能。

2.2 Producer的ACK机制

现在我们已经知道生产者发送消息有个确认的机制,那么Kafka里是何时确认呢?Kafka是通过配置acks的值确认机制的,这里一共提供了三种情况,对应不同的ACK机制:

  1. acks=0,生产者不等待broker的响应。这种情况下延迟最低,但是有可能丢失数据,比较适合高吞吐量、接受消息丢失的场景。
  2. acks=1,生产者发送消息等待broker的响应,等待leader落盘成功后响应确认。这种情况下,如果是在leader完成同步消息给follower前发生故障,则可能发生消息丢失
  3. acks=-1,生产者发送消息等待broker的响应,直到leader和follower全部落盘成功后才会响应确认。此机制能严格保证不丢失数据。但当所有的follower同步完成之后,leader发送ack响应之前,leader发生了宕机,此时生产者会以为发送失败了,然后会重新发送数据给新的leader,因此该情况下会导致数据重复发送。

3 Producer的数据一致性

Kafka里数据有副本,那么必然会出现leader和follower不一致的情况,那么Kafka又是如何保证数据一致性的呢?如下图所示,这里先介绍两个概念:

  • LEO:Log end offset,即每个副本中的最后一个offset。
  • HW:High Watermark,所有副本中最小的LEO。
    在这里插入图片描述
  1. follower故障
    此时follower会被临时踢出ISR,待到恢复时,会读取本地磁盘里的HW,将log文件里高于HW的消息截断掉并从leader开始同步数据,直到follower赶上leader时会重新加入ISR。
  2. leader故障
    此时会从ISR选举出新的leader,然后会将所有副本里的log文件高于HW的消息截断,从新的leader重新同步数据。

注意,LEO和HW是保证数据一致性的机制,并不能保证数据不丢失或者数据不重复。

4 Producer的ExactlyOnce

聊完上面的,相信大家对Kafka生产者的可靠性、数据一致性有了一定的认识。这里再抛出一个比较深入的问题:Kafka能保证ExactlyOnce(即精准一次性)么,如果能,又是如何保证呢?
实际上,在交易业务里,对于精准一次性要求是比较高的,我们绝不能丢失交易数据,也不应该发起重复交易,这就是ExactlyOnce语义
Kafka本身可以保证两种语义,根据上面的ack机制,我们可以明显的得到:当ack=0时,消息可能丢失,此时对应的是At most once语义;当ack=-1时,消息不会丢失但可能重复消费,此时对应的At least once语义。此时我们应该想到,要想保证ExactlyOnce,应该在At least once语义的基础上保证幂等性即可。

幂等性:用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。

但Kafka如何保证消息发送的幂等性呢?
Kafka在0.11新增的功能,对于每个producer会分配一个唯一 的PID,发往同一个broker的消息会附带一个Sequence Number,broker端会对<PID,partitionId,Sequence Number>做一个缓存,当具有相同主键的消息提交时,Kafka只会持久化一条。

但是PID会随着生产者重启而发生变化,并且不同的partition对应的partitionId也不相同,所以Kafka的幂等性无法保证跨会话、跨分区的ExactlyOnce

点点关注,不会迷路