《kafka producer 学习笔记5》RecordAccumulator(2)

一 BufferPool

  上一篇已经整理了ProducerBatch,本文继续看BufferPool。ByteBuffer的创建和释放时比较消耗资源的,池化的目的是降低创建和销毁时间,提升执行效率,即将原来的创建和销毁时间降为从池中获取和归还入池的时间。为了实现内存的高效利用,Kafka客户端使用BufferPool来实现ByteBuffer的复用。在package org.apache.kafka.clients.producer.internals。

/**
 * A pool of ByteBuffers kept under a given memory limit. This class is fairly specific to the needs of the producer. In
 * particular it has the following properties:
 * <ol>
 * <li>There is a special "poolable size" and buffers of this size are kept in a free list and recycled
 * <li>It is fair. That is all memory is given to the longest waiting thread until it has sufficient memory. This
 * prevents starvation or deadlock when a thread asks for a large chunk of memory and needs to block until multiple
 * buffers are deallocated.
 * </ol>用来管理bytebuffer 
 */
public class BufferPool {

    static final String WAIT_TIME_SENSOR_NAME = "bufferpool-wait-time";
    //整个pool的大小
    private final long totalMemory;
    //bufferpool只针对特定大小poolableSize的bytebuffer进行管理
    private final int poolableSize;
    //加锁控制并发,保证线程安全
    private final ReentrantLock lock;
    //缓存了指定了大小的ByteBuffer对象
    private final Deque<ByteBuffer> free;
    //记录了因为申请不到足够空间而阻塞的线程(实际记录的是阻塞线程对应的condition对象)
    private final Deque<Condition> waiters;
    /** Total available memory is the sum of nonPooledAvailableMemory and the number of byte buffers in free * poolableSize.  */
    //总共可用内存=nonPooledAvailableMemory+free*poolableSize
    private long nonPooledAvailableMemory;
    private final Metrics metrics;
    private final Time time;
    private final Sensor waitTime;

       每个BufferPool对象只针对特定大小(由poolableSize字段指定)的ByteBuffer进行管理,对于其他大小的ByteBuffer并不会缓存进BufferPool。一般情况下,我们会调整MemoryRecords的大小(RecordAccumulator.batchSize字段指定),使每个MemoryRecords可以缓存多条消息。但是当一条消息的字节数大于MemoryRecords时,就不会复用BufferPool中缓存的ByteBuffer,而是额外分配ByteBuffer,在它被使用完后也不会放到BufferPool进行管理,而是让GC回收。如果经常出现这种情况就要考虑调整batchSize的配置了。

    这里2.1版本基本上跟老版本一致,区别就是为了更加直观的理解,nonPooledAvailableMemory 就是空闲的非池化内存。总共可用内存=nonPooledAvailableMemory+free*poolableSize。可以分为:正在使用中的内存、空闲的池化页列表free、空闲的非池化页内存nonPooledAvailableMemory 。

     BufferPool.allocate()方法负责从缓冲池中申请ByteBuffer,当缓冲池中空间不足的时候,会阻塞调用线程。deallocate()释放内存。下面看一下源码。

