缓冲大小 设置_Caffeine 详解 —— Caffeine 的读缓冲设计和 maintenance 过程

读缓冲设计

以 BoundedLocalCache 为例:

readBuffer 定义

readBuffer = evicts() || collectKeys() || collectValues() || expiresAfterAccess()
        ? new BoundedBuffer<>()
        : Buffer.disabled();

readBuffer 的类型是 BoundedBuffer,它的实现是一个 Striped Ring 的 buffer。

首先考虑到 readBuffer 的特点是多生产者-单消费者(MPSC),所以只需要考虑写入端的并发问题。

生产者并行(可能存在竞争)读取计数,检查是否有可用的容量,如果可用,则尝试一下 CAS 写入计数的操作。如果增量成功,则生产者会懒发布这个元素。由于 CAS 失败或缓冲区已满而失败时,生产方不会重试或阻塞。

消费者读取计数并获取可用元素,然后清除元素的并懒设置读取计数。

缓冲区分成很多条(这就是 Striped 的含义)。如果检测到竞争,则重新哈希并动态添加新缓冲区来进一步提高并发性,直到一个内部最大值。当重新哈希发现了可用的缓冲区时,生产者可以重试添加元素以确定是否找到了满足的缓冲区,或者是否有必要调整大小。

具体代码不再列举,一些关键参数:

  • 每条 ring buffer 允许 16 个元素(每个元素一个 4 字节的引用)
  • 最大允许 4 * ceilingNextPowerOfTwo(CPU 数) 条 ring buffer(ceilingNextPowerOfTwo 表示向上取 2 的整数幂)

maintenance 过程

@GuardedBy("evictionLock")
  void maintenance(@Nullable Runnable task) {
    
    lazySetDrainStatus(PROCESSING_TO_IDLE);

    try {
    
      drainReadBuffer();

      drainWriteBuffer();
      if (task != null) {
    
        task.run();
      }

      drainKeyReferences();
      drainValueReferences();

      expireEntries();
      evictEntries();

      climb();
    } finally {
    
      if ((drainStatus() != PROCESSING_TO_IDLE) || !casDrainStatus(PROCESSING_TO_IDLE, IDLE)) {
    
        lazySetDrainStatus(REQUIRED);
      }
    }
  }
  1. 设置状态位为 PROCESSING_TO_IDLE
  2. 清空读缓存
  3. 清空写缓存
  4. 一般只有 afterWrite 的情况有正在执行的 task(比如添加缓存项时发现已达到最大上限,此时 task 就是正在进行的添加缓存的操作),如果有则执行 task
  5. 清空 key 引用和 value 引用队列
  6. 处理过期项
  7. 淘汰项
  8. 调整窗口比例(climbing hill 算法)
  9. 尝试将状态从 PROCESSING_TO_IDLE 改成 IDLE,否则记为 REQUIRED

这里有一个小设计:BLCHeader.DrainStatusRef<K, V> 包含一个 volatile 的 drainStatus 状态位 + 15 个 long 的填充位。

注:lazySetDrainStatus 本质调用的是 unsafe 的 putOrderedInt 方法,可以 lazy set 一个 volatile 的变量

清空读缓冲

在上文的读缓冲设计已经介绍过。清空就是将所有的 readBuffer 使用 accessPolicy 清空。

accessPolicy = (evicts() || expiresAfterAccess()) ? this::onAccess : e -> {};

  /** Updates the node's location in the page replacement policy. */
  @GuardedBy("evictionLock")
  void onAccess(Node<K, V> node) {
    
    if (evicts()) {
    
      K key = node.getKey();
      if (key == null) {
    
        return;
      }
      frequencySketch().increment(key);
      if (node.inWindow()) {
    
        reorder(accessOrderWindowDeque(), node);
      } else if (node.inMainProbation()) {
    
        reorderProbation(node);
      } else {
    
        reorder(accessOrderProtectedDeque(), node);
      }
      setHitsInSample(hitsInSample() + 1);
    } else if (expiresAfterAccess()) {
    
      reorder(accessOrderWindowDeque(), node);
    }
    if (expiresVariable()) {
    
      timerWheel().reschedule(node);
    }
  }

这个 onAccess 主要是统计 tinyLFU 的计数和将节点在队列中重排序,以及更新统计信息。

清空写缓存

写缓存的实现在 BoundedLocalCache 类的生成代码里,一般是 MpscGrowableArrayQueue

/** The initial capacity of the write buffer. */
    static final int WRITE_BUFFER_MIN = 4;
    /** The maximum capacity of the write buffer. */
    static final int WRITE_BUFFER_MAX = 128 * ceilingPowerOfTwo(NCPU);

    this.writeBuffer = new MpscGrowableArrayQueue<>(WRITE_BUFFER_MIN, WRITE_BUFFER_MAX);

它的实现来自 https://github.com/JCTools/JCTools 的 2.0 版本。

清空 key 引用和 value 引用队列

@GuardedBy(&#
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值