Kafka-可靠性

1. Kafka对可靠性的承诺

分区消息的有序性
Kafka可以保证同一个分区消息的先入先出。生产者默认的分区策略是轮询+按键哈希映射,在分区数量不变的情况下,具有相同键的消息会被生产到同一个分区。但是需要注意的是,在max.in.flight大于1的情况下,重试可能会导致消息乱序

max.in.flight.requests.per.connection参数表示生产者在收到Broker上一个响应之前可以发送多少个消息批次。当该参数大于1时,重试就可能会导致分区消息乱序。譬如:第一个批次提交失败,第二个批次提交成功,而后上一个批次重试成功,两个批次的顺序就反过来了

Kafka对已提交消息作有限度的持久化保证
当消息被写入所有同步副本时,但不一定要落盘,则认为是已提交,但生产者可以选择接受不同类型的确认。

有限度表示至少一台保存该消息的Broker存活,Kafka就能保证该消息不会丢失。

消费者只能读取到已提交消息
Kafka利用高水位(HW)来定义消息的可见性,表示分区下哪些消息是可以被消费者消费的。

Kafka在每个副本对象中都保存两个属性:高水位和LEO值LEO值表示日志末端位移(Log End Offset),即下一条消息的位移值。在同一个副本对象中,高水位值永远小于LEO值。

首领副本的高水位代表着分区的高水位,为了确定这个值,Kafka在首领副本上还额外保存了所有跟随者副本的LEO值。当跟随者副本从首领副本拉取消息时,首领副本会用其请求的偏移量来更新该副本的LEO值。由于在LEO值之前的消息已经被跟随者副本保存,那么首领副本的高水位 = 所有同步副本LEO值的最小值。利用高水位实现了消费者只能获取到已提交消息的限制。

2. Kafka可靠性的核心

复制和分区多副本机制是Kafka Broker高可用、高持久性的核心。Kafka既不是完全的同步复制,也不是完全的异步复制,而是基于ISR实现了动态复制方案。

Kafka将副本分为两类:领导者副本(Leader Replica)和跟随者副本(Follower Replica)。每个分区都仅有一个领导者副本,所在broker称为分区首领,负责处理所有生产和消费请求;跟随者副本只是异步地与领导者副本进行同步,不对外提供服务,并在领导者故障时参与首领选举。

既然是异步的,就存在与领导者不实时同步的风险。为了精准的定义同步的含义,Kafka提出了同步副本的概念,并利用ISR集合来管理同步副本。ISR是一个动态调整的集合。

跟随者成为同步副本需要满足的条件:

  1. 跟随者副本落后于领导者副本之间的时间延迟较小,小于replica.lag.time.max.ms,与落后的消息数量无关;
  2. 跟随者与ZK维持着正常的心跳

只有被复制到所有同步副本的已提交消息才能被消费者消费。

滞后的同步副本会拖慢生产者和消费者。

3. 如何可靠地使用Kafka

3.1 Broker配置

1. 配置合适的复制系数 replication.factor >= 3
分区的副本数量,默认值为3。更大的复制系数意味着更高的可用性,但会占据更多的空间。这需要在可用性和存储空间作出权衡。

2. 配置合适的最少同步副本 min.insync.replicas > 1
最小同步副本保证了已提交消息至少被写入多少个副本,当同步副本数量不足时,Broker会停止接收新的写请求,此时该分区变成了只读模式。该参数需要在可用性A和数据一致性C之间做出权衡。

Kafka只对已提交消息作有限度的持久化保证。消息被写入所有同步副本即被认为是已提交的,有限度是指至少一个保存该消息的Broker存活,Kafka才能保证消息不丢失。如果当前只有一个同步副本,那么当该副本不可用时,数据就可能会丢失,因为数据可能没有落盘。

配合生产者的发送确认acks = all,就可以确定在返回确认前,至少有多少个副本能够收到消息。这种确认参数可靠性最高。

3. 禁用不完全的首领选举
分区首领选举时不允许非同步副本成为首领。当首领副本宕机时,一个同步副本会被选举为新的首领,这样已提交的消息不会丢失。但若在选举时,副本都是不同步的,这个时候就需要在可用性A和数据一致性C之间做出选择。若允许不同步的副本成为首领,就有数据丢失和不一致的风险;若不允许,那么分区在原首领副本恢复前将处于不可用状态。

建议禁用不完全的首领选举,保证数据一致性,并通过增加副本数来提高可用性。

3.2 生产者的使用

