1.构造器
kafka生产者构造器如下,可以传入properties,KV序列化器等等
从传入properties这个构造器入手解析下,其实KV序列化器也可以在properties里面指定,就是下面这种创建kafka生产者的常规操作,指定broker的信息等等
public static KafkaProducer<String, String> createKafkaProducer() {
Properties properties = new Properties();
properties.put("bootstrap.servers", "ali1:9092,ali2:9092,ali3:9092");
properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
KafkaProducer<String, String> producer = null;
try {
producer = new KafkaProducer<String, String>(properties);
} catch (Exception e) {
e.printStackTrace();
}
return producer;
}
2.传入的properties
properties 可以指定一堆东西,这里挑几个《Kafka权威指南》里面提到的参数说下
acks
生产者在ack个副本收到消息后认为发送成功。
如果指定是0,生产者无需确认会,直接发送下一批,高吞吐,有风险
如果指定是1,leader收到就算发送成功,如果用同步发送(生产者的send方法返回Future对象的get方法阻塞线程)那就等确认后再发,延迟就是信息传输一个来回,。如果用异步,速度会受maxInflightRequests这个参数的限制,这个参数代表生产者收到broker响应之前最大在途消息数量,源码中默认是5,如果指定是1,结合retries重试机制这样能严格保证顺序,其实和同步速度也没区别了。
如果指定是all(或者-1),ISR队列里面所有节点都收到消息才认为是成功,最安全,延迟更大点
retries
如果broker返回异常,生产者重试发送最大次数。retery.backoff.ms是重新发送的间隔,默认100ms,这里有个调优是测试下恢复崩溃节点的时间,然后重试次数✖间隔时间 不要小于这个恢复时间,防止生产者过早放弃重试。
linger.ms
RecordAccumulator是用来缓存数据的组件,消息经过分区器后会存入到这里,一个Queue对应一个broker的partition,当linger.ms设置为某个数时候,不管这个Queue是否填满,里面的消息都会发送出去。默认是0,这种情况来一个发一个(生产者有可用线程情况下)。如果设置大于0的数,那就是让消息等一会看看有无其他消息进来一起走,增大延迟,提升吞吐量。
maxInflightRequests
前面提到了,设置为1时候,即使有重试,也是保证顺序。(如果消息收到了且follower同步了,但是因为broker leader崩溃没返回成功回调函数Recordmeta,然后重试,那就需要幂等性来保证唯一,顺序是不变的)
几个timeout参数
参数 | 意思 |
---|---|
request.timeout.ms | 生产者发送消息后等待服务器返回的响应时间 |
metadata.fetch.timeout.ms | 生产者获取集群元数据等待响应时间 |
timeout.ms | broker等待副本同步时间 |
整体架构图如下,[图片来自《深入理解Kafka》
3.拦截器
this.interceptors = new ProducerInterceptors<>(interceptorList);
可以传入拦截器list,形成拦截器链。拦截器需要继承ProducerInterceptor接口,里面onSend方法可以自己定制传输前的动作,onAcknowledgement是自己定制在消息回调前做的动作。下面代码是消息的onSend“总方法”,把每个拦截器都率过一遍分别调用各自实现的onSend方法,消息的onAcknowledgement“总回调前方法”也是这样。看到下面逻辑,interceptors这个List里面,你放入的拦截器顺序会影响结果的。
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;
}
别人实现的一个实现拦截器的例子
4.分区器
把record按照一定的规律发往不同分区(先进上面对应的RecordAccumulator里面的Queue)。 ProducerRecord 构造方法中可以传入指定分区,如果没传入,那就kafka 生产者自己生成
可以看到默认分区器是DefaultPartitioner,进去看下分区策略
doSend方法中
int partition = partition(record, serializedKey, serializedValue, cluster);
//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);
}
三元表达式,record没有partition信息就自己生成,如下所示,keyBytes(key 序列化后)如果有东西,那就调用murmur2方法“Generates 32 bit murmur2 hash from byte array”,根据字节数组生成hashcode,这个murmur2方法是他们试出来的,“碰巧效果很好”,感兴趣可以看源代码,这里就不往下追了。总之搞出一个hashcode去%分区数。如果没有key,就按照topic string生成随机数,按照<topic, 随机数字>放入topicCounterMap,然后这个随机数自增取模,每次自增后覆盖原来的<topic,上一个数字>。
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 partition
return Utils.toPositive(nextValue) % numPartitions;
}
} else {
// hash the keyBytes to choose a partition
return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
}
}