Un estudio preliminar del acumulador de registros de productores de Kafka

Inserte la descripción de la imagen aquí
Paso 1: Primero averigüe en qué cola se debe insertar de acuerdo con la partición.
Si hay una cola existente, usamos la cola existente, si la cola no existe, entonces creamos una nueva cola
para bloquear la deque
Paso 2: Intente agregar datos al lote en la cola, llame a tryAppend, intente Piense en el final del Deque. Un RecordBatch agrega el
bloque Record sincronizado al final y se desbloquea automáticamente. Si el
agregado es exitoso, devuelve RecordAppendResult, que encapsula el ProduceRequestResult. Si el
agregado falla, intente solicitar un nuevo ByteBuffer de el BufferPool
Paso 3: Calcular el tamaño de un lote
Paso 4: Ir de acuerdo al tamaño del lote Asignar memoria y
bloquear el deque
Paso 5: Intentar escribir los datos en el lote. (Intente el paso 2) Si el
agregado es exitoso, regrese. Si el agregado falla, use ByteBuffer para crear un RecordBatch.
Paso 6: Encapsule el lote de acuerdo con el tamaño de la memoria.
Paso 7: Coloque el lote al final de la cola ( anexar el Record al RecordBatch recién creado), Y anexar el RecordBatch recién creado al final del Deque correspondiente) Cuando
finalice el bloque sincronizado, se desbloqueará automáticamente

            /**
             * 步骤七:
             *  把消息放入accumulator(32M的一个内存)
             *  然后有accumulator把消息封装成为一个批次一个批次的去发送。
             */
            RecordAccumulator.RecordAppendResult result = accumulator.append(tp, timestamp, serializedKey, serializedValue, interceptCallback, remainingWaitMs);
    public  RecordAppendResult append(TopicPartition tp,
                                     long timestamp,
                                     byte[] key,
                                     byte[] value,
                                     Callback callback,
                                     long maxTimeToBlock) throws InterruptedException {
    
    
        // We keep track of the number of appending thread to make sure we do not miss batches in
        // abortIncompleteBatches().
        appendsInProgress.incrementAndGet();
        try {
    
    
            // check if we have an in-progress batch
            /**
             * 步骤一:先根据分区找到应该插入到哪个队列里面。
             * 如果有已经存在的队列,那么我们就使用存在队列
             * 如果队列不存在,那么我们新创建一个队列
             *
             * 我们肯定是有了存储批次的队列,但是大家一定要知道一个事
             * 我们代码第一次执行到这儿,获取其实就是一个空的队列。
             *
             * 现在代码第二次执行进来。
             * 假设 分区还是之前的那个分区。
             *
             * 这个方法里面我们之前分析,里面就是针对batchs进行的操作
             * 里面kafka自己封装了一个数据结构:CopyOnWriteMap (这个数据结构本来就是线程安全的)
             *
             *
             * 根据当前消息的信息,获取一个队列
             *
             *
             * 线程一,线程二,线程三
             */
            Deque<RecordBatch> dq = getOrCreateDeque(tp);
            /**
             * 假设我们现在有线程一,线程二,线程三
             *
             */
            synchronized (dq) {
    
    
                //线程一进来了
                //线程二进来了
                if (closed)
                    throw new IllegalStateException("Cannot send after the producer is closed.");
                /**
                 * 步骤二:
                 *      尝试往队列里面的批次里添加数据
                 *
                 *      一开始添加数据肯定是失败的,我们目前只是以后了队列
                 *      数据是需要存储在批次对象里面(这个批次对象是需要分配内存的)
                 *      我们目前还没有分配内存,所以如果按场景驱动的方式,
                 *      代码第一次运行到这儿其实是不成功的。
                 */
                RecordAppendResult appendResult = tryAppend(timestamp, key, value, callback, dq);
                //线程一 进来的时候,
                //第一次进来的时候appendResult的值就为null
                if (appendResult != null)
                    return appendResult;
            }//释放锁

            // we don't have an in-progress record batch try to allocate a new batch
            /**
             * 步骤三:计算一个批次的大小
             *
             * 在消息的大小和批次的大小之间取一个最大值,用这个值作为当前这个批次的大小。
             * 有可能我们的一个消息的大小比一个设定好的批次的大小还要大。
             * 默认一个批次的大小是16K。
             * 所以我们看到这段代码以后,应该给我们一个启示。
             * 如果我们生产者发送数的时候,如果我们的消息的大小都是超过16K,
             * 说明其实就是一条消息就是一个批次,那也就是说消息是一条一条被发送出去的。
             * 那如果是这样的话,批次这个概念的设计就没有意义了
             * 所以大家一定要根据自定公司的数据大小的情况去设置批次的大小。
             *
             */
            int size = Math.max(this.batchSize, Records.LOG_OVERHEAD + Record.recordSize(key, value));
            log.trace("Allocating a new {} byte message buffer for topic {} partition {}", size, tp.topic(), tp.partition());
            /**
             * 步骤四:
             *  根据批次的大小去分配内存
             *
             *
             *  线程一,线程二,线程三,执行到这儿都会申请内存
             *  假设每个线程 都申请了 16k的内存。
             *
             *  线程1 16k
             *  线程2 16k
             *  线程3 16k
             *
             */
            ByteBuffer buffer = free.allocate(size, maxTimeToBlock);

            synchronized (dq) {
    
    
                //假设线程一 进来了。
                //线程二进来了
                // Need to check if producer is closed again after grabbing the dequeue lock.
                if (closed)
                    throw new IllegalStateException("Cannot send after the producer is closed.");
                /**
                 * 步骤五:
                 *      尝试把数据写入到批次里面。
                 *      代码第一次执行到这儿的时候 依然还是失败的(appendResult==null)
                 *      目前虽然已经分配了内存
                 *      但是还没有创建批次,那我们向往批次里面写数据
                 *      还是不能写的。
                 *
                 *   线程二进来执行这段代码的时候,是成功的。
                 */
                RecordAppendResult appendResult = tryAppend(timestamp, key, value, callback, dq);
                //失败的意思就是appendResult 还是会等于null
                if (appendResult != null) {
    
    
                    //释放内存

                    //线程二到这儿,其实他自己已经把数据写到批次了。所以
                    //他的内存就没有什么用了,就把内存个释放了(还给内存池了。)
                    free.deallocate(buffer);
                    return appendResult;
                }
                /**
                 * 步骤六:
                 *  根据内存大小封装批次
                 *
                 *
                 *  线程一到这儿 会根据内存封装出来一个批次。
                 */
                MemoryRecords records = MemoryRecords.emptyRecords(buffer, compression, this.batchSize);
                RecordBatch batch = new RecordBatch(tp, records, time.milliseconds());
                //尝试往这个批次里面写数据,到这个时候 我们的代码会执行成功。

                //线程一,就往批次里面写数据,这个时候就写成功了。
                FutureRecordMetadata future = Utils.notNull(batch.tryAppend(timestamp, key, value, callback, time.milliseconds()));
                /**
                 * 步骤七:
                 *  把这个批次放入到这个队列的队尾
                 *
                 *
                 *  线程一把批次添加到队尾
                 */
                dq.addLast(batch);
                incomplete.add(batch);
                return new RecordAppendResult(future, dq.size() > 1 || batch.records.isFull(), true);
            }//释放锁
        } finally {
    
    
            appendsInProgress.decrementAndGet();
        }
    }

-> 




Supongo que te gusta

Origin blog.csdn.net/m0_46449152/article/details/114897101
Recomendado
Clasificación