前提了解:
整个kafka生产者客户端由两条线程协调运行。
这两条线程分别为主线程和sender线程(发送线程)
主线程的作用就是:由KafkaProducer创建消息,然后通过可能的拦截器,序列化器,分区器的作用之后缓存到消息累加器
send线程的作用就是:负责将消息累加器中的消息发送到kafka中。
拦截器:
拦截器是在kafka0.10.0.0版本中就已经引入的一个功能,kafka一共有两种拦截器。生产者拦截器和消费者拦截器。
生产者拦截器:
生产者拦截器可以用来在消息发送前做一些准备工作,比如按照某个规则过滤不符合要求的消息,修改消息的内容等,也可以用来在发送回调逻辑前做一些定制化的需求。
当然生产者拦截器的使用也很方便,主要是自定义实现org.apache.kafka.clients.producer.ProducerInterceptor接口。
ProducerInterceptor接口中包含3个方法:
//producer会在消息序列化器,分区器之前调用拦截器的onSend()方法来对消息进行定制化操作。public ProducerRecord<K, V> onSend(ProducerRecord<K, V> record);
//producer会在消息被应答之前或者发送失败时调用生产者的 onAcknowledgement()方法//不过该方法运行在Producerde I/O线程中,所以方法中的实现代码逻辑越简单越好,否则会影响消息的发送速度public void onAcknowledgement(RecordMetadata metadata, Exception exception);
//用于在关闭拦截器时执行一些资源的清理工作public void close();
自定义生产者拦截器示例:
import org.apache.kafka.clients.producer.ProducerInterceptor;import org.apache.kafka.clients.producer.ProducerRecord;import org.apache.kafka.clients.producer.RecordMetadata;import java.util.Map;/*** 生产者拦截器*/public class ProducerInterceptorPrefix implements ProducerInterceptor<String, String> {private volatile long sendSuccess = 0;private volatile long sendFailure = 0;//定制化数据@Overridepublic ProducerRecord<String, String> onSend(ProducerRecord<String, String> producerRecord) {final String mpdofoeddValue = "preFix" + producerRecord.value();return new ProducerRecord<String, String>(producerRecord.topic(),producerRecord.partition(),producerRecord.timestamp(),producerRecord.key(),mpdofoeddValue,producerRecord.headers());}//消息被应答前发送失败时会调用@Overridepublic void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {if(e == null ){sendSuccess ++;}else{sendFailure ++;}}@Overridepublic void close() {final double successRatio = (double)sendSuccess / (sendSuccess + sendFailure);System.out.println("发送成功率 = " + String.format("%f",successRatio* 100) + "%");}@Overridepublic void configure(Map<String, ?> map) { }}
实现自定义的ProducerInterceptorPrefix之后,需要在producer的配置参数中指定拦截器。示例:
import org.apache.kafka.clients.producer.KafkaProducer;import org.apache.kafka.clients.producer.ProducerConfig;import org.apache.kafka.clients.producer.ProducerRecord;import org.apache.kafka.common.serialization.StringSerializer;import java.util.Properties;/*** kafka生产*/public class KafkaProducerFastStart {public static final String brokerList = "127.0.0.1:9092";public static final String topic = "topic-demo";public static void main(String args[]) {Properties properties = new Properties();properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, brokerList); //集群地址properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); // 16K batch.sizeproperties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432); // 32M buffer.memoryproperties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());//KEY序列化方式properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());//value序列化方式properties.put(ProducerConfig.ACKS_CONFIG, "1");properties.put(ProducerConfig.RETRIES_CONFIG, 3);//retriesproperties.put(ProducerConfig.LINGER_MS_CONFIG, 1);//linger.msproperties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, ProducerInterceptorPrefix.class.getName());// interceptor.classes 拦截器//配置生产者客户端参数并创建KafkaProducer实例KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);//构建需要发送得消息ProducerRecord<String, String> data = new ProducerRecord<String, String>(topic, "key", "value");try {producer.send(data);} catch (Exception e) {e.printStackTrace();} finally {producer.close();}}}
文中第27行指定了拦截器。
当然,拦截器不仅仅可以指定一个。还可以指定多个形成拦截链,拦截链会按照interceptor.classes 参数配置的拦截器的顺序来一一执行。每个拦截器之间用逗号隔开。
properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,ProducerInterceptorPrefix.class.getName() + "," + ProducerInterceptorPrefix2.class.getName());// interceptor.classes 拦截器
序列化器:
生产者需要用序列化器将对象转换成字节数组才能通过网络发送给kafka.当然消费者需要用对应的反序列化器将kafka的字节数组转换为相应的对象。
kafka客户端自带的序列化器有:
DoubleSerializer ,ByteArraySerializer ,ByteBufferSerializer,
BytesSerializer , IntegerSerializer , LongSerializer ,StringSerializer
kafka生产者指定序列化器:
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());//KEY序列化方式properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());//value序列化方式
当然,如果这几种序列化器都无法满足应用需求,我们可以选择如 Avro,JSON,Thrift,Protobuf等,或者使用自定义类型的序列化器来实现。
图中 Company 为自定义的对象。
import org.apache.kafka.common.serialization.Serializer;import java.io.UnsupportedEncodingException;import java.nio.ByteBuffer;import java.util.Map;/**生产端序列化器*/public class CustomizeSerializer implements Serializer<Company> {@Overridepublic void configure(Map<String, ?> map, boolean b) { }@Overridepublic byte[] serialize(String topic, Company company) {if (company == null) {return null;}byte[] name, address;try {if (company.getName() != null) {name = company.getName().getBytes("UTF-8");} else {name = new byte[0];}if (company.getAddress() != null) {address = company.getAddress().getBytes("UTF-8");} else {address = new byte[0];}ByteBuffer buffer = ByteBuffer.allocate(4 + 4 + name.length + address.length);buffer.putInt(name.length);buffer.put(name);buffer.putInt(address.length);buffer.put(address);return buffer.array();} catch (UnsupportedEncodingException e) {e.printStackTrace();}return new byte[0];}@Overridepublic void close() {}}
kafkaProducer配置:
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,CustomizeSerializer.class.getName());
反序列化器:
import org.apache.kafka.common.errors.SerializationException;import org.apache.kafka.common.serialization.Deserializer;import java.io.UnsupportedEncodingException;import java.nio.ByteBuffer;import java.util.Map;/*** 反序列化器*/public class CustomizeDeserializer implements Deserializer<Company> {@Overridepublic void configure(Map<String, ?> map, boolean b) { }@Overridepublic Company deserialize(String topic, byte[] data) {if(data == null ){return null;}if(data.length < 8 ){throw new SerializationException("Size of data received by DemoDeserializer is shorter than expected !");}ByteBuffer buffer = ByteBuffer.wrap(data);int nameLen,addressLen;String name, address;nameLen = buffer.getInt();final byte[] nameBytes = new byte[nameLen];buffer.get(nameBytes);addressLen = buffer.getInt();byte[] addressBytes = new byte[addressLen];buffer.get(addressBytes);try{name = new String(nameBytes,"UTF-8");address = new String(addressBytes,"UTF-8");}catch (UnsupportedEncodingException e){throw new SerializationException("Error occur when deserializing");}return new Company(name,address);}@Overridepublic void close() { }}
消费者指定反序列化器:
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,CustomizeDeserializer.class.getName());
分区器:
消息通过send()方法发送broker的过程中,有可能会经过拦截器,序列化器,之后,就会需要确定消息要发往的分区。如果ProducerRecord中指定了partition字段,那么就不需要分区器的作用。因为partition代表的就是索要发往的分区号。
kafka提供的默认分区器是org.apache.kafka.clients.producer.internals.DefaultPartitioner.它实现了org.apache.kafka.clients.producer.Partitioner接口,这个接口定义了2个方法:
//用来计算分区号。@params topic 分区@params key 消息的key@params keyBytes 序列化后的消息key@params value 消息值@params valueBytes 序列化后的值@params cluster 集群的元数据信息public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster);public void close();
当然Pratitioner接口还有一个父接口:org.apache.kafka.common.Configurable。该接口中只有一个方法:
//用来获取配置信息及初始化数据void configure(Map<String, ?> configs);
接下来我们看下默认分区器中的实现:
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);int numPartitions = partitions.size();if (keyBytes == null) {int nextValue = nextValue(topic);List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);if (availablePartitions.size() > 0) {int part = Utils.toPositive(nextValue) % availablePartitions.size();return availablePartitions.get(part).partition();} else {// no partitions are available, give a non-available partitionreturn Utils.toPositive(nextValue) % numPartitions;}} else {// hash the keyBytes to choose a partitionreturn Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;}}
可以看到:如果key不为null,那么默认分区器会对key进行哈希(采用MurmurHash2算法)得到的哈希值来计算分区号。
接下来我们可以自定义我们自己的分区器:
import org.apache.kafka.clients.producer.Partitioner;import org.apache.kafka.common.Cluster;import org.apache.kafka.common.PartitionInfo;import org.apache.kafka.common.utils.Utils;import java.util.List;import java.util.Map;import java.util.concurrent.atomic.AtomicInteger;/*** 分区器*/public class Dempartitioner implements Partitioner {private final AtomicInteger counter = new AtomicInteger(0); //原子操作类AtomicInteger@Overridepublic int partition(String topic, Object key, byte[] keyBytes, Object valye, byte[] valueBytes, Cluster cluster) {List<PartitionInfo> partitionInfos = cluster.partitionsForTopic(topic);int numPartitions = partitionInfos.size();if(null == keyBytes){return counter.getAndIncrement() % numPartitions;}else{return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;}}@Overridepublic void close() {}@Overridepublic void configure(Map<String, ?> map) { }}
指定分区器的配置:
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,Dempartitioner.class.getName());//partitioner.class
消息累加器:
RecordAccumulator也叫消息累加器,主要用来缓存消息以便Sender线程可以批量发送,进而减少网络传输的资源消耗来提升性能。 是在客户端开辟出的一块内存区域。
this.accumulator = new RecordAccumulator(logContext,config.getInt("batch.size"),this.totalMemorySize,this.compressionType,config.getLong("linger.ms"),retryBackoffMs,this.metrics,this.time,this.apiVersions,this.transactionManager);
参数buffer.memory,默认为33554432B,及32MB. 指定RecordAccumulator缓存的大小
如果生产者发送消息的速度超过发送到服务器的速度,则会导致生产者空间不足。这个时候producer的send方法要么被阻塞,要么抛出异常。这个取决于参数max.block.ms的配置,此参数默认值为60000,及60秒。
RecordAccumulator的内部为每个分区都维护了一个双端队列。队列中的具体内容就是ProducerBatch(消息批次).即Deque<ProducerBatch>.
通俗来讲,ProducerBatch为一个消息批次,可以将较小的ProducerRecord拼凑成一个较大的ProducerBatch.来减少网络请求次数提高吞吐量。
当然ProducerRecord即为我们的消息。
每次有消息要写入到累加器中的时候,会先去寻找对应的双端队列,从队列的尾部获取一个ProducerBatch, 事先会判断消息的大小,如果大小在被写入的Producerbatch批次范围内,则写入。如果没找到ProducerBatch或者消息大小大于被写入的批次范围无法写入的时候,就会新建一个ProducerBatch,根据参数batch.size来创建batch大小。如果消息大于batch.size的大小,就以评估的大小创建ProducerBatch.
消息写入缓存时,追加到双端队列的尾部Sender线程读取消息时,从双端队列的头部读取。注意ProducerBatch中可以包含很多个ProducerRecord(消息)。
在RecordAccumulator的内部还有一个BufferPool,主要来实现ButeBuffer的复用。不过BufferPool只针对特定大小的ByteBuffer进行管理。超过大小是不会进入BufferPool的。可以通过参数batch.size来指定。默认为16384B.
当每条消息流入消息累加器,会先寻找每个分区中的ProducerBatch双端队列。如果找不到,会判断消息的大小,如果小于batch.size。则用batch.size的大小来创建ProducerBatch.
sender线程会从RecordAccumulator中获取缓存的消息之后,进一步将消息封装成<Node,Request>的形式,通过KafkaClient进行IO操作。
这里有篇关于源码解读的文章:
https://www.cnblogs.com/dingaimin/p/11523787.html




