kafka笔记6
我们讨论可靠性时,一般使用保证这个词,它是确保系统在各种不同的环境下能够发生一致的行为。Kafka可以在哪些方面作出保证呢?
1.Kafka可以保证分区消息的顺序
2.只有消息被写入分区的所有同步副本时,它才会被认为是已提交的。生产者可以选择接收不同类型的确认。
3.只要还有一个副本是活跃的,那么提交的消息就不会丢失。
4.消费者只能读取已经提交的消息。
Kafka的管理员和开发者可以在配置参数上作出权衡,从而得到它们想要的可靠性,这种权衡一般是指消息存储的可靠性和一致性的重要程度与可用性,高吞吐量,低延迟和硬件成本的重要程度之间的权衡。
6.2复制
Kafka的复制机制和分区的多副本架构师Kafka可靠性保证的核心。把消息写入多个副本可使Kafka在发生崩溃时仍能保证消息的持久性。
Kafka的主题被分为多个分区,分区是基本的数据块,分区存储在单个磁盘上,Kafka可以保证分区里的事件是有序的,分区可以在线(可用),也可以离线(不可用)。每个分区可以有多个副本,其中一个副本是首领,所有的事件都直接发送给首领副本,或者从首领副本读取事件。其它副本只需要与首领保持同步,并及时复制最新的事件,当首领副本不可用时,其中一个副本会成为新的首领。
分区首领时同步副本,对于跟踪者副本来说,它需要满足以下条件才能被认为是同步的。
1.与zookeeper之间有一个活跃的会话,(过去6秒(可配置)向zookeeper发送过心跳)。
2.在过去10秒(可配置)从首领获取过消息。
3.过去10秒获取过最新的消息,光从首领获取消息是不够的,还必须是几乎零延迟的。
如果不能满足以上任何一点,那么就被认为是不同步的。
一个滞后的同步副本会导致生产者和消费者变慢,因为在消息被认为已提交之前,客户端会等待所有同步副本接收消息。
6.3broker配置
broker有三个配置参数会影响Kafka的消息存储的可靠性。与其它配置参数一样,它们可以应用在broker级别,用于控制所有主题的行为,也可以应用在主题级别,用于控制特定主题的行为。
在主题级别控制可靠性,意味着Kafka集群可以同时拥有可靠的主题和非可靠的主题。
6.3.1复制系数
主题级别的配置参数是replication.factor,而在broker级别可以通过default.repllication.factor来配置自动创建的主题。
我们假定主题的复制系数是3,也就是说每个分区总共会被3个不同的broker复制三次。即使在主题创建之后,也可以通过新增或移除副本来改变复制系数。
更高的复制系数会带来更好的可用性,可靠性和更少的故障。
副本的分布也很重要,默认情况下,Kafka会确保分区的每个副本都被放在不同的broker上,如果这些broker处在同一个机架上,一旦机架的交换机发生故障,分区就会不可用,所以我们建议把broker分布在多个不同的机架上。并使用broker.reck参数来为每个broker配置所在机架的名字。如果配置了机架名字,Kafka会保证分区的副本被分布在多个机架上,从而获得更高的可用性。
如果首领不可用时,其它副本都是不同步的,如果把unclean.leader.election.enable设为true,就是允许不同步的副本称为首领,那么我们将面临丢失消息的风险。如果把这个参数设为false,就要等待原先的首领重新上线,从而降低了可用性。银行系统一般会禁用这种不完全的首领选举(把这个参数设为false)。
6.3.3最少同步副本
min.insync.replicas
根据Kafka对可靠性保证的定义,消息只有在被写入到所有同步副本之后才会被认为是已提交的。如果两个副本都变为不可用,那么broker都会停止接受生产者的请求。尝试发送数据的生产者就会收到NoEnoughReplicasException异常。但消费者仍然可以继续读取已有的数据。
6.4在可靠的系统里使用生产者
每个使用Kafka的开发人员必须注意两件事:
1.根据可靠性需求配置恰当的acks值
2.在参数配置和代码里正确处理错误。
6.4.1发送确认
生产者可以选择以下三种确认模式。
acks=0意味着如果生产者通过网络把消息发送出去,就认为消息已经成功写入Kafka,这种情况下有可能发生错误。但是速度是最快的。
acks=1意味着首领在收到消息并把它写入到分区数据文件就会返回确认信息,这个情况下,如果发生正常的首领选举,生产者会收到异常,如果生产者能恰当处理异常,选择重发,最终消息仍然可以发送成功,但这个模式仍然可能发生丢失数据,比如消息成功写入首领,在复制到跟随者副本之前,首领发生崩溃。
acks=all,意味着首领在返回确认之前,会等待所有同步副本收到消息。如果和min.insync.replicas参数结合起来,就可以决定在返回确认前至少有多少副本能够收到消息。这是最保险的做法——生产者会一直重试直到消息被成功提交。不过生产者在继续发送其它消息之前需要等待所有的副本都收到当前消息。虽然可以通过使用异步模式和更大的批次加快速度,但这样通常会降低吞吐量。
6.4.2配置生产者的重试参数
生产者需要处理的错误包含两部分:一部分是生产者可以自动处理的错误,还有一部分是需要开发者手动处理的错误。
如果broker返回的错误可以通过重试来解决,那么生产者会自动处理这些错误。生产者向broker发送消息,broker可以返回一个成功响应码或者一个错误响应码。错误响应码可以分为两种:
一种是重试之后可以解决的,还有一种是无法通过重试解决的。
一般情况下,如果目标是不丢失任何消息,那么最好在生产者遇到可重试错误时能够保持重试。Kafka的跨数据中心复制工具默认会进行无限制的重试。作为一个高可用性的复制工具,它绝不会丢失消息。
要注意重试可能会导致消息重复,例如由于网络问题消息写入后生产者没有收到确认消息,生产者重试。这种情况下broker会收到两个相同的消息。
6.4.3额外的错误处理
使用生产者内置的重试机制可以在不造成消息丢失的情况下轻松处理大部分错误,不过对于开发人员来说,仍然需要处理其它类型的错误,包括:
不可重试的broker错误,例如消息大小错误,认证错误;
在消息发送之前的错误,如序列化错误;
在生产者达到重试次数上限时或在消息占用的内存达到上限时发生的错误。
如果错误处理只是为了重发,那么最好使用生产者内置的重试机制。
6.5在可靠的系统里使用消费者
只有那些被提交到Kafka的数据,也就是已经被写入到所有同步副本的数据,对消费者时可用的,这意味着消费者得到的消息已经具备了一致性。消费者唯一要做的事跟踪哪些消息是已经读取过的,哪些是还没有读取过的。这是在读取消息时不丢失消息的关键。
从分区读取数据时,消费者会获取一批事件,检查其中最大的偏移量,然后从这个偏移量开始读取另外一批事件,这样可以保证消费者总能以正确的顺序获取新数据,不会错过任何事件。
如果消费者提交了偏移量却未能处理完消息,那么就可能造成消息丢失,这也是消费者丢失消息的主要原因,因此我们会非常重视偏移量的提交时间点和方法。
已提交偏移量是指消费者发送给Kafka的偏移量,用于确认它已经收到并处理好的消息位置。
6.5.1消费者的可靠性配置
为了保证消费者的可靠性,有4个非常重要的配置参数。
如果两个消费者具有相同的group.id,并且订阅了同一个主题,那么每个消费者会分到主题分区的一个子集,如果希望消费者可以看到主题的所有消息,那么需要为它们设置唯一的group.id
2.auto.offset.reset
这个参数指定了没有偏移量可提交时,消费者做什么?有两种配置:earliest消费者从分区的开始位置读取数据,latest消费者从分区的末尾读取数据,这样可以减少重复处理消息,但可能错过一些消息。
3.enable.auto.commit
可以让消费者自动提交偏移量,也可以在代码里手动提交偏移量。自动提交的好处是实现消费者逻辑时可以少考虑一些问题。缺点是无法控制重复处理消息,如果把消息交给另外一个后台线程去处理,自动提交机制可能在消息还没有处理完毕就提交偏移量。
4.auto.commit.interval.ms
与第三个参数有直接联系,如果选择了自动提交,可以通过该参数配置自动提交的频度,默认是5秒提交一次。
6.5.2显式提交偏移量
1.总是在处理完事件后再提交偏移量
如果所有处理在轮询里完成,并且不需要在轮询之间维护状态,那么可以使用自动提交,或者轮询结束后进行自动提交。
2.提交频率是性能和重复消息数量之间的权衡
可以在一个循环多次提交偏移量,或者多个循环只提交一次,这取决于你在性能和重复之间的权衡
3.确保对提交的偏移量心里有数
处理完消息后再提交偏移量是非常关键的——否则会导致消费者错过消息
4.再均衡
一般要在分区被撤销之前提交偏移量,并在分配到新分区时清理之前的状态。
5.消费者可能需要重试
假设要把Kafka数据写到数据库,不过那时数据库不可用,想稍后重试,要注意,你提交的是偏移量,不是确认,可以采用两种办法解决:
一是提交最后一个处理成功的偏移量,然后把还没有处理的消息保存到缓冲区,调用消费者的pause方法来确保其它轮询不会返回数据,在保持轮询的同时尝试重新处理,如果重试成功,或者重试次数到达上限并决定放弃,那么把错误消息记录下来并丢弃消息,然后调用resume方法让消费者从轮询继续获取新数据。
二是遇到可重试错误时,把错误写入一个独立主题,然后继续。一个独立的消费者群组复制从该主题读取错误消息,并进行重试,或者使用其中一个消费者同时从该主题读取错误并进行重试,不过重试时需要暂停该主题。这个模式有点像dead-letter-queue.
6.消费者可能需要维护状态
如果需要在多个轮询之间维护状态,有一个办法是提交偏移量同时把最近计算的结果写到一个主题上,消费者线程重启之后,它就可以拿到最近的平均数接着计算,这个问题很复杂,建议使用KafkaStream。
7.长时间处理
如果处理数据需要很长时间,暂停轮询不能超过几秒,即使不想获取更多数据,也要保持轮询,这样客户端才能向broker发送心跳。这种情况下,一种常见的做法是使用一个线程池来处理数据,因为使用多个线程可以并行处理,从而加快速度。把数据交给线程池处理后,就可以暂停消费者然后保持轮询,但不获取新数据,直到工作线程处理完成。继续获取新数据,因为消费者保持轮询,心跳就会正常,就不会发生在均衡。
8.仅一次传递
实现仅一次处理最简单最常用的办法是把结果写到一个支持唯一键的系统里,比如键值存储引擎,关系数据库,elasticSearch或者其他数据存储引擎。这种情况下,要么消息本身包含一个唯一键(通常是这样),要么使用主题,分区和偏移量的组合创建一个唯一键——它们的组合可以唯一标识一个Kafka记录。如果出现重复记录,只需要覆盖原来的消息即可,就像没有出现重复数据一样。这个模式叫做幂等姓写入,它是一种很常见也很有用的模式。
如果写入消息的系统支持事务,最简单的使用关系数据库,hdfs有一些被重新定义过的原子操作也经常用来达到相同目的。我们把消息和偏移量放在同一个事物里,这样它们就能保持同步。消费者重启时,就会获取最近被处理过的消息的偏移量。然后调用seek方法从该偏移量继续读取数据。
6.6验证系统的可靠性
建议做三个层面的验证——配置验证,应用程序验证和生产环境的应用程序监控。
6.6.1验证配置
从应用程序可以很容易对broker和客户端配置进行验证,有以下两方面原因:
1.验证配置是否满足你的需求
2.帮助理解系统的行为,了解对Kafka基本准则的理解是否存在偏差,然后改进,理解这些准则是如何被应用到各种场景中的。
Kafka提供了两个重要工具验证配置:org.apache.kafka.tools包里的VerifiableProducer和VerifiableConsumer这两个类。我们可以从命令行允许这两个类,或者把它们嵌入到自动化测试框架。
VerifiableProducer生成一系列消息,这些消息包含从1到你指定的某个数字,你可以使用与生产者相同的方式配置VerifiableProducer,在运行VerifiableProducer时,它会把每个消息是否成功发送到broker的结果打印出来。
VerifiableConsumer执行另一种检查,它读取事件,并按照顺序打印这些事件。他也会打印已提交的偏移量和再均衡的相关信息。
可以考虑以下测试:
1.首领选举,生产者和消费者恢复正常需要多久?
2.控制器选举,重启控制器系统需要多久恢复?
3.依次重启,可以重启broker而不丢失数据吗?
4.不完全首领选举测试,如果依次停止所有副本,然后启动一个不同步的broker会发生什么?要怎么恢复正常?这样做可以接受吗?
Kafka代码库包含了大量测试用例,它们使用VerifiableProducer和VerifiableConsumer来确保迭代的版本能够正常工作。
6.6.2应用程序验证
应用程序的验证包含检查自定义的错误处理代码,偏移量提交方式,再均衡监听器和其它使用了Kafka客户端的地方。
建议做如下测试:
1.客户端从服务器断开连接
2.首领选举
3.依次重启broker
4.依次重启消费者
5.依次重启生产者
看测试结果是否符合预期。
6.6.3在生产环境监控可靠性
首先Kafka的Java客户端包含了JMX度量指标,这些指标可以用于监控客户端的状态和事件。对于生产者来说最重要的指标时消息的error-rate和retry-rate,如果两个指标上升,说明系统出现了问题。
除此之外,还要监控生产者日志——发送消息的错误日志被设为WARN级别。
对于消费者来说最重要的指标时consumer-lag,这个指标表明了消费者的处理速度与最近提交到分区里的偏移量之间还有多少差距。理想情况下,该指标总是0,消费者总能读到最新的消息。不过实际中,poll方法会返回很多消息,因此该指标会有波动,关键是确保消费者最终会赶上去,而不是越落越远。
监控数据流是为了确保所有生成的数据会被及时读取,为了确保数据能够及时读取,需要知道数据是什么时候生成的。0.10.0版本的Kafka在消息里增加了时间戳,表明了消息的生成时间。如果使用的是更早的客户端,建议在消息里加入时间戳,应用程序名字和机器名,这样有助于诊断问题。
为了确保所有消息能够在合理时间被读取,应用程序需要记录生成消息的数量,而消费者需要记录已经读取消息的数量以及消息生成时间到当前时间的时间差
然后需要工具来比较生产者和消费者记录的消息数量(为了确保没有丢失消息),确保这两者之间的时间差不会超出我们允许的范围。