Kafka-如何保证消费者的可靠性

Kafka-如何保证消费者的可靠性

只有那些被提交到kafka的数据,也就是那些已经被写入所有同步副本的数据,对消费者是可用的,这意味着消费者得到的消息已经具备了一致性。消费者唯一要做的是跟踪哪些消息是已经读取过的,哪些是还没有读取过的。这是在读取消息时不丢失消息的关键。

在从分区读取数据时,消费者会获取一批事件,检查这批事件里最大的偏移量,然后从这个偏移量开始读取另外一批事件。这样可以保证消费者总能以正确的顺序获取新数据,不会错过任何事件。

如果一个消费者退出,另一个消费者需要知道从什么地方开始继续处理,它需要知道前一个消费者在退出前处理的最后一个偏移量是多少。它们把当前读取的偏移量保存起来,在退出之后,同一个群组里的其他消费者就可以接手它们的工作。如果消费者提交了偏移量却未能处理完消息,那么就有可能造成消息丢失,这也是消费者丢失消息的主要原因。在这种情况下,如果其他消费者接手了工作,那些没有被处理完的消息就会被忽略,永远得不到处理。

消费者的可靠性配置

1.group.id:如果两个消费者具有相同的group.id,并且订阅了同一个主题,那么每个消费者会分到主题分区的一个子集,也就是说他们只能读到所有消息的一个子集(不过群组会读取主题所有的消息)。如果希望消费者可以看到主题的所有消息,那么需要为它们设置唯一的group.id

2.auto.offset.reset:这个参数指定了在没有偏移量可提交时或者请求的偏移量在broker上不存在时,消费者会做什么。这个参数有两种配置:

  • earliest:消费者会从分区的开始位置读取数据,不管偏移量是否有效,这样会导致消费者读取大量的重复数据,但可以保证最少的数据丢失。
  • latest:消费者会从分区的末尾开始读取数据,这样可以减少重复处理消息,但是很有可能会错过一些消息。

3.enable.auto.commit:这是一个非常重要的配置参数,可以让消费者基于任务调度自动提交偏移量,也可以在代码里手动提交偏移量。自动提交的一个最大好处是,在实现消费者逻辑时可以少考虑一些问题。如果在消费者轮询操作里处理所有的数据,那么自动提交可以保证只提交已经处理过的偏移量。自动提交的缺点:无法控制重复处理消息(比如消费者在自动提交偏移量之前停止处理消息),而且如果把消息交给另外一个后台线程去处理,自动提交机制可能会在消息还没有处理完毕就提交偏移量。

4.auto.commit.interval.ms:此参数与enable.auto.commit有直接的联系,如果选择了自动提交偏移量,可以通过此参数配置提交的频度,默认值是每5秒钟提交一次。一般来说,频繁提交会增加额外的开销,但也会降低重复处理消息的概率。

显式提交偏移量

1.总是在处理完事件后再提交偏移量

如果所有的处理都是在轮询里完成,并且不需要在轮询之间维护状态(比如为了实现聚合操作),那么可以使用自动提交,或者在轮询结束时进行手动提交。

2.提交频度是性能和重复消息数量之间的权衡

即使是在最简单的场景里,比如所有的处理都在轮询里完成,并且不需要在轮询之间维护状态,仍然可以再一个循环里多次提交偏移量(甚至可以在每处理完一个事件之后),或者多个循环里只提交一次(与生产者的acks=all配置有点类似),这完全取决于在性能和重复处理消息之间做出的权衡。

3.确保对提交的偏移量心里有数

在轮询过程中提交偏移量有一个不好的地方,就是提交的偏移量有可能是读取到的最新偏移量,而不是处理过的最新偏移量。要记住,在处理完消息后再提交偏移量时非常关键的--否则会导致消费者错过消息。

4.再均衡

在设计应用程序时要注意处理消费者的再均衡问题。一般要在分区被撤销之前提交偏移量,并在分配到新分区时清理之前的状态。

5.消费者可能需要重试