1. 配置合适的发送确认参数acks
只有当消息被写入所有同步副本时才被认为是已提交,但生产者可以选择接受不同类型的确认,该参数会导致生产者和Broker之间对已提交存在歧义。

  • acks=0,只要消息网络发送成功,Broker就会给生产者返回成功,吞吐量最高;
  • acks=1,首领副本收到消息并成功写入活跃片段(不一定刷盘),Broker就会给生产者返回成功,吞吐量取决于发送方式:同步、异步;
  • acks=all,所有同步副本收到消息并成功写入,Broker才会给生产者返回成功。配合min.insync.replicas参数(最小同步副本数,Broker参数),就可以确定在返回确认前,至少有多少个副本能够收到消息。这种确认参数可靠性最高。

2. 配置合适的重试参数,利用Kafka客户端进行重试
在需要至少一次语义,不允许消息丢失时,可重试错误尽量交给Kafka自动完成,业务代码里只处理非可重试错误以及重试次数超出的情况。

重试可能会导致消息重复、乱序。

max.in.flight.requests.per.connection参数表示生产者在收到Broker上一个响应之前可以发送多少个消息批次。当该参数大于1时,重试可能导致分区消息乱序。譬如:第一个批次提交失败,第二个批次提交成功,而后上一个批次重试成功,两个批次的顺序就反过来了

3. 妥善地处理异常
推荐使用异步发送,并在回调函数里处理异常。

3.3 消费者的使用

1. 应尽量减少计划外的再均衡,这部分情况主要集中于群组成员的意外离线

  • 未能及时发送心跳而被群组协调器踢出群组
    这需要合理的设置心跳超时时间和心跳发送频率,使得在超时之前,消费者至少能够发送三次心跳。另外,群组协调器需要利用心跳响应来传递再均衡开启信息,较高的心跳发送频率能够使得群组更快地进入再均衡。

  • 消费时间过长超出最大轮询间隔从而导致的主动退群
    若消费者消息处理时间过长,没能及时poll下一批消息,消费者会主动发起LeaveGroup请求,退出群组。适当调高轮询间隔时间,缩短消息消费时间,减小每次获取的消息数量。或者多线程消费消息,此时无法保证消费顺序,且需要小心地处理位移提交。

  • GC
    消费者频繁的Full GC导致长时间停顿,在实际场景中也很常见。

2. 妥善地处理偏移量
推荐使用手动提交,更加灵活、可控。要确保消息处理完成之后,在提交位移,否则容易出现消息丢失。在不同场景混用同步提交与异步提交:

  1. 对于常规性的位移提交使用异步提交来避免程序阻塞,对于瞬时错误不进行重试问题不大;
  2. 在关闭消费者前使用同步提交,来规避一些网络抖动、Broker GC等瞬时错误,确保在关闭前将位移正确的提交;
  3. 如果单次处理的消息量较大,可在处理途中分批提交。

这样既不影响TPS,也改善了消费者的高可用性。

另外,自动提交和手工提交均不能规避重复消费,提高提交频率只能减少重复数量,但不能避免。仅一次消费语义需要借助外部系统

3. 配置合适的偏移量重置参数 auto.offset.reset
消费者在读取一个没有偏移量或者偏移量无效(消费者长时间离线,偏移量对应的消息已经被删除)的情况下,应该如何处理。默认值是latest,表示从最新的消息开始消费。另一个取值是earliest。

4. 交付语义

4.1 最多一次(At Most Once Semantics)

消息可能会丢失,但绝不会被重复发送。

Kafka可以提供最多一次交付保障,生产者禁止重试,消费者需要妥善处理位移,防止重复消费。

4.2 至少一次(At Least Once)

消息不会丢失,但有可能被重复发送。Kafak默认提供至少一次交付保障。

4.2.1 实现基础

Kafka为了提供至少一次的交付保障,对消息可靠性做出了两点保障:

  1. 对已提交消息作有限度地持久化保障;
  2. 只有已提交消息对消费者可见。

当消息被写入所有同步副本时,但不一定要落盘,则认为是已提交,Kafak利用复制和分区多副本机制来保证消息的高持久性。生产者可以选择接受不同类型的确认。有限度表示至少一台保存该消息的Broker存活,Kafka就能保证该消息不会丢失。

消息的可见性则是利用高水位(HW)来定义。

4.2.2 配置及使用

实现至少一次语义,需要对生产者、消费者以及Broker进行合适的配置:

