1. 分区和副本机制
1.1 生产者分区写入策略
生产者写入消息到topic,Kafka将依据不同的策略将数据分配到不同的分区中
轮询分区策略随机分区策略按key分区分配策略自定义分区策略
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;@Overridepublic void configure(Map<String, ?> configs) {r = new Random();}@Overridepublic int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {// cluster.partitionCountForTopic 表示获取指定topic的分区数量return r.nextInt(1000) % cluster.partitionCountForTopic(topic);}@Overridepublic 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个
1.3.2 RoundRobin轮询策略
RoundRobinAssignor轮询策略是将消费组内所有消费者以及消费者所订阅的所有topic的partition按照字典序排序(topic和分区的hashcode进行排序),然后通过轮询方式逐个将分区以此分配给每个消费者。
配置
配置消费者的partition.assignment.strategy为org.apache.kafka.clients.consumer.RoundRobinAssignor。
1.3.3 Stricky粘性分配策略
从Kafka 0.11.x开始,引入此类分配策略。主要目的:
分区分配尽可能均匀在发生rebalance的时候,分区的分配尽可能与上一次分配保持相同
没有发生rebalance时,Striky粘性分配策略和RoundRobin分配策略类似。
上面如果consumer2崩溃了,此时需要进行rebalance。如果是Range分配和轮询分配都会重新进行分配,例如:
通过上图,我们发现,consumer0和consumer1原来消费的分区大多发生了改变。接下来我们再来看下粘性分配策略。
我们发现,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上这就实现了分布式。
关于副本是否提供服务,详情请看/weixin_46055693/article/details/124824889?spm=1001..3001.5501
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程序)。这种情况下,程序将从指定的分区重新开始消费数据。
如何进行手动消费分区中的数据呢?
不再使用之前的 subscribe 方法订阅主题,而使用 「assign」方法指定想要消费的消息
String topic = "test";TopicPartition partition0 = new TopicPartition(topic, 0);TopicPartition partition1 = new TopicPartition(topic, 1);consumer.assign(Arrays.asList(partition0, partition1));
一旦指定了分区,就可以就像前面的示例一样,在循环中调用「poll」方法消费消息
注意
当手动管理消费分区时,即使GroupID是一样的,Kafka的组协调器都将不再起作用如果消费者失败,也将不再自动进行分区重新分配
1.5.4 总结
高级API就是直接让Kafka帮助管理、处理分配、数据(我们之前写的代码就是高级API) offset存储在ZK中由kafka的rebalance来控制消费者分配的分区开发起来比较简单,无需开发者关注底层细节无法做到细粒度的控制 低级API:由编写的程序自己控制逻辑 自己来管理Offset,可以将offset存储在ZK、MySQL、Redis、HBase、Flink的状态存储指定消费者拉取某个分区的数据可以做到细粒度的控制原有的Kafka的策略会失效,需要我们自己来实现消费机制如果觉得《Kafka从入门到精通(七)分区和副本机制》对你有帮助,请点赞、收藏,并留下你的观点哦!