Kafka有两个很重要的配置参数,acksmin.insync.replicas.其中acks是producer的配置参数,min.insync.replicas是Broker端的配置参数,这两个参数对于生产者不丢失数据起到了很大的作用.接下来,本文会以图示的方式讲解这两个参数的含义和使用方式。通过本文,你可以了解到:

  • Kafka的分区副本
  • 什么是同步副本(In-sync replicas)
  • 什么是acks确认机制
  • 什么是最小同步副本
  • ack=all与最小同步副本是如何发挥作用的

分区副本

Kafka的topic是可以分区的,并且可以为分区配置多个副本,改配置可以通过replication.factor参数实现. Kafka中的分区副本包括两种类型:领导者副本(Leader Replica)和追随者副本(Follower Replica),每个分区在创建时都要选举一个副本作为领导者副本,其余的副本自动变为追随者副本. 在 Kafka 中,追随者副本是不对外提供服务的,也就是说,任何一个追随者副本都不能响应消费者和生产者的读写请求. 所有的请求都必须由领导者副本来处理. 换句话说,所有的读写请求都必须发往领导者副本所在的 Broker,由该 Broker 负责处理. 追随者副本不处理客户端请求,它唯一的任务就是从领导者副本异步拉取消息,并写入到自己的提交日志中,从而实现与领导者副本的同步.

Kafka默认的副本因子是3,即每个分区只有1个leader副本和2个follower副本.具体如下图所示:

上面提到生产者客户端仅写入Leader broker,跟随者异步复制数据。由于Kafka是一个分布式系统,必然会存在与 Leader 不能实时同步的风险,所以需要一种方法来判断这些追随者是否跟上了领导者的步伐, 即追随者是否同步了最新的数据.换句话说,Kafka 要明确地告诉我们,追随者副本到底在什么条件下才算与 Leader 同步?这就是下面所要说的ISR同步副本机制.

同步副本(In-sync replicas)

In-sync replica(ISR)称之为同步副本,ISR中的副本都是与Leader进行同步的副本,所以不在该列表的follower会被认为与Leader是不同步的. 那么,ISR中存在是什么副本呢?首先可以明确的是:Leader副本总是存在于ISR中. 而follower副本是否在ISR中,取决于该follower副本是否与Leader副本保持了“同步”.

尖叫提示:对于”follower副本是否与Leader副本保持了同步”的理解如下:

(1)上面所说的同步不是指完全的同步,即并不是说一旦follower副本同步滞后与Leader副本,就会被踢出ISR列表.

(2)Kafka的broker端有一个参数replica.lag.time.max.ms, 该参数表示follower副本滞后与Leader副本的最长时间间隔,默认是10秒. 这就意味着,只要follower副本落后于leader副本的时间间隔不超过10秒,就可以认为该follower副本与leader副本是同步的,所以哪怕当前follower副本落后于Leader副本几条消息,只要在10秒之内赶上Leader副本,就不会被踢出出局.

(3)如果follower副本被踢出ISR列表,等到该副本追上了Leader副本的进度,该副本会被再次加入到ISR列表中,所以ISR是一个动态列表,并不是静态不变的。

如上图所示:Broker3上的partition1副本超过了规定时间,未与Leader副本同步,所以被踢出ISR列表,此时的ISR为[1,3].

acks确认机制

acks参数指定了必须要有多少个分区副本收到消息,生产者才认为该消息是写入成功的,这个参数对于消息是否丢失起着重要作用,该参数的配置具体如下:

  • acks=0,表示生产者在成功写入消息之前不会等待任何来自服务器的响应. 换句话说,一旦出现了问题导致服务器没有收到消息,那么生产者就无从得知,消息也就丢失了. 改配置由于不需要等到服务器的响应,所以可以以网络支持的最大速度发送消息,从而达到很高的吞吐量。

  • acks=1,表示只要集群的leader分区副本接收到了消息,就会向生产者发送一个成功响应的ack,此时生产者接收到ack之后就可以认为该消息是写入成功的. 一旦消息无法写入leader分区副本(比如网络原因、leader节点崩溃),生产者会收到一个错误响应,当生产者接收到该错误响应之后,为了避免数据丢失,会重新发送数据.这种方式的吞吐量取决于使用的是异步发送还是同步发送.

    尖叫提示:如果生产者收到了错误响应,即便是重新发消息,还是会有可能出现丢数据的现象. 比如,如果一个没有收到消息的节点成为了新的Leader,消息就会丢失.

  • acks =all,表示只有所有参与复制的节点(ISR列表的副本)全部收到消息时,生产者才会接收到来自服务器的响应. 这种模式是最高级别的,也是最安全的,可以确保不止一个Broker接收到了消息. 该模式的延迟会很高.

最小同步副本

上面提到,当acks=all时,需要所有的副本都同步了才会发送成功响应到生产者. 其实这里面存在一个问题:如果Leader副本是唯一的同步副本时会发生什么呢?此时相当于acks=1.所以是不安全的.

Kafka的Broker端提供了一个参数min.insync.replicas,该参数控制的是消息至少被写入到多少个副本才算是”真正写入”,该值默认值为1,生产环境设定为一个大于1的值可以提升消息的持久性. 因为如果同步副本的数量低于该配置值,则生产者会收到错误响应,从而确保消息不丢失.

Case 1

如下图,当min.insync.replicas=2且acks=all时,如果此时ISR列表只有[1,2],3被踢出ISR列表,只需要保证两个副本同步了,生产者就会收到成功响应.

Case 2

如下图,当min.insync.replicas=2,如果此时ISR列表只有[1],2和3被踢出ISR列表,那么当acks=all时,则不能成功写入数;当acks=0或者acks=1可以成功写入数据.

Case 3

这种情况是很容易引起误解的,如果acks=all且min.insync.replicas=2,此时ISR列表为[1,2,3],那么还是会等到所有的同步副本都同步了消息,才会向生产者发送成功响应的ack.因为min.insync.replicas=2只是一个最低限制,即同步副本少于该配置值,则会抛异常,而acks=all,是需要保证所有的ISR列表的副本都同步了才可以发送成功响应. 如下图所示:

总结

acks=0,生产者在成功写入消息之前不会等待任何来自服务器的响应.

acks=1,只要集群的leader分区副本接收到了消息,就会向生产者发送一个成功响应的ack.

acks=all,表示只有所有参与复制的节点(ISR列表的副本)全部收到消息时,生产者才会接收到来自服务器的响应,此时如果ISR同步副本的个数小于min.insync.replicas的值,消息不会被写入.