Kafka Practice (12): Explication détaillée et débogage du code source du producteur (KafkaProducer)

Cette section analyse le code source du producteur pour vous familiariser avec le processus d'envoi des données du producteur. En ce qui concerne l'utilisation d'Idea pour compiler et déboguer le code source kafka, vous pouvez lire le blog précédent: Compilation et débogage du code source kafka local. La version analysée cette fois est kafka-1.0.0;

1. Préparation de l'environnement

L'exécution de zk (version 3.4.12) dans l'environnement win est déjà terminée et le code source de kafka a été compilé. Reportez-vous à: Compilez et déboguez le code source local de kafka , ajoutez une configuration pour le créer dans le run- -> debug -> du sujet d'idée: yzg (3 partitions et 1 sauvegarde), démarrage local et effet de fonctionnement:

Deuxièmement, le processus de production et l'analyse de la classe KafkaProducer

KafkaProducer se trouve dans le package org.apache.kafka.clients.producer (tout le code source concernant le producteur est dans ce package). Lorsque vous utilisez la classe de producteur, vous devez instancier KafkaProducer, qui définit le mécanisme d'envoi. KafkaProducer est une sous-classe de Producer. L'instance de producteur (producteur) termine la transmission des données en instanciant la classe KafkaProducer et en appelant sa méthode send (), comme suit:

① Passez d'abord un intercepteur;

② Appelez la méthode KafkaProducer.send (). DoSend (), doSend sérialise d'abord la clé et la valeur en fonction du sérialiseur spécifié;

③ Une fois que la fonction partition () a obtenu les données et les données sérialisées, partitionnez les données;

④ Appelez la méthode RecordAccumulator.append () et lancez les données traitées dans l'attribut de classe RecordAppendResult de RecordAccumulator (objet cache);

⑤ La méthode RecordAccumulator.append () met d'abord les données en file d'attente et les place dans l'objet Deque, qui contient plusieurs ProducerBatch;

⑥ Une fois le processus ci-dessus terminé, appelez this.sender.wakeup () pour réveiller le thread de l'expéditeur, et le thread enverra des données.

 Le constructeur de la classe KafkaProducer est le suivant. Une fois que l'instance de producteur est passée dans la configuration du cluster et le sérialiseur (le nom de la rubrique n'a pas encore été transmis), KafkaProducer est instancié pour terminer l'instanciation de toutes les propriétés associées. Les objets principaux sont

private KafkaProducer(ProducerConfig config, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
    try {
        Map<String, Object> userProvidedConfigs = config.originals();
        this.producerConfig = config;
        this.time = Time.SYSTEM;
        String clientId = config.getString(ProducerConfig.CLIENT_ID_CONFIG);
        if (clientId.length() <= 0)
            clientId = "producer-" + PRODUCER_CLIENT_ID_SEQUENCE.getAndIncrement();
        this.clientId = clientId;

        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);
        this.partitioner = config.getConfiguredInstance(ProducerConfig.PARTITIONER_CLASS_CONFIG, Partitioner.class);
        long retryBackoffMs = config.getLong(ProducerConfig.RETRY_BACKOFF_MS_CONFIG);
        if (keySerializer == null) {
            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) {
            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);
        }

        // load interceptors and make sure they get clientId
        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 = interceptorList.isEmpty() ? null : new ProducerInterceptors<>(interceptorList);
        ClusterResourceListeners clusterResourceListeners = configureClusterResourceListeners(keySerializer, valueSerializer, interceptorList, reporters);
        this.metadata = new Metadata(retryBackoffMs, config.getLong(ProducerConfig.METADATA_MAX_AGE_CONFIG),
                true, true, clusterResourceListeners);
        this.maxRequestSize = config.getInt(ProducerConfig.MAX_REQUEST_SIZE_CONFIG);
        this.totalMemorySize = config.getLong(ProducerConfig.BUFFER_MEMORY_CONFIG);
        this.compressionType = CompressionType.forName(config.getString(ProducerConfig.COMPRESSION_TYPE_CONFIG));

        this.maxBlockTimeMs = config.getLong(ProducerConfig.MAX_BLOCK_MS_CONFIG);
        this.requestTimeoutMs = config.getInt(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG);
        this.transactionManager = configureTransactionState(config, logContext, log);
        int retries = configureRetries(config, transactionManager != null, log);
        int maxInflightRequests = configureInflightRequests(config, transactionManager != null);
        short acks = configureAcks(config, transactionManager != null, log);

        this.apiVersions = new ApiVersions();
        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));
        this.metadata.update(Cluster.bootstrap(addresses), Collections.<String>emptySet(), time.milliseconds());
        ChannelBuilder channelBuilder = ClientUtils.createChannelBuilder(config);
        Sensor throttleTimeSensor = Sender.throttleTimeSensor(metricsRegistry.senderMetrics);
        NetworkClient client = new NetworkClient(
                new Selector(config.getLong(ProducerConfig.CONNECTIONS_MAX_IDLE_MS_CONFIG),
                        this.metrics, time, "producer", channelBuilder, logContext),
                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);
        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;
        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);
    }
}

