kafka源码_Kafka源码深度剖析系列(五)——KafkaProducer初始化

了解KafkaProducer的初始化过程,重点掌握初始化过程中涉及到的核心参数。

e458ad10875fc39c4b9709de5a9499b9.png

-     初始化代码     -

这篇文章主要分析KafkaProducer初始化的时候都初始化了啥?我们还是以场景驱动的方式。上一次我们的代码运行到:

 //TODO 核心代码        producer = new KafkaProducer<>(props);
点击过去,看到如下代码:
 public KafkaProducer(Properties properties) {        this(new ProducerConfig(properties), null, null);    }
直接再点击过去 看到的这个方法就是KafkaProducer初始化的方法,也是我们今天主要分析的方法了。
private KafkaProducer(ProducerConfig config, Serializer keySerializer, Serializer valueSerializer) {        try {            log.trace("Starting the Kafka producer");            //获取用户自定义的配置            Map userProvidedConfigs = config.originals();            this.producerConfig = config;            this.time = new SystemTime();             //获取client id,如果没有就自动生成一个            clientId = config.getString(ProducerConfig.CLIENT_ID_CONFIG);            if (clientId.length() <= 0)                clientId = "producer-" + PRODUCER_CLIENT_ID_SEQUENCE.getAndIncrement();            //跟监控信息有关,这个跟我们分析源码的主流程关系不大            //所以关于metric的东西可以不用关心            Map metricTags = new LinkedHashMap();            metricTags.put("client-id", clientId);            MetricConfig metricConfig = new MetricConfig().samples(config.getInt(ProducerConfig.METRICS_NUM_SAMPLES_CONFIG))                    .timeWindow(config.getLong(ProducerConfig.METRICS_SAMPLE_WINDOW_MS_CONFIG), TimeUnit.MILLISECONDS)                    .tags(metricTags);            List reporters = config.getConfiguredInstances(ProducerConfig.METRIC_REPORTER_CLASSES_CONFIG,                    MetricsReporter.class);            reporters.add(new JmxReporter(JMX_PREFIX));            this.metrics = new Metrics(metricConfig, reporters, time);            //使用反射获取一个分区器            this.partitioner = config.getConfiguredInstance(ProducerConfig.PARTITIONER_CLASS_CONFIG, Partitioner.class);            //重要参数,这个参数的默认值是100ms            //代表的意思是Producer支持重试,两次重试之间的时间间隔            long retryBackoffMs = config.getLong(ProducerConfig.RETRY_BACKOFF_MS_CONFIG);            //设置key 和 value的序列化器            if (keySerializer == null) {                this.keySerializer = config.getConfiguredInstance(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,                        Serializer.class);                this.keySerializer.configure(config.originals(), true);            } else {                config.ignore(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG);                this.keySerializer = keySerializer;            }            if (valueSerializer == null) {                this.valueSerializer = config.getConfiguredInstance(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,                        Serializer.class);                this.valueSerializer.configure(config.originals(), false);            } else {                config.ignore(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG);                this.valueSerializer = valueSerializer;            }            // load interceptors and make sure they get clientId            userProvidedConfigs.put(ProducerConfig.CLIENT_ID_CONFIG, clientId);            List> interceptorList = (List) (new ProducerConfig(userProvidedConfigs)).getConfiguredInstances(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,                    ProducerInterceptor.class);            //设置拦截器,不过拦截器不是Kafka的重要的功能            this.interceptors = interceptorList.isEmpty() ? null : new ProducerInterceptors<>(interceptorList);            ClusterResourceListeners clusterResourceListeners = configureClusterResourceListeners(keySerializer, valueSerializer, interceptorList, reporters);            //重要参数,指的是Producer多久去更新一次Kafka的元数据            //默认是5分钟            this.metadata = new Metadata(retryBackoffMs, config.getLong(ProducerConfig.METADATA_MAX_AGE_CONFIG), true, clusterResourceListeners);            //重要参数,这个参数开发的时候一般我们要进行设置,它指的是一个请求最大多大            //我们也可以理解为代表的意思是一条消息最大允许多大,这个的默认值是1M            //1M这个值有些偏小。可以根据大家的公司的情况来,笔者在的公司            //这个值设置为10M            this.maxRequestSize = config.getInt(ProducerConfig.MAX_REQUEST_SIZE_CONFIG);            //重要参数            //消息在发送在之前都需要缓存起来,这地方指的就是缓存的内存大小            //默认是32M,一般情况32M可以满足需求,但是也可以根据公司的情况            //进行调优,不过现在先不着急,因为我估计大家对这个参数理解还是很蒙圈            //我们后面的文章会详细讲到关于这个参数的使用。            this.totalMemorySize = config.getLong(ProducerConfig.BUFFER_MEMORY_CONFIG);            //压缩类型,kafka支持多种压缩类型            this.compressionType = CompressionType.forName(config.getString(ProducerConfig.COMPRESSION_TYPE_CONFIG));            /* check for user defined settings.             * If the BLOCK_ON_BUFFER_FULL is set to true,we do not honor METADATA_FETCH_TIMEOUT_CONFIG.             * This should be removed with release 0.9 when the deprecated configs are removed.             */            if (userProvidedConfigs.containsKey(ProducerConfig.BLOCK_ON_BUFFER_FULL_CONFIG)) {                log.warn(ProducerConfig.BLOCK_ON_BUFFER_FULL_CONFIG + " config is deprecated and will be removed soon. " +                        "Please use " + ProducerConfig.MAX_BLOCK_MS_CONFIG);                boolean blockOnBufferFull = config.getBoolean(ProducerConfig.BLOCK_ON_BUFFER_FULL_CONFIG);                if (blockOnBufferFull) {                    this.maxBlockTimeMs = Long.MAX_VALUE;                } else if (userProvidedConfigs.containsKey(ProducerConfig.METADATA_FETCH_TIMEOUT_CONFIG)) {                    log.warn(ProducerConfig.METADATA_FETCH_TIMEOUT_CONFIG + " config is deprecated and will be removed soon. " +                            "Please use " + ProducerConfig.MAX_BLOCK_MS_CONFIG);                    this.maxBlockTimeMs = config.getLong(ProducerConfig.METADATA_FETCH_TIMEOUT_CONFIG);                } else {                    this.maxBlockTimeMs = config.getLong(ProducerConfig.MAX_BLOCK_MS_CONFIG);                }            } else if (userProvidedConfigs.containsKey(ProducerConfig.METADATA_FETCH_TIMEOUT_CONFIG)) {                log.warn(ProducerConfig.METADATA_FETCH_TIMEOUT_CONFIG + " config is deprecated and will be removed soon. " +                        "Please use " + ProducerConfig.MAX_BLOCK_MS_CONFIG);                this.maxBlockTimeMs = config.getLong(ProducerConfig.METADATA_FETCH_TIMEOUT_CONFIG);            } else {                this.maxBlockTimeMs = config.getLong(ProducerConfig.MAX_BLOCK_MS_CONFIG);            }            /* check for user defined settings.             * If the TIME_OUT config is set use that for request timeout.             * This should be removed with release 0.9             */            if (userProvidedConfigs.containsKey(ProducerConfig.TIMEOUT_CONFIG)) {                log.warn(ProducerConfig.TIMEOUT_CONFIG + " config is deprecated and will be removed soon. Please use " +                        ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG);                this.requestTimeoutMs = config.getInt(ProducerConfig.TIMEOUT_CONFIG);            } else {                this.requestTimeoutMs = config.getInt(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG);            }            //创建RecordAccumulator 对象            //对象里面传进去了我们刚刚分析的重要的参数            this.accumulator = new RecordAccumulator(config.getInt(ProducerConfig.BATCH_SIZE_CONFIG),                    this.totalMemorySize,                    this.compressionType,                    config.getLong(ProducerConfig.LINGER_MS_CONFIG),                    retryBackoffMs,                    metrics,                    time);            List addresses = ClientUtils.parseAndValidateAddresses(config.getList(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG));            //这段代码看起来想是去获取集群的数据            //但是其实代码里没有去获取到元数据,我们后面的文章去分析。            this.metadata.update(Cluster.bootstrap(addresses), time.milliseconds());            ChannelBuilder channelBuilder = ClientUtils.createChannelBuilder(config.values());            //重要,构建了一个NetworkdClient对象,这个是Kafka的网络核心,里面传了很多重要的参数            //MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION  每个连接允许最多有多个请求没收到响应,默认是5个。            //如下的几个参数是跟网络相关的参数,大家可以积累,以后在自己的项目中也可以这么设置            //CONNECTIONS_MAX_IDLE_MS_CONFIG 一个网络连接,最多空闲多久,就要把回收掉 默认是9分钟。这个也可以根据情况去设置            //RECONNECT_BACKOFF_MS_CONFIG 重试建立连接的时间间隔            //SEND_BUFFER_CONFIG soket发送缓冲区的大小,默认是128K            // RECEIVE_BUFFER_CONFIG  socket接收缓冲区的大小,默认是32K            //上面的有个参数需要引起大家的注意哈:            //MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION 对应的配置参数是max.in.flight.requests.per.connection            //这个参数指的是限制Producer客户端在单个连接上能够发送的未响应请求的个数,默认是5个            //大家要注意我们的Kafka是有重试机制,那么里面这儿可以允许放5个发送了但是未响应的请求            //假如里面的排在前面的消息返回响应说是失败了,然后要重新发送,那么这样的话,消息的顺序就打乱了            //所以如果这个值 大于 1 就会有 消息顺序被打乱的风险。            //特别关心消息顺序的同学要注意,需要把这个参数设置为1            NetworkClient client = new NetworkClient(                    new Selector(config.getLong(ProducerConfig.CONNECTIONS_MAX_IDLE_MS_CONFIG), this.metrics, time, "producer", channelBuilder),                    this.metadata,                    clientId,                    config.getInt(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION),                    config.getLong(ProducerConfig.RECONNECT_BACKOFF_MS_CONFIG),                    config.getInt(ProducerConfig.SEND_BUFFER_CONFIG),                    config.getInt(ProducerConfig.RECEIVE_BUFFER_CONFIG),                    this.requestTimeoutMs, time);            //重要,我们要注意,这个里面把上面创建的client对象传进去了。            //从缓冲区里获取数据,然后发送出去            //RETRIES_CONFIG对应的参数是retries ,代表的是重试的次数,一般开发的时候我们建议要设置这个值,默认是0,也就是不重试            //ACKS_CONFIG acks,这个的值由 0 1 all(-1) 三个数            //0 代表 生成者把消息发送给Broker以后,就立马返回。至于有没有写成功也不关心            //1 代表 生成者把消息发送给Broker以后,需要等leader partition写入成功以后 返回响应。            //1- 代表需要所有的partition都写入成功以后才返回响应。            //注:0 1 两个都会有可能造成数据丢失。0 的结果很好想,我只是发送到服务端,然后立马返回            //服务端那儿有可能就会写失败。写失败了数据就丢了。1 发送到服务端以后还要等leader partition写入            //成功以后才会返回响应,但是大家想如果这个时候leader partition在的broker要是宕机了            // 这条数据也就会丢失,其实这个情况是很有可能发生的,因为我们维护集群的时候,有些参数就是需要            //重启集群才能生效,重启服务的过程中,肯定就会导致部分leader partition 宕机。            //所以如果不允许丢数据的同学,可以重点关注这个参数。            this.sender = new Sender(client,                    this.metadata,                    this.accumulator,                    config.getInt(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION) == 1,                    config.getInt(ProducerConfig.MAX_REQUEST_SIZE_CONFIG),                    (short) parseAcks(config.getString(ProducerConfig.ACKS_CONFIG)),                    config.getInt(ProducerConfig.RETRIES_CONFIG),                    this.metrics,                    new SystemTime(),                    clientId,                    this.requestTimeoutMs);            String ioThreadName = "kafka-producer-network-thread" + (clientId.length() > 0 ? " | " + clientId : "");            //里面放进去了sender对象            //里面的代码设计也挺有意思,文章结尾的时候我们分析一下            this.ioThread = new KafkaThread(ioThreadName, this.sender, true);            //启动了这个线程,启动起来以后会有很多很多的事要干,这个我们就只能           //后面的文章再去分析了。            this.ioThread.start();            this.errors = this.metrics.sensor("errors");            config.logUnused();            AppInfoParser.registerAppInfo(JMX_PREFIX, clientId);            log.debug("Kafka producer started");        } catch (Throwable t) {            // call close methods if internal objects are already constructed            // this is to prevent resource leak. see KAFKA-2121            close(0, TimeUnit.MILLISECONDS, true);            // now propagate the exception            throw new KafkaException("Failed to construct kafka producer", t);        }    }
初始化的时候涉及到很多工作要做,里面的其余代码我们后面的文章再去分析。我们接下来简单看一下里面有一个有意思的代码。
 this.ioThread = new KafkaThread(ioThreadName, this.sender, true);            //启动了这个线程            this.ioThread.start();
代码点过去发现代码如下:
public class KafkaThread extends Thread {    private final Logger log = LoggerFactory.getLogger(getClass());    public KafkaThread(final String name, Runnable runnable, boolean daemon) {        super(runnable, name);        setDaemon(daemon);        setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {            public void uncaughtException(Thread t, Throwable e) {                log.error("Uncaught exception in " + name + ": ", e);            }        });    }}
KafkaThread 里面是对线程做了简单的封装,个人感觉代码这样设计会比较清晰,把线程和业务代码分离,KafkaThread对线程进行封装,业务逻辑在Sender里面。

e458ad10875fc39c4b9709de5a9499b9.png

-     总结     -

今天我们主要学习的是KafkaProducer初始化过程,初始化过程中涉及到了很多参数,而且了解这些参数对于我们理解kafka和项目开发都比较重要,大家要重点掌握。 下一篇文章我们分析,KafkaProducer初始化代码里面的这段代码:
 this.metadata.update(Cluster.bootstrap(addresses), time.milliseconds());
看起来像是去拉取元数据,但是到底有没有拉取元数据我们下次进行分析。 大家加油! 4e63786329ab3b3b388eb82e197033cb.png -   关注“大数据观止”   -

d65932ffe19efd43fa0237cc9960bffd.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值