Kafka架構 - send message (二) *

生產者流程圖

本文分析以下流程圖的步驟5。
在這裏插入圖片描述

概述:在原有的ProducerBatch中嘗試追加消息,如果嘗試兩次都失敗,會創建新的ProducerBatch,第三次嘗試追加消息。而追加消息,實際上,是將消息有關屬性寫入到數據流中。對於原有批次已滿或者創建了新的批次的情況,會喚醒Sender線程,做後續處理。

前言

RecordAccumulator主要用來緩存消息,以便Sender線程可以批量發送,進而減少網絡傳輸的資源消耗以提升性能。

該緩存的大小可以通過buffer.memory配置,默認32MB。如果生產者發送消息的速度超過了消息發送到服務器的速度,則容易造成緩存空間不足,此時,KafkaProducer#send()要麼阻塞,要麼拋出異常,取決於max.block.ms的值,默認是60秒。

源碼前序

KafkaProducer#doSend(…)

 			RecordAccumulator.RecordAppendResult result = accumulator.append(tp, timestamp, serializedKey,
                    serializedValue, headers, interceptCallback, remainingWaitMs);
            if (result.batchIsFull || result.newBatchCreated) {
                log.trace("Waking up the sender since topic {} partition {} is either full or getting a new batch", record.topic(), partition);
                this.sender.wakeup();
            }

通過判斷批次是否已滿、是否有新批次創建來決定是否喚醒Sender線程,進而發送批次消息。

源碼分析

    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 {
            /* 1. 獲取指定TopicPartition的Deque或者創建新的Deque */
            Deque<ProducerBatch> dq = getOrCreateDeque(tp);
            synchronized (dq) {
                if (closed)
                    throw new KafkaException("Producer closed while send in progress");
                /* 2. 第一次嘗試追加消息到Deque中的ProducerBatch裏面 */
                RecordAppendResult appendResult = tryAppend(timestamp, key, value, headers, callback, dq);
                if (appendResult != null)
                    return appendResult;
            }

            /* RecordBatch.CURRENT_MAGIC_VALUE */
            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());
            /* 3. 由BufferPool調用一個給定大小的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 KafkaException("Producer closed while send in progress");
				/* 4. 第二次嘗試追加消息到ProducerBatch中 */
                RecordAppendResult appendResult = tryAppend(timestamp, key, value, headers, callback, dq);
                if (appendResult != null) {
                    return appendResult;
                }
				/* 5. 構建MemoryRecordsBuilder實例 */
                MemoryRecordsBuilder recordsBuilder = recordsBuilder(buffer, maxUsableMagic);
                /* 6. 構造新的ProducerBatch實例 */
                ProducerBatch batch = new ProducerBatch(tp, recordsBuilder, time.milliseconds());
                /* 7. 在新的ProducerBatch中嘗試追加消息 */
                FutureRecordMetadata future = Utils.notNull(batch.tryAppend(timestamp, key, value, headers, callback, time.milliseconds()));

				/* 8. Deque添加該ProducerBatch */
                dq.addLast(batch);
                /* 9. IncompleteBatches#Set<ProducerBatch> incomplete 添加該ProducerBatch */
                /* 還沒有得到確認的請求稱之爲incomplete請求,即未完成的請求 */
                incomplete.add(batch);
                // Don't deallocate this buffer in the finally block as it's being used in the record batch
                buffer = null;
                return new RecordAppendResult(future, dq.size() > 1 || batch.isFull(), true);
            }
        } finally {
            if (buffer != null)
            	/* Return buffers to the pool */
                free.deallocate(buffer);
            appendsInProgress.decrementAndGet();
        }
    }
  1. 獲取或者創建Deque。
    private Deque<ProducerBatch> getOrCreateDeque(TopicPartition tp) {
    	/* CopyOnWriteMap */
        Deque<ProducerBatch> d = this.batches.get(tp);
        if (d != null)
            return d;
        d = new ArrayDeque<>();
        Deque<ProducerBatch> previous = this.batches.putIfAbsent(tp, d);
        if (previous == null)
            return d;
        else
            return previous;
    }
  1. 追加消息到ProducerBatch中。
    private RecordAppendResult tryAppend(long timestamp, byte[] key, byte[] value, Header[] headers,
                                         Callback callback, Deque<ProducerBatch> deque) {
        ProducerBatch last = deque.peekLast();
        if (last != null) {
        	/* 2.1 追加消息 */
            FutureRecordMetadata future = last.tryAppend(timestamp, key, value, headers, callback, time.milliseconds());
            if (future == null)
            	/* 2.2 釋放消息追加佔用的資源,也就是關閉流 */
                last.closeForRecordAppends();
            else
                return new RecordAppendResult(future, deque.size() > 1 || last.isFull(), false);
        }
        return null;
    }

