当前位置: 首页 > 科技观察

从基础谈Kafka事务流到实践

时间:2023-03-13 16:20:28 科技观察

事件源、最终一致性、微服务、CQRS等,这些越来越多的概念现代开发者耳熟能详。从细粒度的服务组装到复杂的以业务为中心的应用架构,其中最重要的部分是基于中间件的业务解耦。在本文中,我们介绍了中间件的基本构建块-事务流。它的领导者是事实上的交易流平台标准ApacheKafka,同时还将介绍Kafka的Web界面工具Kafdrop。概述事务流平台属于更广泛的面向消息中间件(MoM)类别,它类似于传统的消息队列和主题,但由于日志结构的不可变性,它提供了更强的时序保证和实质性的性能改进。简而言之,事务流更高效,因为写入操作仅限于顺序追加。传统消息队列(MQ)中的消息倾向于任意排序并且通常彼此独立,而流中的事务(或记录)倾向于按时间顺序或因果顺序排序。此外,事务流保留它们的记录,而MQ在读取消息后丢弃消息。所以事务流往往更适合事件驱动的架构,包括事件源、最终一致性、CQRS等(当然也包括FIFO消息队列,但是FIFO队列和成熟的事务流平台区别很大大,不仅限于订购)。事务流平台是MoM领域中一个相对较新的范例。与成百上千的MQ风格的消息代理相比,只有少数主流的可用。与AMQP、MQTT、XMPP和JMS等既定标准相比,事务流空间中没有等效标准。交易流媒体平台目前是一个正在进行的研究和实验的活跃领域。然而,交易流媒体平台不仅仅是一个商业产品,还是一个复杂的学术问题。可广泛应用于消息和事务场景,可用于常规替代消息队列的传统使用场景。架构概述下图提供了Kafka组件架构的简要概述。限于篇幅,这里不详细介绍Kafka的内部工作原理。kafka的组成Kafka是一个分布式系统,包括以下几个关键组件:Broker(代理)节点:负责集群内批量I/O操作和持续持久化。代理附加包含集群托管的主题分区的日志文件。分区可以跨多个代理复制,以实现水平可伸缩性和增加的持久性,这些复制的分区称为副本。有一个代理节点作为控制节点(controller),其他副本由它管理(followers)。一个代理节点被选为集群控制器,负责分区状态的内部管理,同时也负责仲裁给定分区的领导者-跟随者角色。ZooKeeper节点:在幕后,Kafka需要一种方法来管理集群中的整体控制器状态。如果控制器由于某种原因退出,则有一个协议可以从剩余的代理集中选出另一个控制器。ZooKeeper在很大程度上实现了控制器选举、心跳等实际机制。ZooKeeper还充当各种配置的存储库,维护集群元数据、领导者和追随者状态、配额、用户信息、ACL和其他内部管理项目。由于底层的选举和共识协议,ZooKeeper节点的数量必须是奇数。生产者:负责向Kafka主题发布消息的客户端应用程序。由于Kafka的日志结构特性及其在多消费者生态系统中共享主题的能力,只有生产者可以修改底层日志文件中的数据。实际I/O由代理节点代表生产者客户端执行。可以向同一个Kafka主题发布任意数量的生产者消息,并且可以选择用于存储记录的分区。消费者:从主题读取消息的客户端应用程序。任意数量的消费者都可以阅读同一主题的内容;然而,基于消费者的配置和分组,存在管理记录在消费者之间分配的规则。Partitions、Records、Offsets和TopicsPartitions是完全有序的记录序列,每个partition对应一个appendlog,这是Kafka的基础。每条记录都有一个ID:64位整数偏移量和毫秒级时间戳。它可能存在一个键和一个值。两者都是字节数组,都是可选的。术语“完全有序”仅表示对于任何给定的生产者,记录将按照应用程序发出的顺序写入。如果记录P在Q之前发布,则P在分区中将在Q之前。(假设P和Q共享一个分区。)此外,所有消费者将以相同的顺序读取它们。对于每个可能的消费者,P总是在Q之前被读取。在大多数用例中,这种顺序保证是至关重要的。通常,已发布的记录将对应于一些真实世界的交易,并且保留这??些交易的时间表通常是必不可少的。记录偏移量是分区中记录的唯一标识符。偏移量是稀疏地址空间中严格单调递增的整数,每个记录偏移量总是高于前一个记录偏移量,并且相邻偏移量之间可能存在可变间隙。如果启用了压缩或者作为事务的结果,必然会有间隙,所以也有可能偏移量不连续。应用程序不应该试图从字面上解释偏移量,也不应该猜测下一个偏移量是什么。然而,任何记录的相对顺序都可以从偏移量中推断出来,根据它们的偏移量对记录进行排序。下图显示了内部分区的结构:第一个偏移量(也称为低水位线)是要显示给消费者的第一条消息。由于Kafka的保留期限制,不一定是最先发布的消息。可以根据时间和/或分区大小修剪记录。发生这种情况时,低水位线似乎向后移动,比低水位线早的记录将被截断。主题是分区的逻辑分组。一个主题可以有一个或多个分区,一个分区只能有一个主题或主题的一部分。主题是Kafka的基础,允许并行和负载平衡。我们之前说过分区显示总顺序。因为一个主题中的分区是相互独立的,所以这个主题被称为具有偏序。简而言之,这意味着一些记录相对于彼此是可排序的,但相对于其他一些记录是不可排序的。全序和偏序的概念虽然听起来很学术,但对于构??建性能事务流管道非常重要。它允许我们在可能的情况下并行处理记录,同时在必须的地方保持秩序。稍后,我们将探讨记录顺序、消费者并行度和主题大小的概念。例:新闻出版实践是检验真理的唯一标准。我将理论付诸实践,并通过示例说明概念。我们将启动一对Docker容器,一个用于Kafka,一个用于Kafdrop。我们使用DockerCompose方式启用容器。在所选目录中创建一个docker-compose.yaml文件,内容如下:为方便起见,我们使用obsidiandynamics/kafka镜像,它将Kafka和ZooKeeper整齐地打包在一个镜像中。然后用docker-composeup启动容器。启动成功后,在浏览器中访问localhost:9000即可看到Kafdrop登录界面。该实例是一个单代理集群,还没有??任何主题。我们可以使用Kafka的命令行工具创建一个主题并发布一些消息。我们可以使用dockerexec工具来操作kafka容器,方便调用内置的CLI工具:dockerexec-itkafka-kafdrop_kafka_1bash上面的命令会让我们进入容器的shell命令行界面。该工具位于/opt/kafka/bin目录下,cd进入该目录:创建一个名为streams-intro的topic,包含3个分区:切换回Kafdrop界面,现在我们可以看到新创建的列表主题中的新主人。接下来,我们可以使用kafka-console-producer工具发布消息:注意:kafka-topics使用--bootstrap-server参数配置Kafkabroker列表,而kafka-console-producer使用--broker-list。记录由换行符分隔。键和值部分由冒号分隔,如key.separator属性所示。在本例中,我们可以输入以下内容:完成后,按CTRL+D完成消息发布。然后切换回Kafdrop并单击streams-intro主题。您将看到该主题的概述和底层分区的详细分类:我们创建了一个包含三个分区的主题。然后,我们发布了5条记录,其中包含两个唯一键foo和bar。Kafka使用键将记录映射到分区,这样具有相同键的所有记录将始终出现在同一分区上。方便且重要的是,它允许出版商指定记录的确切顺序。稍后,我们将更详细地讨论密钥散列和分区分配。查看分区表,分区#0的第一个和最后一个偏移量分别为0和2。分区#2的值为零和3,而分区#1显示为空白。在KafdropwebUI中点击#0将带您进入主题查看器:您可以在bar键下看到发布的两条记录。请注意,它们与foo记录完全无关。消费者和消费者群体我们在上面已经谈到了例子。监听生产者发布消息后,将记录发送到流中。这些记录被组织成组织良好的分区。Kafka的发布-订阅拓扑遵循灵活的多对多模型,因此任意数量的生产者和消费者都可以同时与流进行交互。根据实际的解决方案,流拓扑也可以是一对多、多对一。让我们谈谈如何消费这些记录。消费者是通过客户端库连接到Kafka集群的进程或线程。消费者通常(但不一定)是整个消费者组的成员。该组由group.id属性指定。consumergroup其实是kafka中的一种负载均衡机制,负责在group内的consumer实例之间大致平均分配partition。当组中的第一个消费者订阅主题时,它将收到主题中的所有分区。当第二个消费者稍后加入时,它会得到大约一半的分区,减轻第一个消费者的负载。当一个消费者离开(通过断开连接或超时)时,这个过程被逆转并且更多的分区被提供给剩余的消费者。因此,消费主题中记录的消费者从Kafka和它所属的其他消费者分配的分区中获取份额。就负载平衡而言,这应该非常简单。但是,这里有一个关键点,使用记录的行为并没有删除它。这乍一看似乎自相矛盾,尤其是当消费行为与消费相关联时。(如果有消费者,则应称为“读者”。)一个简单的事实是,消费者对主题及其分区绝对没有影响。主题是仅附加的,记录只能由生产者或卡夫卡本身附加到(作为压缩或清除的一部分)。消费者只读操作是“便宜的”,因此允许许多人跟踪日志而不会给集群带来负担。这是事务流和传统消息队列之间的又一个区别,也是至关重要的。消费者在内部维护一个指向分区中下一条记录的偏移量,并在每次连续读取时增加偏移量。当消费者第一次订阅一个主题时,可以选择从主题的头端或尾端开始订阅。可以通过将auto.offset.reset属性设置为最新、最早或无来控制此行为。在后一种情况下,如果消费者组不存在先前的偏移量,则会触发异常。消费者在本地保存他们的偏移状态向量。由于不同消费群体中的消费者互不干扰,可能会有很多人同时阅读同一个话题。消费者以自己的偏移量读取消息;缓慢或积压的消费者对其组中的其他消费者没有影响。为了说明这个概念,我们考虑一个有两个分区的主题作为一个场景。两个消费者组-A和B-订阅该主题。每个组有三个实例,消费者分别命名为A1、A2、A3、B1、B2和B3。下图说明了两组如何共享主题,以及消费者如何独立浏览记录。仔细看看上面的图片,您会发现缺少了一些东西。消费者A3和B1不在上图中。这是因为Kafka保证一个分区只能分配给其消费者组中的一个消费者。由于每个组中有三个消费者,但只有两个分区,因此一个消费者将保持空闲状态,等待其组中的另一个消费者离开。这样,消费者组不仅是一种负载均衡机制,而且是一种类似栅栏的独占性控制,用于在不牺牲安全性的情况下构建高性能管道,特别是在记录只需要一个线程处理或同时处理的情况下任何给定时间。消费者组也用于确保可用性。通过定期从主题中提取记录,消费者可以隐式地向集群报告集群“健康”,从而延长其分区分配的租约。但是,如果某个消费者未能在允许的时间内再次读取,则认为它有缺陷,其分区将重新分配给组中剩余的“健康”消费者。截止日期由消费者客户端上的max.poll.interval.ms属性控制,默认设置为五分钟。使用交通系统的类比,主题就像高速公路,而区域就是车道。一条记录相当于一辆汽车,其乘客对应于记录值。几辆车可以在同一条高速公路上安全行驶,只要它们保持车道。共享同一路线的汽车按顺序行驶,形成队列。现在,想象每条车道都通向一个匝道,将其交通转移到某个地方。如果一个坡道建立起来,其他坡道可能仍能顺利流动。Kafka利用这种机制来保证端到端的吞吐量,轻松实现每秒百万条记录的QPS。创建主题时,可以选择分区数、频道数。分区在一个消费者组中的消费者之间大致平均分配,确保不会同时将分区分配给两个(或更多)消费者。注意:创建后,可以通过增加分区数来调整主题的大小。但是,如果不重新创建主题,就不可能减少分区数。记录对应于事件、消息、命令或任何其他可流式传输的内容。记录的精确划分取决于制作者。生产者可以在发布记录时显式分配分区索引,尽管这种方式很少被使用。一种更常见的方法是将键分配给记录,就像我们在前面的示例中所做的那样。key对Kafka来说是完全不透明的,换句话说,Kafka不解释key的内容,而是把它当作一个字节数组。这些字节使用一致的散列技术进行散列以导出分区索引。共享相同散列的记录保证占据相同的分区。假设一个主题有多个分区,具有不同键的记录可能会在不同的分区中结束。但是,具有不同哈希值的记录也可能由于哈希键冲突而最终位于同一分区中。生产者不需要关心记录将映射到哪个特定分区,只要相关记录最终位于同一分区中并且它们的顺序被保留即可。同样,消费者对不需要关心他们被分配到哪个分区,只要他们接收记录的顺序与发布顺序相同,并且他们的分区分配不与组中的其他消费者重复。示例:交易平台假设我们正在寻找上市股票的特定价格模式,并在确定特定模式后发送交易信号。存货很多,想并行处理无可厚非。但是,任何给定股票代码的时间序列必须在单个消费者上按顺序处理。Kafka使这个用例和其他类似的用例几乎不可能实现。我们将创建两个主题:价格,用于存储原始价格数据。订单主题,用于保存由其生成的任何订单。我们可以划分更多的分区,使我们能够完全并行化操作。我们可以在价格主题上为每个价格发布一条记录,以股票代码为键。Kafka的自动分区分配将确保每个ticker由其组中的一个消费者处理。消费者实例可以自由扩展和缩放以匹配处理负载。消费者组的命名应该有意义,最好能反映消费应用程序的目的。例如trading-strategy.abc,这是一个名为“ABC”的虚拟交易策略。一旦消费者确定了价格模式,他们就可以在订单主题上发布另一条消息,即订单请求。我们将调用另一个消费者组OrderExecution,它负责读取订单并将其转发给经纪人。在这个简单的例子中,我们创建了一个完全事件驱动和高度可扩展的端到端交易管道,假设没有其他瓶颈。在负载增加的情况下,我们可以在各个阶段动态添加更多处理节点。假设您需要多个交易策略同时运行,由一个公共数据源驱动。此外,交易策略将由不同的团队制定;目的是尽可能地解耦这些实现,让团队能够自主运作,甚至可以使用不同的编程语言和工具链按照自己的节奏进行开发和部署。Kafka灵活的多对多发布-订阅架构结合了状态消费和广播语义。通过使用不同的消费者组,Kafka允许不同的应用程序按照自己的节奏共享输入主题和处理事件。第二个交易策略将需要一个专门的消费者组:trading-strategy.xyz,将其特定的业务逻辑应用于通用定价流,并将生成的订单发布到相同的订单主题。通过这种方式,Kafka能够从易于重用和组合的离散元素构建模块化事件处理管道。总结事务流平台是构建模块化、松散耦合、事件驱动应用程序的有效构建块。在事务流的世界中,Kafka巩固了其作为成功的开源解决方案的地位,它平衡了灵活性和高性能。并发性和并行性是Kafka架构的核心,形成部分有序的事务流,可以在可扩展的消费者生态系统中进行负载平衡。消费者及其周围群体的简单重新配置可能会导致非常不同的事件分派和处理语义。当然,Kafka并非没有缺陷。可以说Kafka工具低于标准。大多数Kafka从业者早已放弃现成的CLI实用程序,转而使用其他开源工具,例如Kafdrop、Kafkacat和第三方商业产品,例如KafkaTool,新手可能会踩到这些坑。总而言之,Kafka代表了构建和构建复杂系统方式的范式转变,其优势也很明显。