分区是 Kafka 的核心功能,对于 Kafka 的存储结构、消息的生产消费方式都至关重要。
Partition(分区)
每 topic 都可以分成多分区,每个分区都是一组有序的、不可变的记录序列,每个分区在存储层面是 append log 文件。
分区中的每一条消息数据都被赋予了一个连续的数字ID,即偏移量 (offset) ,用于唯一标识分区中的每条消息数据,任何发布到此分区的消息都会被直接追加到 log 文件的尾部。
为什么要进行分区?
既然 Topic 已经对消息进行了分类,为什么每个 Topic 内部还需要再次分区?
topic 是逻辑的概念,partition 是物理的概念。
最根本的原因就是 kafka 基于文件进行存储,当文件内容大到一定程度时,很容易大到单个磁盘的上限。如果没有分区,一个 topic 对应的消息集在分布式集群服务组中,就会分布不均匀,可能导致某台服务器消息堆积,吞吐也容易导致瓶颈。
有了分区后,kafka 内部会根据一定的算法把分区数尽可能均匀分布到不同的服务器上,比如:服务器1负责 topic 的分区0,服务器2负责 topic 的分区1,生产者发送消息时若没指定发送到哪个分区的时候,kafka 就会根据一定算法均匀分布到不同的服务器上。
所以,分区的目的是通过多分区实现负载均衡的效果,提高 kafka 访问吞吐率。
Kafka分区策略
Kafka 默认分区策略同时实现了两种策略:
- 不指定Key:则使用轮询策略
- 指定Key:默认实现按消息键策略
轮询策略
轮询策略,即顺序分配策略。轮询策略是 Kafka 默认提供的分区策略,有非常优秀的负载均衡表现,能保证消息最大限度地被平均分配到所有分区上。
按消息键策略
Kafka 允许为每条消息定义消息键,简称为 key。一旦消息被定义了 key,就可以保证同一个 key 的所有消息都进入到相同的分区中。
消息顺序性
使用 kafka 发送的消息如果没有顺序上的约束的话,那么在一个 topic 的不同 partition 之间就是水平扩展关系。只是多几个并行的消息通道而已。直接传递消息(V)即可。
如果将 Topic 设置成单分区,该 Topic 的所有的消息都只在一个分区内读写,保证全局的顺序性,但将丧失 Kafka 多分区带来的高吞吐量和负载均衡的性能优势。
多分区消息保序的方法是按消息键策略,同一 key 的消息都会发送到同一分区,kafka 的 broker 进程会保证 producer 推送消息的顺序。既可以保证分区内的消息顺序,也可以实现多分区带来的高吞吐量。
发送消息的时候,可以指定一个分区 key,这样就可以写入特定分区了。分区 key 会通过 Hash 计算结果决定写入哪个分区,有相同分区 key 的消息,会被放到相同的分区中。一个分区可能会有不同的 K,且不同的 K 是交叉排列的,相同的 K 在一个分区没有排列在一起。虽然是和其他 K 交叉排列的,但是顺序还是一样的。consumer 通过 K 去区分同一种需要顺序性的消息,从而能按照原有顺序去消费。
如果没有使用分区 key,Kafka 就会使用轮询的方式来决定写入哪个分区中。这样,消息会均衡的写入各个分区,这样无法确保消息的有序性。
注意
- 如果在多分区消息保序的场景一下业务上也有关联必须由一个 producer 去 push ,因为一个 producer 内部才能去保证消息有序性。
- 消息重试只是简单将消息重新发送到原来的分区,不会重新选择分区。
消费消息
Kafka 不会向消费者推送消息,消费者必须自己从 Topic 的分区中拉取消息。
kafka 只能保证分区内有序,无法保证分区间有序,所以消费时数据是无序的。
如果增加分区时,分区内的消息不会重新进行分配,随着数据继续写入,新分区才会参与再平衡。
偏移量 (Offset)
一个分区对应一个磁盘上的文件,分区中的每条记录都会被分配一个唯一的序号,称为 Offset(偏移量)。
Offset 是一个递增的、不可变的数字,由 Kafka 自动维护。当一条记录写入分区时,它就被追加到 log 文件的末尾,并被分配一个序号,作为 Offset。在 kafka 中几乎不允许对消息进行“随机读写”,文件只能顺序的读写。
一个 Topic 如果有多个分区的话的情况下,那么从 Topic 这个层面来看,消息是无序的。
若单独看分区的情况下,分区内部消息是有序的。所以,一个分区内部消息有序,一个 Topic 跨分区是无序的。
如果强制要求 Topic 整体有序,就只能让 Topic 只有一个分区。