Kafka分区器
send 方法里面有一块是指定计算消息该发往哪个分区的流程:
/**
* 步骤三:
* 根据分区器选择消息应该发送的分区。
*
* 因为前面我们已经获取到了元数据
* 这儿我们就可以根据元数据的信息
* 计算一下,我们应该要把这个数据发送到哪个分区上面。
*/
int partition = partition(record, serializedKey, serializedValue, cluster);
/**
* computes partition for given record.
* if the record has partition returns the value otherwise
* calls configured partitioner class to compute the 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);
}
如果你的这个消息已经分配了分区号,那直接就用这个分区号就可以了(比如消息发送失败需要重试发送,这时候分区号实际上之前已经计算好了不需要重新在计算),但是正常情况下,消息是没有分区号的。那么就需要调用Partitioner接口的方法, 看下接口信息:
public interface Partitioner extends Configurable {
/**
* 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 The serialized key to partition on( or null if no key)
* @param value The value to partition on or null
* @param valueBytes The 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);
/**
* This is called when partitioner is closed.
*/
public void close();
}
它的类的继承信息:有一个默认实现类,或者用户根据该接口自己实现自定义的分区类
它有一个默认实现类DefaultPartitioner, 看一下:
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
//首先获取到我们要发送消息的对应的topic的分区的信息
List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
//计算出来分区的总的个数
int numPartitions = partitions.size();
/**
* producer 发送数据的时候:
* message
*
* key,message
* message
*
*/
//策略一: 如果发送消息的时候,没有指定key
if (keyBytes == null) {
//这儿有一个计数器
//每次执行都会加一
//10
int nextValue = counter.getAndIncrement();
//获取可用的分区数
List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
if (availablePartitions.size() > 0) {
//计算我们要发送到哪个分区上。
//一个数如果对10进行取模, 0-9之间
//11 % 9
//12 % 9
//13 % 9
//实现一个轮训的效果,能达到负载均衡。
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
// hash the keyBytes to choose a partition
//直接对key取一个hash值 % 分区的总数取模
//如果是同一个key,计算出来的分区肯定是同一个分区。
//如果我们想要让消息能发送到同一个分区上面,那么我们就
//必须指定key. 这一点非常重要
//希望大家一定一定要知道。
return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
}
}
简单来说这段逻辑就是:首先先计算出该topic对应的分区是哪些,然后看一下上层有没有指定要发送的key,如果没指定,那么就根据内部维护的一个线程安全的计数器来进行对分区取模完成负载均衡得到要发送的具体的那个分区号,如果指定了key,那么就通过mumur2hash 计算取模。