KafkaProducer的实例化最终都是由核心构造方法实现的。
kafka的producer分为主线程和sender线程,主线程负责将消息放到缓存中,sender线程以守护线程方式运行轮询缓存进行发送消息。
在KafkaProducer实例化的同时,需要实例化缓存队列RecordAccumulator,缓存队列就是主线程存放消息的。并开启sender线程,
初始化Kafkaclient,就是为网络读写做准备的,Kafkaclient是网络读写的核心实现
核心构造方法非常复杂,这只是该方法的 一个梳理,其中每一个重要还会细细分析。
KafkaProducer(ProducerConfig config,
Serializer<K> keySerializer,
Serializer<V> valueSerializer,
Metadata metadata,
KafkaClient kafkaClient) {
try {
/**
* 读取用户自定义配置
*
* 注意: 到这里的时候,用户自定义配置和默认初始化配置已经
在ProducerConfig中初始化完成了,
具体查看在调用核心构造方法前的其他构造方法中初始化配置的的源码
*/
Map<String, Object> userProvidedConfigs = config.originals();
// 获取所有的配置,包括默认的初始化配置
this.producerConfig = config;
this.time = Time.SYSTEM;
/**
* 获取配置client.id配置,这个配置可以帮助追踪消息来源 这个clientId 发送消息的时候
,会被服务器记录,可用于追踪消息的原来 没有对clientId进行配置 将会自动生成 producer-1,
* producer-2,...格式的clientId
*/
String clientId = config.getString(ProducerConfig.CLIENT_ID_CONFIG);
if (clientId.length() <= 0)
clientId = "producer-" + PRODUCER_CLIENT_ID_SEQUENCE.getAndIncrement();
this.clientId = clientId;
/** kafka对事务的处理,暂时都没学习,关于事务,后面统一分析,补上 */
String transactionalId = userProvidedConfigs.
containsKey(ProducerConfig.TRANSACTIONAL_ID_CONFIG)
? (String)userProvidedConfigs.
get(ProducerConfig.TRANSACTIONAL_ID_CONFIG) : null;
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");
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);
reporters.add(new JmxReporter(JMX_PREFIX));
this.metrics = new Metrics(metricConfig, reporters, time);
ProducerMetrics metricsRegistry = new ProducerMetrics(this.metrics);
/**
* 读取使用的分区器 获取Partitioner的一个实例
*
* 分区器配置名properties.put("partitioner.class" ,"全类名")
如果配置了将会使用用户自定义的分区器,如果用户没有自定义分区器,这里将会使用 class
* org.apache.kafka.clients.producer.internals.DefaultPartitioner 分区器进行分区。 具体kafka是如何实现这个功能的点进去源码分析
* config.getConfiguredInstance(),就是读取配置中的一个,接口实例
*/
this.partitioner = config.getConfiguredInstance(ProducerConfig.PARTITIONER_CLASS_CONFIG, Partitioner.class);
// 读取retry.backoff.ms 配置,失败重试的间隔时间
long retryBackoffMs = config.getLong(ProducerConfig.RETRY_BACKOFF_MS_CONFIG);
if (keySerializer == null) {
/**
* 读取序列化器
*
* config.getConfiguredInstance
(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,Serializer.class))
* 就是通过配置值获取一个 Serializer的接口实例, ensureExtended就是对这个实例进行增强,
*/
this.keySerializer = ensureExtended(
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 = ensureExtended(keySerializer);
}
if (valueSerializer == null) {
// 读取value序列化器
this.valueSerializer = ensureExtended(
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 = ensureExtended(valueSerializer);
}
/** 用户自定义配置中加入client.id 配置 获取配置的拦截器,可能配置多个,采用list */
userProvidedConfigs.put(ProducerConfig.CLIENT_ID_CONFIG, clientId);
List<ProducerInterceptor<K, V>> interceptorList = (List)(new ProducerConfig(userProvidedConfigs, false))
.getConfiguredInstances(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, ProducerInterceptor.class);
this.interceptors = new ProducerInterceptors<>(interceptorList);
ClusterResourceListeners clusterResourceListeners =
configureClusterResourceListeners(keySerializer, valueSerializer, interceptorList, reporters);
// 读取max.request.size配置值
this.maxRequestSize = config.getInt(ProducerConfig.MAX_REQUEST_SIZE_CONFIG);
// 读取buffer.memory配置值
this.totalMemorySize = config.getLong(ProducerConfig.BUFFER_MEMORY_CONFIG);
// 读取压缩类型compression.type 配置
this.compressionType = CompressionType.forName(config.getString(ProducerConfig.COMPRESSION_TYPE_CONFIG));
// 读取最大阻塞时间配置 max.block.ms
this.maxBlockTimeMs = config.getLong(ProducerConfig.MAX_BLOCK_MS_CONFIG);
/**
*
* 读取request.timeout.ms 请求超时时间 等待响应的超时时间
*
* 超时后会进行重试,这个值的设置应该大于 replica.lag.time.max.ms 减少由于不必要的生产者重试
*/
this.requestTimeoutMs = config.getInt(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG);
this.transactionManager = configureTransactionState(config, logContext, log);
/**
*
* 重试次数
*
读取retries 配置。当有事务的时候,如果用户没有配置这个值,会一直重试
* 如果允许重试那么考虑消息的顺序问题
* 可以结合max.in.flight.requests.per.connection的配置值进行顺序保证,但该配置会引起吞吐量问题/li>
*
*/
int retries = configureRetries(config, transactionManager != null, log);
/**
*
* - 读取max.in.flight.requests.per.connection 配置
* - 限制客户端在单个连接上能够发送的未响应请求的个数
* - 默认值是5,也就是5个 请求未返回,producer将会被阻塞,
* - 使用了事务,该值不允许大于 5
* - max.in.flight.requests.per.connection设置1 就表明 :只能等上个消息得到response 之后,才能发生下一个请求
* 这样可以保证消息的顺序性,但是吞吐量会下降很多
*
*/
int maxInflightRequests = configureInflightRequests(config, transactionManager != null);
/**
*
* - 获取ack配置: producer收到的ack的配置。默认值是1,有四种配置 in("all", "-1", "0", "1")
*
*
* - 不需要服务器的ack,producer 将消息发送到缓冲区就会认为完成了消息发送
* - 也就是producer感知不到发送失败,会认为所有发送的消息都是被服务器接受
* - 配置的retries 也不会有什么意义,
* - 每个记录返回的偏移量将始终设置为-1
*
*
* - 在集群环境下,当服务端的leader节点收到了请求,写入日志文件,就会立即响应, 也就是收到leader ack 认为成功,
* - 这种情况,除非leader ack后挂了,其他节点数据也没有被同步过去这种情况存在数据丢失
*
**
* - all的配置会在这里转化为 -1
* - 就是收到所有的副本节点数据同步成功,ack,不会存在数据丢失,可靠性投递的最高保障 但是性能也是最低的
*
*
*/
short acks = configureAcks(config, transactionManager != null, log);
this.apiVersions = new ApiVersions();
// 初始化accumulator
this.accumulator = new RecordAccumulator(logContext, config.getInt(ProducerConfig.BATCH_SIZE_CONFIG),
this.totalMemorySize, this.compressionType, config.getLong(ProducerConfig.LINGER_MS_CONFIG),
retryBackoffMs, metrics, time, apiVersions, transactionManager);
/** 获取服务器地址列表 */
List<InetSocketAddress> addresses =
ClientUtils.parseAndValidateAddresses(config.getList(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG));
if (metadata != null) {
this.metadata = metadata;
} else {
// 初始化创建metadata实例
this.metadata = new Metadata(retryBackoffMs, config.getLong(ProducerConfig.METADATA_MAX_AGE_CONFIG),
true, true, clusterResourceListeners);
this.metadata.update(Cluster.bootstrap(addresses), Collections.<String>emptySet(), time.milliseconds());
}
/**
* 创建通道构建器, ChannelBuilder是一认证接口,分别有具体的实现类
*
*
* SaslChannelBuilder
* SslChannelBuilder
* PlaintextChannelBuilder
*
*
* 这里 会根据配置不同的安全协议,创建一个相应类型的ChannelBuilder实例
*/
ChannelBuilder channelBuilder = ClientUtils.createChannelBuilder(config);
//监控数据
Sensor throttleTimeSensor = Sender.throttleTimeSensor(metricsRegistry.senderMetrics);
/**
* 获取kafka的client NetworkClient
*
* NetworkClient是kafka客户端的网络接口层,实现了接口KafkaClient,封装了Java NIO对网络的调用
*
* producer 与broker 建立的网络连接这块底层知识 比较难, 暂不深入分析,后期会细节突破
*/
// 获取一个selector connections.max.idle.ms
Selector selector = new Selector(config.getLong(ProducerConfig.CONNECTIONS_MAX_IDLE_MS_CONFIG),
this.metrics, time, "producer", channelBuilder, logContext);
/**
* 实例化KafkaClient ,一下都是KafkaClient的配置
*
* reconnect.backoff.ms
*
* reconnect.backoff.max.ms
*
* send.buffer.bytes
*
* receive.buffer.bytes
*
* request.timeout.ms
*
* max.in.flight.requests.per.connection
*
*/
KafkaClient client = kafkaClient != null ? kafkaClient :
new NetworkClient(
selector,
this.metadata, clientId, maxInflightRequests,
config.getLong(ProducerConfig.RECONNECT_BACKOFF_MS_CONFIG),
config.getLong(ProducerConfig.RECONNECT_BACKOFF_MAX_MS_CONFIG),
config.getInt(ProducerConfig.SEND_BUFFER_CONFIG),
config.getInt(ProducerConfig.RECEIVE_BUFFER_CONFIG),
this.requestTimeoutMs,
time,
true,
apiVersions,
throttleTimeSensor,
logContext
);
/**
* 初始化sender线程
*
* 这个线程中将从broker获取metadata 信息
* 从accumulator中获取消息并发送给broker
*
* max.request.size
* request.timeout.ms
* retry.backoff.ms
*/
this.sender = new Sender(logContext, client, this.metadata, this.accumulator, maxInflightRequests == 1,
config.getInt(ProducerConfig.MAX_REQUEST_SIZE_CONFIG), acks, retries, metricsRegistry.senderMetrics,
Time.SYSTEM, this.requestTimeoutMs, config.getLong(ProducerConfig.RETRY_BACKOFF_MS_CONFIG),
this.transactionManager, apiVersions);
String ioThreadName = NETWORK_THREAD_PREFIX + " | " + clientId;
/**
* 开启sender线程,并设置为主线程的守护线程
* 这里设置成守护线程有很多好处。我们只需要维护producer线程就可以了,不需要另外维护
* sender线程,在一些后台功能的线程中守护线程的应用场景很多:
* 比如:JVM 的GC线程,比如心跳管理线程。
*/
this.ioThread = new KafkaThread(ioThreadName, this.sender, true);
this.ioThread.start();
this.errors = this.metrics.sensor("errors");
config.logUnused();
AppInfoParser.registerAppInfo(JMX_PREFIX, clientId, metrics);
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);
}
}
2.2 sender线程构造和守护线程配置:
public KafkaThread(final String name, Runnable runnable, boolean daemon) {
super(runnable, name);
configureThread(name, daemon);
}
private void configureThread(final String name, boolean daemon) {
setDaemon(daemon);
setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
log.error("Uncaught exception in thread '{}':", name, e);
}
});
}