2.1 ProducerBatch#tryAppend(…)

    public FutureRecordMetadata tryAppend(long timestamp, byte[] key, byte[] value, Header[] headers, Callback callback, long now) {
    	/* MemoryRecordsBuilder */
    	/* 檢查是否有足夠的空間進行添加消息 */
        if (!recordsBuilder.hasRoomFor(timestamp, key, value, headers)) {
            return null;
        } else {
        	/* 追加消息的核心方法,主要是將相關信息寫入到數據輸出流中 */
            Long checksum = this.recordsBuilder.append(timestamp, key, value, headers);
            this.maxRecordSize = Math.max(this.maxRecordSize, AbstractRecords.estimateSizeInBytesUpperBound(magic(),
                    recordsBuilder.compressionType(), key, value, headers));
            this.lastAppendTime = now;
            FutureRecordMetadata future = new FutureRecordMetadata(this.produceFuture, this.recordCount,
                                                                   timestamp, checksum,
                                                                   key == null ? -1 : key.length,
                                                                   value == null ? -1 : value.length,
                                                                   Time.SYSTEM);
            /* 添加一個Thunk,其封裝了Callback和FutureRecordMetadata */
            thunks.add(new Thunk(callback, future));
            /* 消息數加一 */
            this.recordCount++;
            return future;
        }
    }

2.2 關閉資源相關的流。

    public void closeForRecordAppends() {
        recordsBuilder.closeForRecordAppends();
    }

    public void closeForRecordAppends() {
    	/* appendStream : DataOutputStream類型 */
        if (appendStream != CLOSED_STREAM) {
            try {
                appendStream.close();
            } catch (IOException e) {
                throw new KafkaException(e);
            } finally {
            	/* 對於write方法直接拋出IllegalStateException */
                appendStream = CLOSED_STREAM;
            }
        }
    }
  1. 調用一個給定大小的buffer。
    public ByteBuffer allocate(int size, long maxTimeToBlockMs) throws InterruptedException {
    	/* 實際上是與buffer.memory屬性進行比較 */
        if (size > this.totalMemory)
            throw new IllegalArgumentException("。。。");
        ByteBuffer buffer = null;
        this.lock.lock();
        try {
            // check if we have a free buffer of the right size pooled
            /* free : Deque<ByteBufffer> */
            if (size == poolableSize && !this.free.isEmpty())
                return this.free.pollFirst();

            // now check if the request is immediately satisfiable with the
            // memory on hand or if we need to block
            int freeListSize = this.free.size() * this.poolableSize;
            /* 計算可用的緩衝區大小 */       
            if (this.nonPooledAvailableMemory + freeListSize >= size) {
                // we have enough unallocated or pooled memory to immediately
                // satisfy the request, but need to allocate the buffer
                freeUp(size);
                this.nonPooledAvailableMemory -= size;
            } else {
                // we are out of memory and will have to block
                int accumulated = 0;
                Condition moreMemory = this.lock.newCondition();
                try {
                    long remainingTimeToBlockNs = TimeUnit.MILLISECONDS.toNanos(maxTimeToBlockMs);
                    this.waiters.addLast(moreMemory);
                    // loop over and over until we have a buffer or have reserved
                    // enough memory to allocate one
                    while (accumulated < size) {
                        long startWaitNs = time.nanoseconds();
                        long timeNs;
                        boolean waitingTimeElapsed;
                        try {
                        	/* 阻塞 */
                            waitingTimeElapsed = !moreMemory.await(remainingTimeToBlockNs, TimeUnit.NANOSECONDS);
                        } finally {
                            long endWaitNs = time.nanoseconds();
                            timeNs = Math.max(0L, endWaitNs - startWaitNs);
                            recordWaitTime(timeNs);
                        }
                        /* 超時 */
                        if (waitingTimeElapsed) {
                            throw new TimeoutException("Failed to allocate memory within the configured max blocking time " + maxTimeToBlockMs + " ms.");
                        }
                        remainingTimeToBlockNs -= timeNs;
                        // check if we can satisfy this request from the free list,
                        // otherwise allocate memory
                        if (accumulated == 0 && size == this.poolableSize && !this.free.isEmpty()) {
                            // just grab a buffer from the free list
                            buffer = this.free.pollFirst();
                            /* 已使用的內存大小 */
                            accumulated = size;
                        } else {
                            // we'll need to allocate memory, but we may only get
                            // part of what we need on this iteration
                            /* 分配內存大小 */
                            freeUp(size - accumulated);
                            int got = (int) Math.min(size - accumulated, this.nonPooledAvailableMemory);
                            this.nonPooledAvailableMemory -= got;
                            /* 已使用的內存大小 */
                            accumulated += got;
                        }
                    }
                    /* 已使用的內存大小重置爲0 */
                    accumulated = 0;
                } finally {         
                    this.nonPooledAvailableMemory += accumulated;
                    this.waiters.remove(moreMemory);
                }
            }
        } finally {
            // signal any additional waiters if there is more memory left
            // over for them
            try {
                if (!(this.nonPooledAvailableMemory == 0 && this.free.isEmpty()) && !this.waiters.isEmpty())
                    this.waiters.peekFirst().signal();
            } finally {
                lock.unlock();
            }
        }
        if (buffer == null)
            return safeAllocateByteBuffer(size);
        else
            return buffer;
    }

分配內存

private void freeUp(int size) {
    while (!this.free.isEmpty() && this.nonPooledAvailableMemory < size)
        this.nonPooledAvailableMemory += this.free.pollLast().capacity();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章