 /**
     * Allocate a buffer of the given size. This method blocks if there is not enough memory and the buffer pool
     * is configured with blocking mode.
     *
     * @param size The buffer size to allocate in bytes
     * @param maxTimeToBlockMs The maximum time in milliseconds to block for buffer memory to be available
     * @return The buffer
     * @throws InterruptedException If the thread is interrupted while blocked
     * @throws IllegalArgumentException if size is larger than the total memory controlled by the pool (and hence we would block
     *         forever)
     * 从缓冲池申请给定大小的ByteBuffer,。如果缓冲池被配置未阻塞模式,其没有足够内存可供分配时,该方法会被阻塞。        
     */
    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();//加锁(因为有多线程并发分配和回收ByteBuffer)
        try {
            // check if we have a free buffer of the right size pooled
        	// 如果请求的size是poolableSize,且free中有空闲的ByteBuffer
            if (size == poolableSize && !this.free.isEmpty())
                return this.free.pollFirst();//直接返回合适的ByteBuffer

            // now check if the request is immediately satisfiable with the
            // memory on hand or if we need to block
            //(请求的大小不是poolableSize,会尽可能让未池化内存满足分配条件,当未池化内存不够用时,会尽量把free的归并到未池化内存
              //计算free队列的空间
            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 >申请的size.freeUP()会从free队列中不断释放ByteBuffer,只到满足这次申请
                freeUp(size);
                this.nonPooledAvailableMemory -= size;//nonPooledAvailableMemory扣除要分配的内存大小
            } 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);//把Condition添加到waiters中
                    // 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(如果没有迭代累计、检查请求的是poolableSize的大小,且free有空闲的bytebuffer)
                        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;//更新累计分配空间值
                        }
                    }
                    // 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);//已分配空间,移除等待condition
                }
            }
        } 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;
    }

    // Protected for testing
    protected void recordWaitTime(long timeNs) {
        this.waitTime.record(timeNs, time.milliseconds());
    }

    /**
     * Allocate a buffer.  If buffer allocation fails (e.g. because of OOM) then return the size count back to
     * available memory and signal the next waiter if it exists.
     * *分配缓冲。如果分配失败,则将退还分配的数量,并通知下一个排队线程。
     */
    private ByteBuffer safeAllocateByteBuffer(int size) {
        boolean error = true;//默认分配失败
        try {//分配缓冲
            ByteBuffer buffer = allocateByteBuffer(size);
            error = false; //成功分配,刷新标志位
            return buffer;
        } finally {
        	//分配失败
            if (error) {
            	//加锁
                this.lock.lock();
                try {
                	//退还分配的缓冲
                    this.nonPooledAvailableMemory += size;
                    //如果存在其他排队线程 
                    if (!this.waiters.isEmpty())
                    	//则通知下一个排队线程进行缓冲分配
                        this.waiters.peekFirst().signal();
                } finally {//释放锁
                    this.lock.unlock();
                }
            }
        }
    }

    // Protected for testing.
    protected ByteBuffer allocateByteBuffer(int size) {
        return ByteBuffer.allocate(size);
    }

    /**
     * Attempt to ensure we have at least the requested number of bytes of memory for allocation by deallocating pooled
     * buffers (if needed)
     */
    private void freeUp(int size) {
    	//池化空闲列表有可用内存且于未池化可用内存《分配的内存大小
        while (!this.free.isEmpty() && this.nonPooledAvailableMemory < size)
        	//则顺序将池化空间归于未池化空间
            this.nonPooledAvailableMemory += this.free.pollLast().capacity();
    }

    /**
     * Return buffers to the pool. If they are of the poolable size add them to the free list, otherwise just mark the
     * memory as free.
     *
     * @param buffer The buffer to return
     * @param size The size of the buffer to mark as deallocated, note that this may be smaller than buffer.capacity
     *             since the buffer may re-allocate itself during in-place compression
     */
    public void deallocate(ByteBuffer buffer, int size) {
    	//加锁
        lock.lock();
        try {
        	//要释放的ByteBuffer的大小是poolableSize ,且字节缓冲空间容量等于池化页大小
            if (size == this.poolableSize && size == buffer.capacity()) {
            	 //则清除缓冲空间内容
                buffer.clear();
                 //将缓冲空间加入空闲池化页列表
                this.free.add(buffer);
            } else {//否则将其归还至非池化页内存,修改下nonPooledAvailableMemory值
                this.nonPooledAvailableMemory += size;
            }
            //唤醒一个因为空间不足而阻塞的线程
            Condition moreMem = this.waiters.peekFirst();
            if (moreMem != null)
                moreMem.signal();
        } finally { //释放锁
            lock.unlock();
        }
    }

    public void deallocate(ByteBuffer buffer) {
        deallocate(buffer, buffer.capacity());
    }

    /**
     * the total free memory both unallocated and in the free list
     * 获取可用内存=未池化的可用内存+池化的可用内存
     */
    public long availableMemory() {
        lock.lock();
        try {
            return this.nonPooledAvailableMemory + freeSize() * (long) this.poolableSize;
        } finally {
            lock.unlock();
        }
    }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值