前言:读源码将,不能一行一行分析,需要带着目的去读,最后重要的步骤串联起来

上一节我们将阅读前准备做完了,这一节我们就开始来看发送消息-实例化KafkaProducer时都做了什么?

1、kafka.examples.Producer

作为Debug整个生产者的入口,它其实也就是一个线程类

public class Producer extends Thread

整个类我们关注的重点在 new KafkaProducer<>(props)

public Producer(final String topic,
                   final Boolean isAsync,
                   final String transactionalId,
                   final boolean enableIdempotency,
                   final int numRecords,
                   final int transactionTimeoutMs,
                   final CountDownLatch latch) {
       Properties props = new Properties();
       // 服务器地址 bootstrap.servers => localhost:9092
       props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, KafkaProperties.KAFKA_SERVER_URL + ":" + KafkaProperties.KAFKA_SERVER_PORT);
       // 客户端ID(每个客户端唯一) client.id => DemoProducer
       props.put(ProducerConfig.CLIENT_ID_CONFIG, "DemoProducer");
       // 消息的 Key 的序列化方式
       props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class.getName());
       // 消息的 Value 的序列化方式
       props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
       // 发送消息超时时间
       if (transactionTimeoutMs > 0) {
           props.put(ProducerConfig.TRANSACTION_TIMEOUT_CONFIG, transactionTimeoutMs);
       }
       // 事务ID
       if (transactionalId != null) {
           props.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, transactionalId);
       }
       // 是否启动幂等性 false
       props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, enableIdempotency);

       producer = new KafkaProducer<>(props);
       this.topic = topic;
       this.isAsync = isAsync;
       this.numRecords = numRecords;
       this.latch = latch;
   }
2、org.apache.kafka.clients.producer.KafkaProducer
1、类成员变量
/** 生产者唯一标识(对应 client.id 属性配置 ) */
    private final String clientId;
    // Visible for testing
    final Metrics metrics;
    /** 分区选择器(对应 partitioner.class 属性配置),如果未明确指定分区,则基于一定的策略为消息选择合适的分区 */
    private final Partitioner partitioner;
    /** 消息的最大长度(对应 max.request.size 配置,包含消息头、序列化之后的 key 和 value) */
    private final int maxRequestSize;
    /** 发送单条消息的缓冲区大小(对应 buffer.memory 配置) */
    private final long totalMemorySize;
    /** kafka 集群元数据 */
    private final ProducerMetadata metadata;
    /** 消息收集器,用于收集并缓存消息,等待 Sender 线程的发送 */
    private final RecordAccumulator accumulator;
    /** 消息发送线程对象 */
    private final Sender sender;
    /** 消息发送线程 */
    private final Thread ioThread;
    /** 压缩算法(对应 compression.type 配置) */
    private final CompressionType compressionType;
    private final Sensor errors;
    /** 时间戳工具 */
    private final Time time;
    /** key 序列化器(对应 key.serializer 配置) */
    private final Serializer<K> keySerializer;
    /** value 序列化器(对应 value.serializer 配置) */
    private final Serializer<V> valueSerializer;
    /** 封装配置信息 */
    private final ProducerConfig producerConfig;
    /** 等待更新 kafka 集群元数据的最大时长 */
    private final long maxBlockTimeMs;
    /** 发送拦截器(对应 interceptor.classes 配置),用于待发送的消息进行拦截并修改,也可以对 ACK 响应进行拦截处理 */
    private final ProducerInterceptors<K, V> interceptors;
    /** 版本 */
    private final ApiVersions apiVersions;
    /** 事务 */
    private final TransactionManager transactionManager;
2、消息发送流程(暂不用细看)

javakafka发送消息 kafka 发送消息例子_java

3、构造方法分析

整个带参构造方法很长,可以将其分为以下几步:
1.配置一些用户自定义的参数
… 设置一些默认配置,详见下面代码部分…
2.Metric监控信息
3.设置分区器,默认使用的DefaultPartitioner
… 设置一些默认配置,详见下面代码部分…
4.设置序列化器
5.设置拦截器
… 设置一些默认配置,详见下面代码部分…
6.创建了一个核心的组件RecordAccumulator,缓存消息
7.获取 kafka 集群主机列表
8.设置metadata元数据
9.初始化Sender线程
10.初始化KafkaThread(守护线程)
11.启动KafkaThread
… 设置一些默认配置,详见下面代码部分…

为了大家阅读体验,这里只贴该类构造器方法

