消息发送流程:
消息发送组件:
其中消息的追加包含以下几个组件。我们在KafkaProducer中调用send方法发送一个消息,在消息追加步骤,最终是将消息添加到了ByteBuffer中。
消息追加器流程:
一、KafkaProducer
1.1、拦截器的实现
我们发现在send的时候,如果存在拦截器,则调用onSend方法。
@Override
public Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback) {
// intercept the record, which can be potentially modified; this method does not throw exceptions
ProducerRecord<K, V> interceptedRecord = this.interceptors == null ? record : this.interceptors.onSend(record);
return doSend(interceptedRecord, callback);
}
onSend方法的实现非常简单,实际上这就是将注册到该Producer的拦截器进行轮询,并进行调用,从源码中我们也可以知道,这个拦截器是有顺序要求的,解析配置文件时是依半角逗号来隔开的。
public ProducerRecord<K, V> onSend(ProducerRecord<K, V> record) {
ProducerRecord<K, V> interceptRecord = record;
for (ProducerInterceptor<K, V> interceptor : this.interceptors) {
try {
interceptRecord = interceptor.onSend(interceptRecord);
} catch (Exception e) {
// do not propagate interceptor exception, log and continue calling other interceptors
// be careful not to throw exception from here
if (record != null)
log.warn("Error executing interceptor onSend callback for topic: {}, partition: {}", record.topic(), record.partition(), e);
else
log.warn("Error executing interceptor onSend callback", e);
}
}
return interceptRecord;
}
// 解析配置时:
case LIST:
if (value instanceof List)
return (List<?>) value;
else if (value instanceof String)
if (trimmed.isEmpty())
return Collections.emptyList();
else
return Arrays.asList(trimmed.split("\\s*,\\s*", -1));
else
throw new ConfigException(name, value, "Expected a comma separated list.");
所以我们的配置文件可以这么写,来注册拦截器,注意,拦截器必须继承ProducerInterceptor。我们在InterceptorPlus 中,把我们即将发送的value强转为了int,然后为其++;
Properties props = new Properties();
String classNames = InterceptorPlus.class.getName() + "," + InterceptorMultiply.class.getName();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.CLIENT_ID_CONFIG, "ProducerTest");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.IntegerSerializer");
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, classNames);
// 拦截器:
/**
* Created by Anur IjuoKaruKas on 2018/9/5
*/
public class InterceptorPlus implements ProducerInterceptor {
@Override
public ProducerRecord onSend(ProducerRecord record) {
Integer val = Integer.valueOf(record.value()
.toString());
String result = String.valueOf(val + 1);
return new ProducerRecord(record.topic(),
record.key(),
result);
}
@Override
public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> configs) {
}
}
1.2、消息分区的实现
首先,先从元数据拿到集群的信息,集群信息中的partitions是以 Map<\String, List<\PartitionInfo>>来存储的。这里根据我们指定的topic名字来获取partitions。这里通过两种方式来确定消息发往哪个分区:
- 如果未指定key:在这个分区器中,保存了一个全局的AtomicInteger counter,通过一个简单的取模来确定到底发送到哪一个分区,DefaultPartitioner.toPositive(int num)方法来确保一个值一定为正数,我们知道负数取模是会得到一个负数的。
- 如果指定了key:我们知道,消息可以被指定一个key。DefaultPartitioner.toPositive(Utils.murmur2(keyBytes)) % numPartitions,同一个key,hash算出来的数字总会是相同的,只要保证分区数量不变,就可以计算出同一个partition,而同一个partition,可以确保消息发送的顺序。
/**
* Compute the partitio