1. Prétraitement des données (intercepteur, sérialisation, partitionneur, cache)

① Le producteur instancie KafkaProducer après avoir récupéré les accessoires, puis appelle send () dans plusieurs threads. Si KafkaProducer ne définit pas d'intercepteurs (instances de la classe ProducerInterceptors), l'enregistrement de données reste inchangé. Si les intercepteurs sont définis, les ProducerInterceptors de l'intercepteur s'appelle La méthode .onSend () filtre l'enregistrement de données, cet intercepteur est personnalisé, il n'y a pas de méthode de filtrage dans le code source;

public Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback) {
    // intercept the record, which can be potentially modified; this method does not throw exceptions
    ProducerRecord<K, V> interceptedRecord = this.interceptors == null ? record : this.interceptors.onSend(record);
    return doSend(interceptedRecord, callback);
}

② Ensuite, appelez la méthode doSend. La méthode partition () est d'abord appelée dans le corps de la méthode. Les paramètres d'entrée sont les données d'enregistrement d'origine et les résultats de la sérialisation des clés et des valeurs;

// 确认topic和集群信息正确
ClusterAndWaitTime clusterAndWaitTime = waitOnMetadata(record.topic(), record.partition(), maxBlockTimeMs);
Cluster cluster = clusterAndWaitTime.cluster;
// 分区器
int partition = partition(record, serializedKey, serializedValue, cluster);
tp = new TopicPartition(record.topic(), partition);
//如果没有指定分区,就使用内置的分区器partitioner.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);
}

③ Ensuite, appelez la méthode RecordAccumulator.append () de l'objet RecordAccumulator détenu par la classe KafkaProducer et renvoyez l'objet RecordAppendResult;

RecordAccumulator.RecordAppendResult result = accumulator.append(tp, timestamp, serializedKey,
        serializedValue, headers, interceptCallback, remainingWaitMs);
// append方法的实现,返回RecordAppendResult 对象
public RecordAppendResult append(TopicPartition tp,
                                 long timestamp,
                                 byte[] key,
                                 byte[] value,
                                 Header[] headers,
                                 Callback callback,
                                 long maxTimeToBlock) throws InterruptedException {
    appendsInProgress.incrementAndGet();
    ByteBuffer buffer = null;
    if (headers == null) headers = Record.EMPTY_HEADERS;
    try {
        Deque<ProducerBatch> dq = getOrCreateDeque(tp);
        synchronized (dq) {
            if (closed)
                throw new IllegalStateException("Cannot send after the producer is closed.");
            RecordAppendResult appendResult = tryAppend(timestamp, key, value, headers, callback, dq);
            if (appendResult != null)
                return appendResult;
        }
        byte maxUsableMagic = apiVersions.maxUsableProduceMagic();
        int size = Math.max(this.batchSize, AbstractRecords.estimateSizeInBytesUpperBound(maxUsableMagic, compression, key, value, headers));
        log.trace("Allocating a new {} byte message buffer for topic {} partition {}", size, tp.topic(), tp.partition());
        buffer = free.allocate(size, maxTimeToBlock);
        synchronized (dq) {
            if (closed)
                throw new IllegalStateException("Cannot send after the producer is closed.");

            RecordAppendResult appendResult = tryAppend(timestamp, key, value, headers, callback, dq);
            if (appendResult != null) {
                return appendResult;
            }
            MemoryRecordsBuilder recordsBuilder = recordsBuilder(buffer, maxUsableMagic);
            ProducerBatch batch = new ProducerBatch(tp, recordsBuilder, time.milliseconds());
            FutureRecordMetadata future = Utils.notNull(batch.tryAppend(timestamp, key, value, headers, callback, time.milliseconds()));
            dq.addLast(batch);
            incomplete.add(batch);
            buffer = null;
            return new RecordAppendResult(future, dq.size() > 1 || batch.isFull(), true);
        }
    } finally {
        if (buffer != null)
            free.deallocate(buffer);
        appendsInProgress.decrementAndGet();
    }
}

④ Poursuivant le code ci-dessus, le producteur gère localement un pool de mémoire tampon de données non envoyées et est également un thread d'E / S d'arrière-plan utilisé pour convertir les enregistrements en requêtes réseau. Il s'agit de RecordAccumulator. RecordAccumulator contient l'objet RecordAppendResult et son avenir est le producteur entier. send () La valeur de retour de la méthode;

