4.7 复制
Kafka在可配置数量的服务器上复制每个主题分区的日志(您可以逐个主题地设置此复制因子)。这允许在群集中的服务器发生故障时自动故障转移到这些副本,以便在出现故障时消息仍然可用。
其他消息传递系统提供了一些与复制相关的功能,但是在我们的看法中,这似乎是一个tacked-on的东西,没有大量使用,并且有很大的缺点:副本处于非活动状态,吞吐量受到严重影响,需要手动配置等。Kafka默认情况下用于复制 - 事实上,我们将不复制的主题实现为复制因子为1的复制主题。
复制单元是主题分区。在非故障情况下,Kafka中的每个分区都有一个leader和零个或多个followers。包括leader在内的副本总数构成复制因子。所有读写都将转到分区的leader。通常,有比broker数多得多的分区,leader在broker之间平均分配。follower的日志与leader的日志相同 - 所有日期都具有相同的偏移量和相同顺序的消息(当然,在任何给定时间,leader可能在其日志末尾有一些尚未复制的消息)。
follower像正常的Kafka消费者一样消费来自leader的消息并将其应用于他们自己的日志中。让follower从leader中pull出来具有很好的特性,允许follower将它们正在应用的日志自然的batch在一起。
与大多数分布式系统一样,自动处理故障需要准确定义节点“活着”的含义。对于Kafka节点,活跃度有两个条件:
- 节点必须能够使用ZooKeeper维护其会话(通过ZooKeeper的心跳机制)
- 如果它是一个follower,它必须复制发生在leader上的写操作,而不是落后于“太远”
我们将满足这两个条件的节点称为“同步中”以避免“活着”或“失败”的模糊性。leader跟踪“同步中”节点的集合。如果follower死亡,卡住或落后,领导者将从同步副本列表中删除它。卡住和滞后复制品的确定由replica.lag.time.max.ms配置控制。
在分布式系统术语中,我们只尝试处理故障的“故障/恢复”模型,其中节点突然停止工作,然后恢复(可能不知道它们已经死亡)。 Kafka没有处理产生任意或恶意的响应(可能是由于错误或foul play)节点中所谓的“ Byzantine”故障。
现在,我们可以更精确地定义,当该分区的所有同步副本将消息应用于其日志时,将消息视为已提交。只有已提交的消息才会发给消费者。这意味着消费者不必担心如果leader失败可能会丢失可能丢失的消息。另一方面,生产者可以选择是否等待消息的发送,这取决于他们在延迟和持久性之间进行的权衡。请注意,主题具有同步副本“最小数量”的设置,当生产者请求确认已将消息写入完整的同步副本集时,将检查该副本。如果生产者请求不那么严格的确认,则即使同步副本的数量低于最小值(例如,它可以仅低于领导者),也可以提交和消费该消息。
Kafka提供的保证是,只要始终存在至少一个同步副本,就不会丢失已提交的消息。
在短暂的故障转移期后,Kafka将在出现节点故障时仍然可用,但在网络分区存在时可能无法保持可用状态。
Replicated Logs: Quorums, ISRs, and State Machines (Oh my!)
Kafka分区的核心是复制日志。复制日志是分布式数据系统中最基本的原语之一,有许多实现方法。其他系统可以使用复制日志作为基元,以实现状态机样式中的分布式系统。复制日志模拟了一系列有序进入消费者进行处理的过程。有很多方法可以实现这一点,但最简单和最快的方法是让领导者选择提供给它的值的顺序。只要leader还活着,所有follower都需要只复制值并跟随leader的选择。
当然,如果leader没有失败,我们就不需要follower了!当leader死亡时,我们需要从follower中选择一位新的leader。但是follower本身可能落后或崩溃所以我们必须确保我们选择一个最新的follower。日志复制算法必须提供的基本保证是,如果我们告诉客户端消息已提交,并且leader失败,我们选择的新leader也必须拥有该消息。这产生了一个权衡:如果leader在宣布提交之前等待更多的follower提交消息,那么将会有更多潜在的可选leader。
如果您选择所需的确认数量和必须比较的日志数量以选择leader以确保重叠,那么这称为仲裁(Quorum)。
这种权衡的一种常见的方法是用一次多数投票来决定提交决定和leader选举。这不是Kafka的办法,但不管怎样,我们需要探索它来理解权衡(tradeoff)。假设我们有2f+1个副本。如果f+1个副本必须在leader声明提交之前收到消息,并且如果我我们通过至少f+1个副本中选择具有最完整日志的follower来选择新的leader,那么,不超过f次失败,leader保证拥有所有已提交的消息。这是因为在任何f+1个副本中,必须至少有一个包含所有已提交消息的副本。该副本的日志将是最完整的,因此将被选择为leader。每个算法都还有很多需要处理的细节(例如精确定义如何使日志更完整,确保leader失败期间的日志一致性或更改副本集中的服务器集),但是我们暂时忽略这些。
这种多数投票的方法具有很好的属性:延迟仅仅取决于最快的服务器,如果复制因子是3,则延迟由较快的follower而不是较慢的。
该family中有各种各样的算法,包括Zookeeper的Zab,Raft和Viewstamped Replication。我们了解到Kafka实际实现的最相似的出版物是Microsoft的PacificA。
多数投票的缺点是,无可选leader的时候容纳不了很多失败。容纳一个故障需要3个副本,容纳两个故障需要5个副本。根据我们的经验,对于实际系统来说,仅以足够的冗余来容纳单个失败是不够的,但是每次做5遍写操作,磁盘空间的要求是5倍,吞吐量是5分之一,对于大容量数据问题来说不是很实用。这可能是为什么仲裁算法更常见于共享集群配置(如Zookeeper),但是主数据存储不常见的愿意。例如,在HDFS中,NameNode高可用性建立在基于多数投票的journal上,但是这种更昂贵的方法并未用于数据本身。
Kafka采用了一种稍微不同的方法来确定其quorum集。Kafka不是多数投票,而是动态维护一组同步副本(in-sync replicas ISR),这些副本会被领导者caught-up。只有这一组的成员才有资格当选为领导者。在所有同步副本都收到写入之前,不会将对Kafka分区的写入视为已提交。无论何时更改,此ISR集都会持久保存到ZooKeeper。因此,ISR中的任何副本都有资格当选为leader。对于Kafka的使用模式而言,这是一个重要因素,因为有许多分区并确保leader平衡很重要。使用此ISR模型和f + 1个副本,Kafka主题可以容忍f次失败而不会丢失已提交的消息。
对于我们希望处理的大多数情况,这种权衡是合理的。在实践中,为了容忍f次失败,多数投票和ISR方法将在提交消息之前等待相同数量的副本确认。(例如,为了在一次失败中存活,majority仲裁需要三次入职和一次确认并且ISR方法需要两次请求和一次确认)。在没有最慢服务器的情况下的提交能力是多数投票方法的优势。但是,我们认为通过允许客户端选择是否阻止提交消息来改善它,并且由于所需的复制因子较低而导致额外的吞吐量和磁盘空间是值得的。
另一个重要的设计区别是Kafka不要求崩溃的节点在其所有数据完整的情况下恢复。这种space中的复制算法依赖于“稳定存储”的存在并不罕见,“稳定存储”在任何没有潜在一致性违规的故障恢复方案不会丢失。这个假设有两个主要问题: 首先,磁盘错误是我们在持久数据系统的实际操作中遇到的最常见问题,并且它们通常不会保持数据完整。其次,即使这不是问题,我们也不希望在每次写入时都要求使用fsync来保证一致性,因为这会将性能降低两到三个数量级。Our protocol for allowing a replica to rejoin the ISR ensures that before rejoining, 它必须完全重新同步,即使它在崩溃中丢失了未刷新的数据。
Unclean leader election: What if they all die?
注意Kafka对数据丢失的保证是基于至少有一个保持同步的副本。如果复制分区的所有节点都挂了,则此保证不再成立。然而,当所有如本都挂了,实际的系统需要做一些合理的事情。如果你不幸发生这种情况,重要的是要考虑将会发生什么。有两种行为可以实施:
- 等待ISR中的副本恢复并选择此副本作为领导者(希望它仍然拥有其所有数据)。
- 选择作为领导者的第一个副本(不一定在ISR中)。
这是可用性和一致性之间的简单权衡。如果我们等待ISR中的副本,那么只要这些副本关闭,我们将保持不可用状态。如果这些复制品被销毁或其数据丢失,那么我们就永久地挂了。另一方面,如果非同步副本恢复并且我们允许它成为领导者,那么即使不保证每个已提交的消息,它的日志也会成为真实的来源。默认情况下,从版本0.11.0.0开始,Kafka选择第一个策略并支持等待一致的副本。可以使用配置属性unclean.leader.election.enable更改此行为,以支持正常运行时间优于一致性的用例。
这种困境并非特定于Kafka。它存在于任何quorum-based的方案中。例如,在多数投票方案中,如果大多数服务器遭受永久性故障,那么您必须选择丢失100%的数据或违反一致性,将现有服务器上剩余的内容作为新的事实来源。
Availability and Durability Guarantees
在写入Kafka时,生产者可以选择是否等待消息被0,1或所有(-1)个副本确认。请注意,“所有副本的确认”并不保证已分配的完整副本集已收到该消息。默认情况下,当acks = all时,只要所有当前的同步内副本都收到消息,就会发生确认。例如,如果主题仅配置了两个副本而一个失败(即,同步副本中只有一个仍然存在),则指定acks = all的写入将成功。但是,如果剩余的副本也失败,则这些写入可能会丢失。虽然这确保了分区的最大可用性,但对于一些喜欢耐用性而非可用性的用户来说,这种行为可能不符合需要。因此,我们提供了两种主题级配置,可用于优先考虑消息的持久性和可用性:
-
禁用unclean的leader选举:如果所有副本都不可用,则分区将保持不可用,直到最近的领导者再次可用。这种选择倾向于不可用性而不是消息丢失的风险。请参阅上一节关于unclean leader选举的部分以擦考更多细节。
-
指定最小ISR大小 - 如果ISR的大小超过某个最小值,则分区将仅接受写入,以防止丢失仅写入单个副本的消息,该副本随后变为不可用。此设置仅在生产者使用acks = all时生效,并保证消息会被至少这些in-sync 副本确认。此设置提供了一致性和可用性之间的权衡。最小ISR大小的更高设置保证了更好的一致性,因为保证将消息写入更多副本,这降低了它将丢失的可能性。 但是,它会降低可用性,因为如果同步副本的数量低于最小阈值,则分区将无法进行写入。
Replica Management
上面关于复制日志的讨论实际上只涵盖了一个日志,即一个主题分区。但是,Kafka集群将管理数百或数千个这样的分区。我们尝试以循环方式平衡群集中的分区,以避免聚集所有高容量主题的分区。同样,我们尝试平衡leadership,以便each node is the leader for a proportional share of its partitions.
优化leader选举过程也很重要,因为这是不可用的关键窗口。leader选举的naive实现将最终导致在节点失效时,节点的托管的所有分区都进行一次选举。相反,我们选择其中一个broker作为“controller”。此控制器检测broker级别的故障,并负责更改故障broker中所有受影响分区的负责人。 结果是,我们能够将许多所需的领导变更通知批处理在一起,这使得选举过程对于大量分区来说更cheap,更快。 如果控制器发生故障,其中一个幸存的broker将成为新的控制器。
网友评论