KafkaProducer(Map<String, Object> configs,
                  Serializer<K> keySerializer,
                  Serializer<V> valueSerializer,
                  ProducerMetadata metadata,
                  KafkaClient kafkaClient,
                  ProducerInterceptors<K, V> interceptors,
                  Time time) {
        ProducerConfig config = new ProducerConfig(ProducerConfig.appendSerializerToConfig(configs, keySerializer,
                valueSerializer));
        try {
            // 1.配置一些用户自定义的参数
            Map<String, Object> userProvidedConfigs = config.originals();
            this.producerConfig = config;
            this.time = time;

            // kafka在0.11之后支持事务
            String transactionalId = (String) userProvidedConfigs.get(ProducerConfig.TRANSACTIONAL_ID_CONFIG);
            // 配置client id编号,一般不配置
            this.clientId = config.getString(ProducerConfig.CLIENT_ID_CONFIG);

            LogContext logContext;
            if (transactionalId == null)
                logContext = new LogContext(String.format("[Producer clientId=%s] ", clientId));
            else
                logContext = new LogContext(String.format("[Producer clientId=%s, transactionalId=%s] ", clientId, transactionalId));
            log = logContext.logger(KafkaProducer.class);
            log.trace("Starting the Kafka producer");

            // metric是一些监控信息,我们一般分析源码的时候 不需要去关心
            Map<String, String> metricTags = Collections.singletonMap("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)
                    .recordLevel(Sensor.RecordingLevel.forName(config.getString(ProducerConfig.METRICS_RECORDING_LEVEL_CONFIG)))
                    .tags(metricTags);
            List<MetricsReporter> reporters = config.getConfiguredInstances(ProducerConfig.METRIC_REPORTER_CLASSES_CONFIG,
                    MetricsReporter.class,
                    Collections.singletonMap(ProducerConfig.CLIENT_ID_CONFIG, clientId));
            JmxReporter jmxReporter = new JmxReporter();
            jmxReporter.configure(userProvidedConfigs);
            reporters.add(jmxReporter);
            MetricsContext metricsContext = new KafkaMetricsContext(JMX_PREFIX,
                    config.originalsWithPrefix(CommonClientConfigs.METRICS_CONTEXT_PREFIX));
            this.metrics = new Metrics(metricConfig, reporters, time, metricsContext);

            // 设置分区器,默认使用的DefaultPartitioner
            this.partitioner = config.getConfiguredInstance(ProducerConfig.PARTITIONER_CLASS_CONFIG, Partitioner.class);
            // 发送失败后重试时间间隔 retry.backoff.ms 默认100ms
            long retryBackoffMs = config.getLong(ProducerConfig.RETRY_BACKOFF_MS_CONFIG);
            // 设置序列化器 也支持自定义序列化器
            if (keySerializer == null) {
                this.keySerializer = config.getConfiguredInstance(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
                                                                                         Serializer.class);
                this.keySerializer.configure(config.originals(Collections.singletonMap(ProducerConfig.CLIENT_ID_CONFIG, clientId)), 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(Collections.singletonMap(ProducerConfig.CLIENT_ID_CONFIG, clientId)), 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);
            ProducerConfig configWithClientId = new ProducerConfig(userProvidedConfigs, false);

            // 设置拦截器   类似于一个过滤器
            List<ProducerInterceptor<K, V>> interceptorList = (List) configWithClientId.getConfiguredInstances(
                    ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, ProducerInterceptor.class);
            if (interceptors != null)
                this.interceptors = interceptors;
            else
                this.interceptors = new ProducerInterceptors<>(interceptorList);
            ClusterResourceListeners clusterResourceListeners = configureClusterResourceListeners(keySerializer,
                    valueSerializer, interceptorList, reporters);

            // max.request.size 生产者往服务端发送消息的时候,规定一条消息最大多大?
            // 如果你超过了这个规定消息的大小,你的消息就不能发送过去。
            // 默认是1M,这个值偏小,在生产环境中,我们需要修改这个值。
            // 经验值是10M。但是大家也可以根据自己公司的情况来。
            this.maxRequestSize = config.getInt(ProducerConfig.MAX_REQUEST_SIZE_CONFIG);
            // 指定缓冲区缓存大小
            this.totalMemorySize = config.getLong(ProducerConfig.BUFFER_MEMORY_CONFIG);
            // kafka是支持压缩数据的,这儿设置压缩格式。
            // 提高你的系统的吞吐量,你可以设置压缩格式。
            // 一次发送出去的消息就更多。生产者这儿会消耗更多的cpu.
            this.compressionType = CompressionType.forName(config.getString(ProducerConfig.COMPRESSION_TYPE_CONFIG));

            // buffer满了或者metadata获取不到(比如leader挂了), 或者序列化没完成分区函数没计算完等等情况下的最大阻塞时间,默认60000ms (60秒)
            this.maxBlockTimeMs = config.getLong(ProducerConfig.MAX_BLOCK_MS_CONFIG);
            // 发送消息上报成功或失败的最大时间
            int deliveryTimeoutMs = configureDeliveryTimeout(config, log);

            this.apiVersions = new ApiVersions();
            this.transactionManager = configureTransactionState(config, logContext);
            // 创建了一个核心的组件RecordAccumulator,缓存消息
            // 创建消息收集器,它会将为消息申请内存、消息压缩(如果需要)并压如到待发送消息缓存队列中
            this.accumulator = new RecordAccumulator(logContext,
                    // 每个批次的大小16KB
                    config.getInt(ProducerConfig.BATCH_SIZE_CONFIG),
                    // 压缩
                    this.compressionType,
                    // 没有达到批次最大限制时 延迟发送的时间 默认0ms
                    lingerMs(config),
                    retryBackoffMs,
                    deliveryTimeoutMs,
                    metrics,
                    PRODUCER_METRIC_GROUP_NAME,
                    time,
                    apiVersions,
                    transactionManager,
                    // 初始化32M的缓冲区
                    new BufferPool(this.totalMemorySize, config.getInt(ProducerConfig.BATCH_SIZE_CONFIG), metrics, time, PRODUCER_METRIC_GROUP_NAME));

            // 获取 kafka 集群主机列表
            List<InetSocketAddress> addresses = ClientUtils.parseAndValidateAddresses(
                    config.getList(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG),
                    config.getString(ProducerConfig.CLIENT_DNS_LOOKUP_CONFIG));
            if (metadata != null) {
                // 元数据存在时就直接赋值
                this.metadata = metadata;
            } else {
                // 不存在就创建一个Metadata对象,主要参数都是超时时间
                this.metadata = new ProducerMetadata(retryBackoffMs,
                        config.getLong(ProducerConfig.METADATA_MAX_AGE_CONFIG),
                        config.getLong(ProducerConfig.METADATA_MAX_IDLE_CONFIG),
                        logContext,
                        clusterResourceListeners,
                        Time.SYSTEM);
                // 更新元数据
                this.metadata.bootstrap(addresses);
            }
            this.errors = this.metrics.sensor("errors");

            // 初始化Sender线程
            this.sender = newSender(logContext, kafkaClient, this.metadata);
            String ioThreadName = NETWORK_THREAD_PREFIX + " | " + clientId;

            // 创建了一个线程(守护线程 setDaemon(daemon)),然后里面传进去了一个sender对象。
            // 内部包含了线程代码
            // 实现把业务代码与线程代码进行隔离,这样会显得清晰点
            this.ioThread = new KafkaThread(ioThreadName, this.sender, true);

            // 启动线程
            this.ioThread.start();
            // WARN 记录用户自定义了但是未使用到的配置
            config.logUnused();

            // 注册一个监控和日志插件
            AppInfoParser.registerAppInfo(JMX_PREFIX, clientId, metrics, time.milliseconds());
            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(Duration.ofMillis(0), true);
            // now propagate the exception
            throw new KafkaException("Failed to construct kafka producer", t);
        }
    }
3、org.apache.kafka.clients.producer.KafkaProducer#newSender

上面我们讲了KafkaProducer构造方法,其中最重要的还是Sender线程

public class Sender implements Runnable

所以我们接着看newSender方法

Sender newSender(LogContext logContext, KafkaClient kafkaClient, ProducerMetadata metadata) {

        // 允许发送失败的条数, 幂等和顺序: max.in.flight.requests.per.connection = 1
        // 需要保证 max.in.flight.requests.per.connection 参数的值不能大于5 否则会报出 ConfigException:
        int maxInflightRequests = configureInflightRequests(producerConfig);
        // 超时配置 默认30s, 自定义配置至少需要大于10s. ReplicaLagTimeMaxMs = 10000L
        // 这应该大于 <code>replica.lag.time.max.ms</code>(代理配置)以减少由于不必要的生产者重试而导致消息重复的可能性。
        // replica.lag.time.max.ms: Kafka判断ISR中的follower和leader同步的根据是Broker的配置参数 replica.lag.time.max.ms 默认是10s
        // 就是说如果follower落后leader 10s,则认为他失效了,会踢出ISR集合。
        int requestTimeoutMs = producerConfig.getInt(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG);
        // KafkaChannel负责基于socket的连接,认证,数据读取发送。
        // 它包含TransportLayer和Authenticator两个部分。TransportLayer负责数据交互,Authenticator负责安全验证
        ChannelBuilder channelBuilder = ClientUtils.createChannelBuilder(producerConfig, time, logContext);
        
        // 监控 看源码的时候可忽略
        ProducerMetrics metricsRegistry = new ProducerMetrics(this.metrics);
        Sensor throttleTimeSensor = Sender.throttleTimeSensor(metricsRegistry.senderMetrics);

        // 初始化了一个重要的网络管理的组件
        // NetworkClient 是后面Sender线程和服务端进行网络I/O的核心类
        KafkaClient client = kafkaClient != null ? kafkaClient : new NetworkClient(
                // connections.max.idle.ms: 默认值是9分钟,一个网络连接最多空闲多久,超过这个空闲时间,就关闭这个网络连接。
                // 可以设置为-1 ,不回收连接,减少频繁的创建和销毁连接
                new Selector(producerConfig.getLong(ProducerConfig.CONNECTIONS_MAX_IDLE_MS_CONFIG),
                        this.metrics, time, "producer", channelBuilder, logContext),
                metadata,
                clientId,
                // max.in.flight.requests.per.connection:默认是5
                // 发送数据的时候,其实是有多个网络连接。每个网络连接可以忍受 producer端发送给broker消息后,消息没有响应的个数。
                // 因为kafka有重试机制,所以有可能会造成数据乱序,如果想要保证有序,这个值要把设置为1.
                maxInflightRequests,
                // reconnect.backoff.ms:socket尝试重新连接指定主机的时间间隔
                producerConfig.getLong(ProducerConfig.RECONNECT_BACKOFF_MS_CONFIG),
                producerConfig.getLong(ProducerConfig.RECONNECT_BACKOFF_MAX_MS_CONFIG),
                // send.buffer.bytes:socket发送数据的缓冲区的大小,默认值是128K
                producerConfig.getInt(ProducerConfig.SEND_BUFFER_CONFIG),
                // receive.buffer.bytes:socket接受数据的缓冲区的大小,默认值是32K
                producerConfig.getInt(ProducerConfig.RECEIVE_BUFFER_CONFIG),
                requestTimeoutMs,
                producerConfig.getLong(ProducerConfig.SOCKET_CONNECTION_SETUP_TIMEOUT_MS_CONFIG),
                producerConfig.getLong(ProducerConfig.SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS_CONFIG),
                ClientDnsLookup.forConfig(producerConfig.getString(ProducerConfig.CLIENT_DNS_LOOKUP_CONFIG)),
                time,
                true,
                apiVersions,
                throttleTimeSensor,
                logContext);

        // 获取acks参数 默认-1 如果用户还显式地指定了 acks 参数,那么还需要保证这个参数的值为 -1(all)
        // 如果不为 -1(这个参数的值默认为1),那么也会报出 ConfigException:
        short acks = configureAcks(producerConfig, log);
        /**
         *  RETRIES_CONFIG -> retries: 重试的次数,默认为0,表示不重试
         *  acks:
         *     0:
         *      producer发送数据到broker后,就完了,没有返回值,不管写成功还是写失败都不管了。
         *       如果是这种请求数据丢失的概率是非常之高,一般不建议设置为0
         *
         *   1:
         *      producer发送数据到broker后,数据成功写入leader partition以后返回响应。
         *      数据 -> broker(leader partition)
         *      也有丢失数据的风险
         *
         *  -1或者all:
         *       producer发送数据到broker后,数据要写入到leader partition里面,并且数据同步到所有的
         *       follower partition里面以后,才返回响应。
         */
        return new Sender(logContext,
                client,
                metadata,
                this.accumulator,
                maxInflightRequests == 1,
                producerConfig.getInt(ProducerConfig.MAX_REQUEST_SIZE_CONFIG),
                acks,
                producerConfig.getInt(ProducerConfig.RETRIES_CONFIG),
                metricsRegistry.senderMetrics,
                time,
                requestTimeoutMs,
                producerConfig.getLong(ProducerConfig.RETRY_BACKOFF_MS_CONFIG),
                this.transactionManager,
                apiVersions);
    }

PS:1、其中有些细节 我就不一一讲了,比如:

需要保证 max.in.flight.requests.per.connection 参数的值不能大于5 否则会报出 ConfigException

javakafka发送消息 kafka 发送消息例子_序列化_02


acks如果不为 -1(这个参数的值默认为1),那么也会报出 ConfigException

javakafka发送消息 kafka 发送消息例子_序列化_03

2、很多配置需要大家自己看代码org.apache.kafka.clients.producer.ProducerConfig,就没有挨着讲了,因为这个类中都给了对应的解释以及默认值了。

解释:

javakafka发送消息 kafka 发送消息例子_kafka_04


默认值:

javakafka发送消息 kafka 发送消息例子_序列化_05