Kafka从入门到精通(七)分区和副本机制

1. 分区和副本机制

1.1 生产者分区写入策略

生产者写入消息到topic,Kafka将依据不同的策略将数据分配到不同的分区中

  1. 轮询分区策略
  2. 随机分区策略
  3. 按key分区分配策略
  4. 自定义分区策略

1.1.1 轮询策略

  • 默认的策略,也是使用最多的策略,可以最大限度保证所有消息平均分配到一个分区。
  • 如果在生产消息时,key为null,则使用轮询算法均衡地分配分区

1.1.2 随机策略(不用)

随机策略,每次都随机地将消息分配到每个分区。在较早的版本,默认的分区策略就是随机策略,也是为了将消息均衡地写入到每个分区。但后续轮询策略表现更佳,所以基本上很少会使用随机策略。

1.1.3 按key分配策略

按key分配策略,有可能会出现「数据倾斜」,例如:某个key包含了大量的数据,因为key值一样,所有所有的数据将都分配到一个分区中,造成该分区的消息数量远大于其他的分区。

1.1.4 乱序问题

轮询策略、随机策略都会导致一个问题,生产到Kafka中的数据是乱序存储的。而按key分区可以一定程度上实现数据有序存储——也就是局部有序,但这又可能会导致数据倾斜,所以在实际生产环境中要结合实际情况来做取舍。
总结

  • 在Kafka中生产者是有写入策略,如果topic有多个分区,就会将数据分散在不同的partition中存储
  • 当partition数量大于1的时候,数据(消息)会打散分布在不同的partition中
  • 如果只有一个分区,消息是有序的

1.1.5 自定义分区策略

实现步骤:
1.创建自定义分区器

public class KeyWithRandomPartitioner implements Partitioner {

    private Random r;

    @Override
    public void configure(Map<String, ?> configs) {
        r = new Random();
    }

    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        // cluster.partitionCountForTopic 表示获取指定topic的分区数量
        return r.nextInt(1000) % cluster.partitionCountForTopic(topic);
    }

    @Override
    public void close() {
    }
}

2.在Kafka生产者配置中,自定使用自定义分区器的类名

props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, KeyWithRandomPartitioner.class.getName());

1.3 消费者分区分配策略

1.3.1 Range范围分配策略

Range范围分配策略是Kafka默认的分配策略,它可以确保每个消费者消费的分区数量是均衡的。
注意:Rangle范围分配策略是针对每个Topic的。

配置
配置消费者的partition.assignment.strategy为org.apache.kafka.clients.consumer.RangeAssignor。

算法公式

n = 分区数量 / 消费者数量
m = 分区数量 % 消费者数量
前m个消费者消费n+1个
剩余消费者消费n个

Kafka从入门到精通(七)分区和副本机制_第1张图片

1.3.2 RoundRobin轮询策略

RoundRobinAssignor轮询策略是将消费组内所有消费者以及消费者所订阅的所有topic的partition按照字典序排序(topic和分区的hashcode进行排序),然后通过轮询方式逐个将分区以此分配给每个消费者。

配置
配置消费者的partition.assignment.strategy为org.apache.kafka.clients.consumer.RoundRobinAssignor。
Kafka从入门到精通(七)分区和副本机制_第2张图片

1.3.3 Stricky粘性分配策略

从Kafka 0.11.x开始,引入此类分配策略。主要目的:

  1. 分区分配尽可能均匀
  2. 在发生rebalance的时候,分区的分配尽可能与上一次分配保持相同

没有发生rebalance时,Striky粘性分配策略和RoundRobin分配策略类似。
Kafka从入门到精通(七)分区和副本机制_第3张图片
上面如果consumer2崩溃了,此时需要进行rebalance。如果是Range分配和轮询分配都会重新进行分配,例如:
Kafka从入门到精通(七)分区和副本机制_第4张图片通过上图,我们发现,consumer0和consumer1原来消费的分区大多发生了改变。接下来我们再来看下粘性分配策略。
Kafka从入门到精通(七)分区和副本机制_第5张图片我们发现,Striky粘性分配策略,保留rebalance之前的分配结果。这样,只是将原先consumer2负责的两个分区再均匀分配给consumer0、consumer1。这样可以明显减少系统资源的浪费,例如:之前consumer0、consumer1之前正在消费某几个分区,但由于rebalance发生,导致consumer0、consumer1需要重新消费之前正在处理的分区,导致不必要的系统开销。(例如:某个事务正在进行就必须要取消了)

总结:
分区分配策略:保障每个消费者尽量能够均衡地消费分区的数据,不能出现某个消费者消费分区的数量特别多,某个消费者消费的分区特别少

  • Range分配策略(范围分配策略):Kafka默认的分配策略
    • n:分区的数量 / 消费者数量
    • m:分区的数量 % 消费者数量
    • 前m个消费者消费n+1个分区
    • 剩余的消费者消费n个分区
  • RoundRobin分配策略(轮询分配策略)
    • 消费者挨个分配消费的分区
  • Striky粘性分配策略
    • 在没有发生rebalance跟轮询分配策略是一致的
    • 发生了rebalance,轮询分配策略,重新走一遍轮询分配的过程。而粘性会保证跟上一次的尽量一致,只是将新的需要分配的分区,均匀的分配到现有可用的消费者中即可
    • 减少上下文的切换

