【003】- Kafka技术内幕之Producer Partition(分区)

我们在前面提到过,kafka的topic是个逻辑概念,实际处理消息处理的是topic的partition。本篇我们将介绍kafka消息发送时是如何分区的以及如何自定义分区。
关注微信公众号,获取更多内容
在这里插入图片描述

一. 默认分区
kafka在发送消息时,有两个参数,一个是key,一个是value,key是跟分区相关的,表示该消息应该发送到哪个分区上。当我们在发送消息时,如果不指定key,则kafka内部默认会进行分区,如果传递了key,则按照key值进行分区。

在kafka中有个接口类Partitioner,该类中有个方法是用来计算消息发送的分区
int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster);
参数说明:
topic:主题名称
key: 分区的key,如果没有,则为null
keyBytes:序列化后的key,如果没有,则为null
value:f发送的消息体
valueBytes:序列化后的value
cluster:当前集群的元数据信息

在KafkaProducer中定义了一个私有方法,该方法计算消息的partition,如果消息有分区,则直接返回,否则使用配置文件中指定的分区类来计算分区
private int partition(ProducerRecord<K, V> record, byte[] serializedKey, byte[] serializedValue, Cluster cluster) {
Integer partition = record.partition();
return partition != null ?
partition :
partitioner.partition(
record.topic(), record.key(), serializedKey, record.value(), serializedValue, cluster);
}

在构造KafkaProducer时,我们可以看到如下代码,这个主要是从配置中读取分区类
this.partitioner = config.getConfiguredInstance(ProducerConfig.PARTITIONER_CLASS_CONFIG, Partitioner.class);
而在ProducerConfig中,我们又看到如下的定义,PARTITIONER_CLASS_CONFIG指向了DefaultPartitioner
public static final String PARTITIONER_CLASS_CONFIG = “partitioner.class”;
CONFIG = new ConfigDef().define(PARTITIONER_CLASS_CONFIG,
Type.CLASS,
DefaultPartitioner.class,
Importance.MEDIUM, PARTITIONER_CLASS_DOC)
DefaultPartitioner是Partitoner的实现类,里面具体定义了默认分区的方式
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
// 根据topic获取topic对应的分区信息,PartitionInfo保存了每个分区信息,包括主题,分区,leader,replicas等
List partitions = cluster.partitionsForTopic(topic);
// 获取主题的分区数
int numPartitions = partitions.size();
// 没有传递key值的情况
if (keyBytes == null) {
//获取topic计数器
int nextValue = nextValue(topic);
// 获取可用的分区
List availablePartitions = cluster.availablePartitionsForTopic(topic);
if (availablePartitions.size() > 0) {
int part = Utils.toPositive(nextValue) % availablePartitions.size();
return availablePartitions.get(part).partition();
} else {
// 没有可用的partition
return Utils.toPositive(nextValue) % numPartitions;
}
} else {
// key不为空,选择分区
return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
}
}

(1) 不指定key的分区
在DefaultPartitioner中定义了ConcurrentMap<String, AtomicInteger> topicCounterMap;它表示topic的计数器
在key为null的情况下,会执行下述代码
private int nextValue(String topic) {
//根据topic获取当前主题的计数器值
AtomicInteger counter = topicCounterMap.get(topic);
// 第一次发送消息
if (null == counter) {
// 创建随机数
counter = new AtomicInteger(ThreadLocalRandom.current().nextInt());
AtomicInteger currentCounter = topicCounterMap.putIfAbsent(topic, counter);
if (currentCounter != null) {
counter = currentCounter;
}
}
// 递增,产生新的计数器
return counter.getAndIncrement();
}

代码测试
private final static ConcurrentMap<String, AtomicInteger> topicCounterMap = new ConcurrentHashMap<String, AtomicInteger>();

public static void main(String args[]) {
    for (int i = 0; i < 10; i++) {
        int nextValue = nextValue("beardata");
        System.out.println("选择分区:" + toPositive(nextValue % 2));
    }
}

private static int nextValue(String topic) {
    AtomicInteger counter = topicCounterMap.get(topic);
    if (null == counter) {
        counter = new AtomicInteger(ThreadLocalRandom.current().nextInt());
        AtomicInteger currentCounter = topicCounterMap.putIfAbsent(topic, counter);
        if (currentCounter != null) {
            counter = currentCounter;
        }
    }
    return counter.getAndIncrement();
}

public static int toPositive(int number) {
    return number & 0x7fffffff;
}

在这里插入图片描述

实践案例
我们创建主题beardata,并指定3个分区,我们在生产者端不指定key,发送5条消息0,1,2,3,4,观察每个分区的数据情况
首先我们查看beardata的分区,
./bin/kafka-topics.sh --describe --topic beardata --zookeeper bigdata000:2181
在这里插入图片描述
可以看到beardata主题有两个partition,分别为0,1
我们发送消息并在消费端分别消费beardata的partition0和partition1
在这里插入图片描述
在这里插入图片描述

结论:当我们未指定key时,消息是均匀分发送给每个partition

(2) 指定key值的分区
调用Utils.murmur2(keyBytes),返回keyBytes的32位hash值
我们在发送消息时,指定两个key,分别为colin和harper,colin发送0,2,4,6,8消息,harper发送1,3,5,7,9消息
在这里插入图片描述

在这里插入图片描述
结论:colin被hash成1,harper被hash成0,分别发送给两个分区

二. 自定义分区
有些场景,我们需要自己定义分区策略,以满足我们的业务需求。一种场景是同一个topic里面消息体处理业务的类型不同,即一个消息包含了不同的几类业务,假如创建多个主题,则开销比较大,我们可以考虑根据消息类型将消息发送到不同的partition上,然后消费端根据partition去处理不同的业务类型,这是我们可以考虑自定义partition。
我们在前面提到过,kafka的Partitioner是一个接口,我们可以实现此接口,定义自己的分区处理逻辑,然后在加载配置时指定我们自定义的分区类即可。在我们下面自定义分区实现中,我们把value大于100的消息发送到beardata的partition0上,小于等于100的发送到beardata的partition1上。
实现代码:
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import java.util.Map;

/**

  • Author: zhangxiong

  • Date: 18-8-5 下午4:55

  • Desc:
    */
    public class BearDataPartitioner implements Partitioner {

    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
    int v = Integer.parseInt(value.toString());
    if (v > 100) {
    return 0;
    } else {
    return 1;
    }
    }

    public void close() {

    }

    public void configure(Map<String, ?> map) {

    }
    }
    在这里插入图片描述
    在这里插入图片描述

以上就是kafka Producer分区的内容,下一篇我们将介绍

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值