一、分区器的初始化时机
1、在KafkaProducer获取分区器,
/**
* 读取使用的分区器
* 分区器配置名properties.put("partitioner.class" ,"全类名")
* 如果配置了将会使用用户自定义的分区器,如果用户没有自定义分区器,这里将会使用
* class org.apache.kafka.clients.producer.internals.DefaultPartitioner
* 分区器进行分区。
* 具体kafka是如何实现这个功能的点进去源码分析
* config.getConfiguredInstance(),就是读取配置中的一个,接口实例
*/
this.partitioner = config.getConfiguredInstance(ProducerConfig.PARTITIONER_CLASS_CONFIG, Partitioner.class);
2、 调用根据配置获取实例对象方法
/**
* 获取T接口的的配置实例对象
*
* @param key 配置ksy
* @param t 接口
* @return A 配置的value实例
*/
public <T> T getConfiguredInstance(String key, Class<T> t) {
//获取配置的Class对象
Class<?> c = getClass(key);
if (c == null)
return null;
//反射创建实例
Object o = Utils.newInstance(c);
//类型判断
if (!t.isInstance(o))
throw new KafkaException(c.getName() + " is not an instance of " + t.getName());
if (o instanceof Configurable)
//配置到用户自定义配置
((Configurable) o).configure(originals());
return t.cast(o);
}
现在看看如何根据
3、从解析配置map中获取配置值
Class<?> c = getClass(“partitioner.class”);获取到的用户护着默认的分区器实例
protected Object get(String key) {
if (!values.containsKey(key))
throw new ConfigException(String.format("Unknown configuration '%s'", key));
//添加到使用配置
used.add(key);
//从已经解析配置中获配置值
return values.get(key);
}
解析配置中读取的配置 private final Map<String, Object> values;
在ProducerConfig中的初始化默认配置源码中可以看出:
配置的是 DefaultPartitioner.class
.define(PARTITIONER_CLASS_CONFIG,
Type.CLASS,
DefaultPartitioner.class,
Importance.MEDIUM, PARTITIONER_CLASS_DOC)
如果用户有自己的配置partitioner.class值,就会在
ProducerConfig的父类AbstractConfig的构造方法
AbstractConfig(ConfigDef definition, Map<?, ?> originals, boolean doLog)
中将用户的自定义值覆盖掉默认的值
在实例化KafkaProducer时候,初始化分区器是读取partitioner.class 配置,该配置默认使用DefaultPartitioner.class,然后使用反射创建对象,然后赋值给KafkaProducer的partitioner成员中。
二、分区器的体系
1、体系图
分区器接口:Partitioner,Kafka默认采用其唯一实现类DefaultPartitioner
2、DefaultPartitioner分区实现:
当指定了key,则采用固定的算法,每次都会计算出同一分区号,如果key不存在则计算出一个可用分区的的一个任意分区
/**
* Compute the partition for the given record.
*
* @param topic The topic name
* @param key The key to partition on (or null if no key)
* @param keyBytes serialized key to partition on (or null if no key)
* @param value The value to partition on or null
* @param valueBytes serialized value to partition on or null
* @param cluster The current cluster metadata
*/
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
// 获取该topic所有分区 【见2.1】
List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
int numPartitions = partitions.size();
// key不存在
if (keyBytes == null) {
// 获取该topic计数器
int nextValue = nextValue(topic);
// 获取可用分区
List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
if (availablePartitions.size() > 0) {
// 计算一个分区,key不存在的到是可用分区的随机一个
int part = Utils.toPositive(nextValue) % availablePartitions.size();
return availablePartitions.get(part).partition();
} else {
// no partitions are available, give a non-available partition
return Utils.toPositive(nextValue) % numPartitions;
}
} else {
// key存在,key存在的情况下,每次得到的都是统一分区
// hash the keyBytes to choose a partition
return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
}
}
private 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();
}
2.1获取指定topic的所有分区
Cluster是Kafka集群的模型,其中维护了
/**
* topic 与 partition 的对应关系
* 一个topic 对应多个partition
*/
private final Map<String, List<PartitionInfo>> partitionsByTopic;
private final Map<String, List<PartitionInfo>> availablePartitionsByTopic;
public List<PartitionInfo> partitionsForTopic(String topic) {
//
List<PartitionInfo> parts = this.partitionsByTopic.get(topic);
return (parts == null) ? Collections.<PartitionInfo>emptyList() : parts;
}