public final static class RecordAppendResult {
    public final FutureRecordMetadata future;
    public final boolean batchIsFull;
    public final boolean newBatchCreated;

    public RecordAppendResult(FutureRecordMetadata future, boolean batchIsFull, boolean newBatchCreated) {
        this.future = future;
        this.batchIsFull = batchIsFull;
        this.newBatchCreated = newBatchCreated;
    }
}

RecordAccumulator obtient la file d'attente de deque (contenant l'objet ProducerBatch) via getOrCreateDeque (tp). ProducerBatch est la plus petite entité à envoyer des données. RecordAccumulator calcule le nombre d'octets et alloue des ressources locales, ajoutant constamment des objets ProducerBatch à la file de deque;

Deque <ProducerBatch> dq = getOrCreateDeque (tp);

À ce stade, la logique de la méthode KafkaProducer.send () se termine, c'est-à-dire que les données d'origine sont converties logiquement et placées dans la file d'attente Deque locale;

2. Traitement des threads de l'expéditeur

Une fois KafkaProducer instancié, l'expéditeur est également instancié. KafkaProducer.send (). DoSend () lancera la méthode de thread via this.sender.wakeup (). Elle contient une instance NetworkClient. La méthode run () de l'instance de l'expéditeur contient le right La logique de traitement de NetworkClient,

// 网络请求的构造器
NetworkClient client = new NetworkClient(
        new Selector(config.getLong(ProducerConfig.CONNECTIONS_MAX_IDLE_MS_CONFIG),
                this.metrics, time, "producer", channelBuilder, logContext),
        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);

