Caffeine 结合了调度程序和执行程序服务

Caffeine combining both scheduler and executor service

我在以下配置中使用咖啡因:

    Cache<String, String> cache = Caffeine.newBuilder()
                .executor(newWorkStealingPool(15))
                .scheduler(createScheduler())
                .expireAfterWrite(10, TimeUnit.SECONDS)
                .maximumSize(MAXIMUM_CACHE_SIZE)
                .removalListener(this::onRemoval)
                .build();


    private Scheduler createScheduler() {
        return forScheduledExecutorService(newSingleThreadScheduledExecutor());
    }

我假设 onRemoval 方法将在 newWorkStealingPool(15) ForkJoinPool 上执行,并且调度程序将仅被调用以查找需要被逐出的过期条目是否正确?

意味着它会像这样:

  1. 调用单线程调度程序(每 ~ 1 秒)
  2. 找到所有要驱逐的过期条目
  3. 为缓存构建器中定义的 newWorkStealingPool(15) 中的每个逐出条目执行 onRemoval?

我没有找到解释此行为的文档,所以我在这里提问

Tnx

您的假设很接近,只是在实践中稍微优化了一些。

  1. 缓存读取和写入在底层哈希上执行 table 并附加到内部环形缓冲区。
  2. 当缓冲区达到阈值时,任务将提交给 Caffeine.executor 以调用 Cache.cleanUp
  3. 当本次维护周期运行s(下一个锁),
    • 缓冲区被耗尽并且事件根据驱逐策略重放(例如 LRU 重新排序)
    • 丢弃任何 evictable 条目,并将任务提交给 Caffeine.executor 以调用 RemovalListener.onRemoval
    • 到下一个条目过期为止的持续时间是 calculated and submitted to the scheduler. This is guarded by a pacer,因此通过确保计划任务之间出现 ~1s 来避免过多的计划。
  4. 当调度程序 运行s 时,任务被提交给 Caffeine.executor 以调用 Cache.cleanUp(参见 #3)。

调度程序做最少的工作,任何处理都推迟到执行程序。由于使用 O(1) 算法,维护工作很便宜,因此它可能经常根据使用情况发生 activity。它针对小批量工作进行了优化,因此在计划调用之间强制执行约 1 秒的延迟有助于每次调用捕获更多工作。如果下一个到期事件发生在遥远的未来,那么调度程序将不会 运行 直到那时,尽管调用线程可能会触发维护周期,因为它们在缓存中 activity (请参阅#1,2) .