如何在Kafka中将严格顺序与大规模并行性结合? - Emil


参与了多个针对各个行业的不同客户的大型Kafka项目之后,遭遇一个似乎永远不会过时的问题是:如何保持严格的顺序,同时仍然并行处理记录?
这是一个公平的问题。严格的顺序是等于串行化,其概念似乎与并行性的目标相矛盾。
 
部分顺序和总顺序
我们将首先探讨顺序的概念。
如事件流平台所期望的那样,Kafka保留已发布记录的顺序,前提是这些记录占用相同的分区。为了理解这在实践中的含义,需要探索Kafka主题的体系结构以及底层的分片机制-分区。
一个分区是一个完全有序序列的记录,而且是卡夫卡的根本。一条记录具有一个ID —一个64位的整数偏移量和一个毫秒级的时间戳。另外,它可能有一个键和一个值;两者都是字节数组,并且都是可选的。术语“完全排序”仅表示对于任何给定的生产者,记录将按照应用程序发出的顺序进行写入。
如果记录P在Q之前发布,则P将在分区中的Q之前。(假设P和Q共享一个分区。)此外,所有使用者将以相同的顺序读取它们;对于每个可能的使用者,将始终在Q之前读取P。在大多数用例中,这种顺序保证至关重要。已发布的记录通常对应于某些现实事件,因此保留这些事件的时间表通常很重要。
一个主题Topic是分区的逻辑集合。一个主题可以具有一个或多个分区,并且一个分区必须恰好是一个主题的一部分。由于主题内的分区是相互独立的,因此称该主题展现出部分顺序。
简单来说,这意味着某些记录可以相对于彼此排序,而相对于某些其他记录则不排序。总顺序和部分顺序的概念虽然听起来有些学术性,但在构建性能事件流管道中非常重要。它使我们能够处理记录并行。
  
生产者角色
了解了主题和分区的概念之后,您可能想知道记录如何映射到基础流。
生产者在发布记录时指定分区,假设您要发布到具有多个分区的主题。(可能只有一个分区主题,在这种情况下这不是问题。)可以直接(通过指定分区索引)来实现,也可以间接地通过确定性地哈希为一致的记录键,(即每次相同)分区索引来实现。  
生产者可以在发布记录时显式分配分区索引,尽管这种方法很少使用。一种更常见的方法是将密钥分配给记录。密钥对Kafka完全不透明-换句话说,Kafka不会尝试解释密钥的内容,而是将其视为字节数组。使用一致的哈希技术对这些字节进行哈希处理以得出分区索引。
共享相同哈希的记录可以保证占据相同的分区。。假设一个主题具有多个分区,则具有不同键的记录可能最终会位于不同的分区中。但是,由于哈希冲突,具有不同哈希值的记录也可能最终会在同一分区中。这就是哈希的本质。
生产者很少在乎记录将映射到哪个特定分区,只有相关记录最终在同一分区中并且保留其顺序。同样,使用者对分配的分区也无动于衷,只要它们以与发布时相同的顺序接收记录,并且其分区分配不会与组中的其他使用者重叠。
  
消费组
记录的实际处理是由消费者(在(可选)消费者组内)进行的。后者充当负载平衡机制-在组内的各个使用者实例之间大致均匀地分配分区分配。Kafka保证一个分区最多只能分配给其消费者组中的一个消费者。(我们说“至多”是为了涵盖所有使用者都处于脱机状态的情况。)当组中的第一个使用者订阅该主题时,它将收到该主题中的所有分区。当第二个使用者随后加入时,它将获得大约一半的分区,从而使第一个使用者减轻了先前负载的一半。这使您能够并行处理事件流,并根据需要添加使用者(理想情况下,使用自动缩放机制),前提是您已对事件流进行了充分的分区。
使用开源Kafdrop工具拍摄:显示了一个具有16个分区的真实主题。“消费者偏移”列显示一个特定消费者组在每个分区内的偏移,它完全独立于可能订阅同一主题的其他消费者组的偏移。

为了提高消费者的吞吐量,必须考虑两个单独的因素:

  1. 主题分区方案。应该对主题进行分区,以使独立事件子流的数量最大化。换句话说,仅在绝对必要的情况下才应保留记录顺序。如果任何两个记录在因果关系上均不合法相关,则不应将它们绑定到同一分区。这意味着要使用不同的键,因为Kafka将使用记录的键作为哈希源来导出其一致的分区映射。
  2. 组中的消费者数量。您可以增加使用者数量以匹配入站记录的负载,最多可以达到主题中的分区数量。(如果愿意,您可以有更多的使用者,但是分区数将为至少获得一个分区分配的活动使用者的数量设置上限;其余使用者将保持空闲状态。)如果您有运行管道的需要,在云环境中,理想情况下,应启用实例自动扩展功能,以根据需要增加用户数量。请注意,使用者可以是进程或线程。根据使用者执行的工作负载类型,您可以使用多个单独的使用者线程,或处理线程池中的记录。

 
结论
我们已经证明,可以在同时中合理地使用顺序和并行性,而不会破坏物理定律。诀窍在于“顺序”的定义,特别是总顺序和部分顺序之间的区别。一个人不能在不擦除顺序的情况下并行处理全部排序的记录。但是,当部分排序的流分解为几个不相关的完全排序的子流时,我们可以轻松地并行处理后者。
使用Kafka的基本构建块(主题,分区和使用者组),您可以构建高性能的事件流应用程序。