有时候,在进行轮询之后,有些消息不会被完全处理,想稍后再来处理。例如,假设要把kafka的数据写到数据库里,不过那个时候数据库不可用,于是想稍后重试。要注意,提交的是偏移量,而不是对消息的确认,这个与传统的发布可订阅消息系统不太一样。如果记录#30处理失败,但记录#31处理成功,那么不应该提交#31,否则会导致#31以内的偏移量都被提交,包括#30在内,而这可能不是想看到的结果。可以采用以下两种模式来解决这个问题。

  • 第一种模式。在遇到可重试错误时,提交最后一个处理成功的偏移量,然后把还没有处理好的消息保存到缓冲区(这样下一个轮询就不会把它们覆盖掉),调用消费者的pause()方法来确保其它的轮询不会返回数据(不需要担心在重试时缓冲区溢出),在保持轮询的同事尝试重新处理。如果重试成功,或者重试次数达到上限并决定放弃,那么把错误记录下来并丢弃消息,然后调用resume()方法让消费者继续从轮询里获取新数据
  • 第二种模式。在遇到可重试错误时,把错误写入一个独立的主题,然后继续。一个独立的消费者群组负责从该主题上读取错误消息,并进行重试,或者使用其中的一个消费者同时从该主题上读取错误消息并进行重试,不过在重试时需要暂停该主题。这种模式有点像其他消息系统里的dead-letter-queue

 

6.消费者可能需要维护状态

 

有时候希望在多个轮询之间维护状态,例如,想计算消息的移动平均数,希望在首次轮询之后计算平均数,然后在后续的轮询中更新这个结果。如果进程重启,不仅需要从上一个偏移量开始处理数据,还需要恢复移动平均数。有一种办法是在提交偏移量的同时把最近计算的平均数写到一个结果主题上。消费者线程在重新启动之后,它就可以拿到最近的平均数并接着计算。不过这并不能完全地解决问题,因为kafka并没有提供事务支持。消费者有可能在写入平均数之后来不及提交偏移量就崩溃了,或者反过来也一样。建议使用KafkaStreams这个类库,它为聚合、连接、时间窗和其他复杂的分析提供了高级的DSL API

 

7.长时间处理

 

有时候处理数据需要很长时间:可能会从发生阻塞的外部系统获取信息,或者把数据写到外部系统,或者进行一个非常复杂的计算。要记住,暂停轮询的时间不能超过几秒钟。即使不想获取更多的数据,也要保持轮询,这样客户端才能往broker发送心跳。在这种情况下,一种常见的做法是使用一个线程池来处理数据,因为使用多个线程可以进行并行处理,从而加快处理速度。在把数据移交给线程池去处理之后,就可以暂停消费者,然后保持轮询,但不获取新数据,直到工作线程处理完成。在工作线程处理完成之后,可以让消费者继续获取新数据。因为消费者一直保持轮询,心跳会正常发送,就不会发生再均衡。

 

8.仅一次传递

 

有些应用程序不仅仅需要至少一次(at-least-once)语义(意味着没有数据丢失),还需要仅一次(exactly-once)语义。尽管kafka现在还不能完全支持仅一次语义,消费者还是有一些办法可以保证kafka里的每个消息只被写到外部系统一次(但不会处理kafka写入数据时可能出现的重复数据)。

 

实现仅一次处理最简单且最常用的办法是把结果写到一个支持唯一键的系统里,比如键值存储引擎、关系型数据库、elasticsearch或其它数据存储引擎。在这种情况下,要么消息本身包含一个唯一键(通常都是这样),要么使用主题、分区和偏移量的组合来创建唯一键--它们的组合可以唯一标识一个kafka记录。如果把消息和一个唯一键写入系统,然后碰巧又读到一个相同的消息,只要把原先的键值覆盖掉即可。数据存储引擎会覆盖已经存在的键值对,就像没有出现过重复数据一样。这个模式被叫做幂等性写入,它是一种很常见也很有用的模式。

 

posted on 2020-07-20 22:47  嘣嘣嚓  阅读(1051)  评论(0编辑  收藏  举报

导航