【Kafka学习】源码系列(二)发送消息-实例化KafkaProducer

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

上一节我们将阅读前准备做完了,这一节我们就开始来看发送消息-实例化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、消息发送流程(暂不用细看)

在这里插入图片描述

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
在这里插入图片描述
acks如果不为 -1(这个参数的值默认为1),那么也会报出 ConfigException
在这里插入图片描述

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Kafka 中,Producer 是用来发送消息Kafka 集群的组件。在本篇文章中,我们将介绍如何使用 KafkaJava 客户端 API 来编写一个简单的 Producer。 1. 引入 Kafka 依赖 首先,需要在 Maven 或 Gradle 构建中引入 Kafka 客户端依赖: ```xml <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>2.8.0</version> </dependency> ``` 2. 创建 Producer 实例 接下来,在 Java 代码中创建一个 KafkaProducer 实例: ```java Properties props = new Properties(); props.put("bootstrap.servers", "localhost:9092"); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); Producer<String, String> producer = new KafkaProducer<>(props); ``` 其中,bootstrap.servers 是必须设置的属性,用于指定 Kafka 集群中至少一个 Broker 的地址。key.serializer 和 value.serializer 用于指定消息的键和值的序列化器。这里我们使用的是 StringSerializer,也可以使用其他序列化器实现自定义序列化逻辑。 3. 发送消息 一旦创建了 KafkaProducer 实例,就可以使用它来发送消息到指定的 Kafka 主题: ```java ProducerRecord<String, String> record = new ProducerRecord<>("test-topic", "key", "value"); producer.send(record); ``` 这里的 ProducerRecord 构造函数中,第一个参数是要发送消息的主题名称,第个参数是消息的键,第三个参数是消息的值。send() 方法用于将 ProducerRecord 发送Kafka 集群。 4. 关闭 Producer 在使用完 Producer 后,需要关闭它以释放资源: ```java producer.close(); ``` 完整代码示例: ```java import org.apache.kafka.clients.producer.*; import java.util.Properties; public class KafkaProducerExample { public static void main(String[] args) { Properties props = new Properties(); props.put("bootstrap.servers", "localhost:9092"); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); Producer<String, String> producer = new KafkaProducer<>(props); ProducerRecord<String, String> record = new ProducerRecord<>("test-topic", "key", "value"); producer.send(record); producer.close(); } } ``` 这就是一个简单的 Kafka Producer 的使用示例。在实际应用中,还可以根据需要设置其他属性,例如消息的分区策略、消息的压缩方式等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张小帅和刘美美

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值