Kafka中的一切都是围绕着分区建模的。它控制着Kafka的存储、可伸缩性、副本和消息移动。
Kafka中的一切都围绕着分区。它在组织Kafka的存储和信息的生产和消费中扮演着重要的角色。理解分区可以帮助你更快地学习Kafka。这篇文章介绍了Kafka分区的概念、结构和行为。
事件(event)、流(streams)和Kafka Topic
在深入分区前,我们先了解一些宏观概念以及这些概念和分区的关系。
事件(Events)
事件代表过去发生的事实。事件是不可改变的,不会停留在一个地方。它们总是携带发生的状态变化从一个系统移动到另一个系统。
流(streams)
事件流表示系统业务活动中的相关事件。
主题(topics)
当一个事件流进入kafka,以topic持久化。在kafka中一个topic是一个物化的事件流。
Topics组将相关的事件分组在一起,并持久化存储。kafka中topic可以类比为数据库中的一张表或文件系统中的一个文件夹。
Topic作为Kafka中的核心概念将生产者和消费者解耦。消费者从kafka主题中拉取消息,而生产者向topic推送消息。一个topic可以包含很多生产者和消费者。
分区(partition)
Kafka的topic被分成多个分区。topic在kafka中是一个逻辑概念,一个分区是最小的存储单元,存储一个主题中的消息的子集。每个分区都是一个日志文件,消息以只追加的方式写入其中。
当谈到一个分区中的消息,我将交替使用记录【record】和消息【message】两个术语。
偏移量(Offsets)和消息的排序
分区中的每条记录都被分配一个名为偏移量(offset)的顺序标识符,该标识符对应分区中的每条记录都是唯一的。
偏移量(offset)是一个递增的、不可变的数字,由Kafka维护。当记录被写入分区时,它被追加到日志的末尾,并分配下一个顺序偏移量。当用户从分区读取记录时,偏移量特别有用。我们稍后会讲到。下图显示了一个包含三个分区的主题。记录被追加到每个分区的末尾。
尽管分区内的消息是有序的,但跨主题的消息不能保证是有序的。
分区是Kafka提供可扩展性的方式
Kafka集群由一个或多个服务器组成。在kafka的世界里,他们被称为代理(brokers)。每个代理持有属于整个集群的记录子集。Kafka将一个特定主题的分区分布到多个broker上。通过这样做,我们将得到以下好处。
- 如果我们将一个主题的所有分区放在一个代理中,那么该主题的可伸缩性将受到代理IO吞吐量的限制。一个主题永远不会超过集群中最大的机器。通过将分区分散到多个代理,单个主题可以水平扩展,以提供远远超过单个代理服务器的性能。
- 单个主题可以由多个使用者并行消费。单个代理存储所有的分区的话会限制消费者的连接数量。分布到多个代理上的分区支持更多的消费者。
- 同一个消费者的多个实例可以连接到不同代理上的分区,从而允许非常高的消息处理吞吐量。每个消费者实例将由一个分区提供服务,以确保每个记录都有一个清晰的处理所有者。
分区是Kafka提供冗余的方式。
Kafka在多个broker上保留同一个分区的多个副本。这个冗余的拷贝称为副本。如果一个代理失败了,Kafka仍然可以用其他代理存储的分区副本来服务消费者。
向分区写入记录
生产者如何决定记录应该进入哪个分区?生产者可以通过三种方式进行裁决。
使用分区键指定分区
生产者可以使用分区键将消息指向特定的分区。分区键可以是来自应用程序上下文的任何值。一个唯一的设备ID或用户ID将是一个很好的分区键。
默认情况下,分区键通过一个哈希函数传递,该函数创建分区分配。这样可以确保所产生的记录包含相同的分区键都将到分配到相同的分区。指定分区键可以将相关事件保存在同一个分区中,并按照确切顺序发送。
如果键没有很好地分布,基于键的分区分配可能会导致代理倾斜。
例如,当使用客户ID作为分区键时,一个客户生成90%的流量,那么在大多数情况下,一个分区将获得90%的流量。在topic量小情况,这可以忽略不计,但在量大情况,这有时会让代理服务器崩溃。
当选择分区键时,确保它们分配均衡的。
允许kafka决定分区
如果生产者在生产记录时没有指定分区键,Kafka将使用轮循分区分配。这些记录将被均匀地写入特定主题的所有分区。
但是,如果不使用分区键,则在给定的分区中不能保证记录的顺序。
关键是使用分区键将相关事件按发送的确切顺序放在同一个分区中。
编写自定义分区程序
在某些情况下,生产者可以使用自己的分区程序实现,该实现使用其他业务规则来进行分区分配。
从分区读取记录
与其他发布/订阅实现不同,Kafka不向消费者推送消息。相反,消费者必须从Kafka主题分区中拉取消息。消费者连接到代理中的一个分区,按照消息被写入的顺序读取消息。
此时,消息的偏移量相当于消费者端游标。消费者通过跟踪消息的偏移量来跟踪它已经消费了哪些消息。读取消息后,消费者将游标移到分区中的下一个偏移量并继续。移动和记住分区内的最后读偏移量是消费者的责任。kafka不管偏移量的变化。
通过记住每个分区上一次消费消息的偏移量,消费者可以在他们选择的时间点加入分区并从那里恢复。这对于从崩溃中恢复后的消费者恢复阅读特别有用。
一个分区可以由一个或多个消费者使用,每次读取的偏移量不同。Kafka有消费者组的概念,多个消费者被分组来消费一个特定的主题。在同一个消费者组中的消费者被分配相同的group-id值。
消费者组概念确保消息只被组中的单个消费者读取。
当一个消费者组消费一个topic的分区时,Kafka会确保每个分区都被组中的一个消费者消费。
下图表述这其中的关系:
消费者组允许消费者以非常高的吞吐量并行化处理消息。但是,一个组的最大并行度将等于该主题的分区数。
例如,对于一个包含N个分区的主题,如果有N + 1个消费者,那么前N个消费者将被分配一个分区,其余消费者将处于空闲状态,除非这N个消费者中有一个发生故障,等待的消费者将被分配分区。这是实现故障热转移的一个很好的策略。
下图说明了这一点:
结论是,消费者的数量并不能决定一个Topic的并行度。它是由分区的数目决定的。