1.构造器

kafka生产者构造器如下,可以传入properties,KV序列化器等等

kafka生产者的python代码 kafka生产者源码解析_kafka


从传入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

kafka生产者的python代码 kafka生产者源码解析_拦截器_02


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》

kafka生产者的python代码 kafka生产者源码解析_随机数_03

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 生产者自己生成

kafka生产者的python代码 kafka生产者源码解析_kafka生产者的python代码_04


可以看到默认分区器是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;
        }
    }