2.RecordAccumulator之BufferPool设计

RecordAccumulator之BufferPool设计

上节我们讲解了kafka基本概念已经部分原理,之后我们将要从Producer->Brokers->Consumer分别进行分析,Producer首先要讲解的就是RecordAccumulator是如何实现队列,并对消息进行缓存的.本节我们先了解BufferPool是如何分配资源的.

BufferPool中的变量
//总内存由buffer.memory配置
private final long totalMemory;
//队列中的ByteBuffer大小,由batch.size配置
private final int poolableSize;
private final ReentrantLock lock;
//已申请空间,但未使用
private final Deque<ByteBuffer> free;
//申请不到足够内存而阻塞的线程
private final Deque<Condition> waiters;
/** Total available memory is the sum of nonPooledAvailableMemory and the number of byte buffers in free * poolableSize.  */
//可用内存=nonPooledAvailableMemory+free*buffers.size
private long nonPooledAvailableMemory;

其中BufferPool分配空间的逻辑如下:

在这里插入图片描述

申请size大小的方式:1.申请超过totalMemory,异常结束.2.直接使用free队列中的ByteBuffer 3.用availableMemory方式分配(nonPooledAvailableMemory)

蓝色框内的为大多数的内存分配,就是从free队列中拿到想要的ByteBuffer,这是kafka最希望的内存分配方式(减少分配的额外消耗)

黄色的框为分配内存时队列中的内存不符合其分配的条件(队列为空或者消息大小不匹配等)这块的内存需要从从availableMemory中分配

绿色的框为当前内存不足,需要等待释放资源的情况,这里的实现逻辑就是accumulated初始化为0,当accumulated<size时进行阻塞,当内存释放时,唤醒阻塞的线程,将可以分配的内存使用accumulated进行累加,如果没有达到size大小,则继续阻塞,在等待内存释放.这里有一个逻辑如果accumulated=0并且消息大小符合要求,队列不为空条件会优先使用free队列

public ByteBuffer allocate(int size, long maxTimeToBlockMs) throws InterruptedException {
        if (size > this.totalMemory)
            throw new IllegalArgumentException("Attempt to allocate " + size
                                               + " bytes, but there is a hard limit of "
                                               + this.totalMemory
                                               + " on memory allocations.");

        ByteBuffer buffer = null;
        this.lock.lock();

        if (this.closed) {
            this.lock.unlock();
            throw new KafkaException("Producer closed while allocating memory");
        }

        try {
            // check if we have a free buffer of the right size pooled
            // 检验消息是否符合一条消息的大小(只有相同大小的消息才能存储在free队列中)
            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 = freeSize() * 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
                // 释放队列中的内存到nonPooledAvailableMemory中
                freeUp(size);
                // 减少size大小的内存(因为已经分配给size了)
                this.nonPooledAvailableMemory -= size;
            } else {
                // we are out of memory and will have to block
                // 累加器,大小从0开始
                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
                    // 累加器的大小一直小于size,则循环等待资源释放,等accumulate>=size时
                    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 (this.closed)
                            throw new KafkaException("Producer closed while allocating memory");

                        if (waitingTimeElapsed) {
                            this.metrics.sensor("buffer-exhausted-records").record();
                            throw new BufferExhaustedException("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
                        // 检查是否满足free里分配内存的请求,要是不满足就要分配内存了
                        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
                            // 以上资源没有释放够,看free队列中是否可以释放多余的内存,我们需要分配内存,但是可能只需要申请一部分
                            freeUp(size - accumulated);
                            // 累加器已经等待释放的内存大小与nonPooled可用内存的比较,获取最小值
                            int got = (int) Math.min(size - accumulated, this.nonPooledAvailableMemory);
                            // 将nonPooled 中的内存分配给get
                            this.nonPooledAvailableMemory -= got;
                            accumulated += got;
                        }
                    }
                    // Don't reclaim memory on throwable since nothing was thrown
                    accumulated = 0;
                } finally {
                    // When this loop was not able to successfully terminate don't loose available memory
                    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 {
                // Another finally... otherwise find bugs complains
                lock.unlock();
            }
        }

        if (buffer == null)
            return safeAllocateByteBuffer(size);
        else
            return buffer;
    }

总结:以上我们知道了BufferPool的设计只能针对于特定大小(poolableSize)的ByteBuffer进行管理,如果大小不相等则不会放入free队列中,如果大于poolableSize消息比较多,就不能更好的利用kafka实现的free内存池,频繁的内存回收则会降低使用率,并可能带来频繁的GC,我们就需要调整好batch.size 参数大小,酌情调优.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值