1.4 副本机制

副本的目的就是冗余备份,当某个Broker上的分区数据丢失时,依然可以保障数据可用。因为在其他的Broker上的副本是可用的。

1.4.1 producer的ACKs参数

对副本关系较大的就是,producer配置的acks参数了,acks参数表示当生产者生产消息的时候,写入到副本的要求严格程度。当producer不断地往Kafka中写入数据,写入数据会有一个返回结果,表示是否写入成功。它决定了生产者如何在性能和可靠性之间做取舍。

配置:

props.put("acks", "all");
  • acks = 0:生产者只管写入,不管是否写入成功,可能会数据丢失。性能是最好的
  • acks = 1:生产者会等到leader分区写入成功后,返回成功,接着发送下一条
  • acks = -1/all:确保消息写入到leader分区、还确保消息写入到对应副本都成功后,接着发送下一条,性能是最差的

根据业务情况来选择ack机制,是要求性能最高,一部分数据丢失影响不大,可以选择0/1。如果要求数据一定不能丢失,就得配置为-1/all。

分区中是有leader和follower的概念,为了确保消费者消费的数据是一致的,只能从分区leader去读写消息,follower做的事情就是同步数据,Backup,即follower不提供读写服务。

那这样一来是否就不具有分布式性质了?
还是具有的。leader不是broker集群的leader,而是相对于一个分区而言的,即一个分区只有一个leader。不同的分区leader在不同的broker上这就实现了分布式。

关于副本是否提供服务,详情请看https://blog.csdn.net/weixin_46055693/article/details/124824889?spm=1001.2014.3001.5501
Kafka从入门到精通(七)分区和副本机制_第6张图片

1.5 高级API(High-Level API)、低级API(Low-Level API)

1.5.1 高级API

高级API的优点

  • 不需要执行去管理offset,直接通过ZK管理;也不需要管理分区、副本,由Kafka统一管理
  • 消费者会自动根据上一次在ZK中保存的offset去接着获取数据
  • 在ZK中,不同的消费者组(group)同一个topic记录不同的offset,这样不同程序读取同一个topic,不会受offset的影响

高级API的缺点

  • 不能控制offset,例如:想从指定的位置读取
  • 不能细化控制分区、副本、ZK等

1.5.2 低级API

通过使用低级API,我们可以自己来控制offset,想从哪儿读,就可以从哪儿读。而且,可以自己控制连接分区,对分区自定义负载均衡。而且,之前offset是自动保存在ZK中,使用低级API,我们可以将offset不一定要使用ZK存储,我们可以自己来存储offset。例如:存储在文件、MySQL、或者内存中。但是低级API,比较复杂,需要执行控制offset,连接到哪个分区,并找到分区的leader。

1.5.3 手动消费分区数据

之前的代码,我们让Kafka根据消费组中的消费者动态地为topic分配要消费的分区。但在某些时候,我们需要指定要消费的分区,例如:

  • 如果某个程序将某个指定分区的数据保存到外部存储中,例如:Redis、MySQL,那么保存数据的时候,只需要消费该指定的分区数据即可
  • 如果某个程序是高可用的,在程序出现故障时将自动重启(例如:后面我们将学习的Flink、Spark程序)。这种情况下,程序将从指定的分区重新开始消费数据。

如何进行手动消费分区中的数据呢?

  1. 不再使用之前的 subscribe 方法订阅主题,而使用 「assign」方法指定想要消费的消息
String topic = "test";
TopicPartition partition0 = new TopicPartition(topic, 0);
TopicPartition partition1 = new TopicPartition(topic, 1);
consumer.assign(Arrays.asList(partition0, partition1));
  1. 一旦指定了分区,就可以就像前面的示例一样,在循环中调用「poll」方法消费消息

注意

  1. 当手动管理消费分区时,即使GroupID是一样的,Kafka的组协调器都将不再起作用
  2. 如果消费者失败,也将不再自动进行分区重新分配

1.5.4 总结

  • 高级API就是直接让Kafka帮助管理、处理分配、数据(我们之前写的代码就是高级API)
    • offset存储在ZK中
    • 由kafka的rebalance来控制消费者分配的分区
    • 开发起来比较简单,无需开发者关注底层细节
    • 无法做到细粒度的控制
  • 低级API:由编写的程序自己控制逻辑
    • 自己来管理Offset,可以将offset存储在ZK、MySQL、Redis、HBase、Flink的状态存储
    • 指定消费者拉取某个分区的数据
    • 可以做到细粒度的控制
    • 原有的Kafka的策略会失效,需要我们自己来实现消费机制

你可能感兴趣的:(大数据,kafka,java,分布式)