生产者

  1. 配置合适的重试参数,利用Kafka客户端进行重试
    可重试错误尽量交给Kafka自动完成,业务代码里只处理非可重试错误以及重试次数超出的情况。需要注意的是,重试可能会导致消息重复、乱序。

  2. 配置发送确认参数acks = all
    只有当消息被写入所有同步副本时才被认为是已提交,但生产者可以选择接受不同类型的确认,该参数会导致生产者和Broker之间对消息是否成功提交存在歧义。
    配置acks=all,只当所有同步副本收到消息并成功写入,Broker才会给生产者返回成功。配合Broker最小同步副本数参数,就可以确定在返回确认前,至少有多少个副本能够收到消息。这种确认参数可靠性最高。

  3. 妥善的处理发送异常
    使用异步发送方式时,在异步回调函数里第异常进行处理;

Broker

  1. 配置复制系数 replication.factor >= 3
    复制系数表示分区的副本数量,更大的复制系数意味着更高的可用性,但会占据更多的空间。这需要在可用性和存储空间作出权衡。

  2. 配置最少同步副本 min.insync.replicas > 1
    最小同步副本保证了已提交消息至少被写入多少个副本,当同步副本数量不足时,Broker会停止接收新的写请求,此时该分区变成了只读模式。该参数需要在可用性A和数据一致性C之间做出权衡。
    由于Kafka只对已提交消息作有限度的持久化保证。如果当前只有一个同步副本,那么当该副本不可用时,数据就可能会丢失,因为数据可能没有落盘。

  3. 禁用不完全的首领选举
    当首领副本宕机时,一个同步副本会被选举为新的首领,这样已提交的消息不会丢失。但若在选举时,副本都是不同步的,这个时候就需要在可用性A和数据一致性C之间做出选择。若允许不同步的副本成为首领,就有数据丢失和不一致的风险;若不允许,那么分区在原首领副本恢复前将处于不可用状态。

为了保证数据不丢失,需要禁用不完全的首领选举,并通过增加副本数来提高可用性。

消费者
为了保证消费者不丢失消息,要妥善的处理位移,确保处理完消息后,再提交位移。同时,偏移量自动重置参数要设置为earliest。

推荐使用手动提交,更加灵活、可控。在不同场景混用同步提交与异步提交:

  1. 对于常规性的位移提交使用异步提交来避免程序阻塞,对于瞬时错误不进行重试问题不大;
  2. 在关闭消费者前使用同步提交,来规避一些网络抖动、Broker GC等瞬时错误,确保在关闭前将位移正确的提交;
  3. 如果单次处理的消息量较大,可在处理途中分批提交。

这样既不影响TPS,也改善了消费者的高可用性。

4.3 精确一次(Exactly Once Semantics,EOS)

Kafka不完全支持精确一次语义,生产者可利用幂等或者事务生产者,而消费者则需要借助外部系统。

4.3.1 生产者

幂等生产者
幂等生产者可以在分区、会话作用域级别上实现消息生产的幂等性,我们可以安全地进行重试,而不会破坏系统状态。

幂等生产者的实现是利用Producer ID和自增的Sequence Number。每个Producer初始化时会分配一个唯一的ID,其发送的每条消息都对应一个自增的Sequence Number。Broker端摒弃具有相同Sequence Number的消息来实现去重。

通过enable.idempotence参数开启幂等生产者。

事务生产者
事务生产者可以实现跨分区、跨会话的消息去重。Kafka事务的隔离级别read committed 提交读,可以保证多条消息原子地写入到目标分区,也可以保证消费者只能读取到事务已提交的消息。

事务生产者利用两阶段提交(2PC),利用事务协调器来完成分布式事务。

事务生产者需要先开启幂等,然后在send方法前后调用事务方法:beginTransaction、commitTransaction。

4.3.2 消费者

消费者实现精确一次语义需要借助外部系统:

  1. 唯一键系统:消费结果幂等写入、消费前过滤;
  2. 事务系统:偏移量和消息消费结果放在一个事务中提交,消费者启动时从外部系统获取偏移量。

4.3.3 持久性的相关配置

精确一次交付还需要一些持久化配置,保证消息不丢失:(参见至少一次)

Broker

  1. 配置复制系数 replication.factor >= 3
  2. 配置最少同步副本 min.insync.replicas > 1
  3. 禁用不完全的首领选举

生产者

  1. 配置合适的重试参数,利用Kafka客户端进行重试
  2. 配置发送确认参数acks = all
  3. 妥善的处理发送异常

参考

《Kafka权威指南》
《Kafka核心技术与实战》
http://matt33.com/2018/11/04/kafka-transaction/
https://www.jianshu.com/p/b753527b91a6

猜你喜欢

转载自blog.csdn.net/cooper20/article/details/106880258