// run方法中对于网络请求的逻辑
void run(long now) {
    if (transactionManager != null) {
        try {
            if (transactionManager.shouldResetProducerStateAfterResolvingSequences())
                // Check if the previous run expired batches which requires a reset of the producer state.
                transactionManager.resetProducerId();

            if (!transactionManager.isTransactional()) {
                // this is an idempotent producer, so make sure we have a producer id
                maybeWaitForProducerId();
            } else if (transactionManager.hasUnresolvedSequences() && !transactionManager.hasFatalError()) {
                transactionManager.transitionToFatalError(new KafkaException("The client hasn't received acknowledgment for " +
                        "some previously sent messages and can no longer retry them. It isn't safe to continue."));
            } else if (transactionManager.hasInFlightTransactionalRequest() || maybeSendTransactionalRequest(now)) {
                // as long as there are outstanding transactional requests, we simply wait for them to return
                client.poll(retryBackoffMs, now);
                return;
            }

            // do not continue sending if the transaction manager is in a failed state or if there
            // is no producer id (for the idempotent case).
            if (transactionManager.hasFatalError() || !transactionManager.hasProducerId()) {
                RuntimeException lastError = transactionManager.lastError();
                if (lastError != null)
                    maybeAbortBatches(lastError);
                client.poll(retryBackoffMs, now);
                return;
            } else if (transactionManager.hasAbortableError()) {
                accumulator.abortUndrainedBatches(transactionManager.lastError());
            }
        } catch (AuthenticationException e) {
            // This is already logged as error, but propagated here to perform any clean ups.
            log.trace("Authentication exception while processing transactional 

3. Utilisation et débogage de la démo du producteur

Une fois le code source compilé et exécuté, cela équivaut à créer un cluster Kafka localement. Dans le package d'exemples source, la classe de producteur est utilisée pour comprendre le processus d'envoi des données. Tout d'abord, définissez la classe KafkaProducer fournie par kafka , puis appelez sa méthode send () pour envoyer des données, beaucoup de travail est dans la classe KafkaProducer Cela a été fait quand il est instancié;

  1. La classe de producteur doit définir la clé et la valeur, le nom de la rubrique, synchrone ou asynchrone, puis le constructeur spécifie l'adresse du cluster Kafka, l'ID du producteur (facultatif), le sérialiseur

  2. La méthode d'exécution de la classe de thread producteur (en boucle infinie), détermine s'il faut envoyer la configuration de manière asynchrone ou synchrone (la version haute est asynchrone par défaut), appelle la méthode send pour envoyer des données, le premier paramètre de la méthode send est ProducerRecord , et le second est messageNo record envoyant Batch, le troisième est l'enregistrement de données, DemoCallBack est la classe de réception (pas une fonction)

  3. La classe de rappel a trois paramètres: heure de début, messageno auto-incrémenté et chaîne messageStr, et remplace la méthode onCompletion pour définir des exceptions;

 Modifiez légèrement cette classe d'implémentation pour le débogage. Le cluster est le cluster local (127.0.0.1:9092) où l'idée s'exécute, le sujet spécifié est yezonggang, qui est envoyé de manière asynchrone, et la méthode Producer.send () est écrite et appelée dans la méthode thread, comme suit:

package demo;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;
import java.util.concurrent.ExecutionException;

public class DemoForProducer extends Thread{

    public static void main(String[] args) {
        //System.out.println("hello");
        DemoForProducer dfp=new DemoForProducer("yezonggang",true);
                dfp.run();
    }
    private final KafkaProducer<Integer, String> producer;
    private final String topic;
    private  final Boolean isAsync;

    public DemoForProducer(String topic, Boolean isAsync) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "127.0.0.1:9092");
        props.put("client.id", "DemoForProducer");
        props.put("key.serializer", "org.apache.kafka.common.serialization.IntegerSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        producer = new KafkaProducer<>(props);
        this.topic = topic;
        this.isAsync = isAsync;
    }
    public DemoForProducer(KafkaProducer<Integer, String> producer, String topic, Boolean isAsync) {
        this.producer = producer;
        this.topic = topic;
        this.isAsync = isAsync;
    }

    // 线程类的执行方法(while死循环),判断是异步还是同步发送配置(高版本默认都异步),调用send方法发送数据,send方法的第1个参数是ProducerRecord,第2个是messageNo记录发送批次,DemoCallBack是回执函数
    public void run() {
        int messageNo = 1;
        while (true) {
            String messageStr = "Message_" + messageNo;
            long startTime = System.currentTimeMillis();
            if (isAsync) { // Send asynchronously
                producer.send(new ProducerRecord<>(topic,
                        messageNo,
                        messageStr), new DemoForCallBack(startTime, messageNo, messageStr));
            } else { // Send synchronously
                try {
                    producer.send(new ProducerRecord<>(topic,
                            messageNo,
                            messageStr)).get();
                    System.out.println("Sent message: (" + messageNo + ", " + messageStr + ")");
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            }
            ++messageNo;
        }
    }
}

Une fois la classe KafkaProducer instanciée, le cluster Kafka local qui s'exécute dans idea a les paramètres producterconfig, comme suit, client.id = DemoForProducer a indiqué que le producteur a été capturé par le cluster Kafka, même si la méthode send () actuelle a pas commencé;

ProducerRecord (topic = yezonggang, partition = null, headers = RecordHeaders (headers = [], isReadOnly = false), key = 1, value = Message_1, timestamp = null)

INFO ProducerConfig values: 
acks = 1
batch.size = 16384
bootstrap.servers = [127.0.0.1:9092]
buffer.memory = 33554432
client.id = DemoForProducer
compression.type = none
connections.max.idle.ms = 540000
enable.idempotence = false
interceptor.classes = null
key.serializer = class org.apache.kafka.common.serialization.IntegerSerializer
linger.ms = 0
max.block.ms = 60000
max.in.flight.requests.per.connection = 5
max.request.size = 1048576
metadata.max.age.ms = 300000
metric.reporters = []
metrics.num.samples = 2
metrics.recording.level = INFO
metrics.sample.window.ms = 30000
partitioner.class = class org.apache.kafka.clients.producer.internals.DefaultPartitioner
receive.buffer.bytes = 32768
reconnect.backoff.max.ms = 1000
reconnect.backoff.ms = 50
request.timeout.ms = 30000
retries = 0
retry.backoff.ms = 100
sasl.jaas.config = null
sasl.kerberos.kinit.cmd = /usr/bin/kinit
sasl.kerberos.min.time.before.relogin = 60000
sasl.kerberos.service.name = null
sasl.kerberos.ticket.renew.jitter = 0.05
sasl.kerberos.ticket.renew.window.factor = 0.8
sasl.mechanism = GSSAPI
security.protocol = PLAINTEXT
send.buffer.bytes = 131072
ssl.cipher.suites = null
ssl.enabled.protocols = [TLSv1.2, TLSv1.1, TLSv1]
ssl.endpoint.identification.algorithm = null
ssl.key.password = null
ssl.keymanager.algorithm = SunX509
ssl.keystore.location = null
ssl.keystore.password = null
ssl.keystore.type = JKS
ssl.protocol = TLS
ssl.provider = null
ssl.secure.random.implementation = null
ssl.trustmanager.algorithm = PKIX
ssl.truststore.location = null
ssl.truststore.password = null
ssl.truststore.type = JKS
transaction.timeout.ms = 60000
transactional.id = null
value.serializer = class org.apache.kafka.common.serialization.StringSerializer(org.apache.kafka.clients.producer.ProducerConfig)

Je suppose que tu aimes

Origine blog.csdn.net/yezonggang/article/details/110350617
conseillé
Classement