读缓冲设计
以 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);
}
}
}
- 设置状态位为 PROCESSING_TO_IDLE
- 清空读缓存
- 清空写缓存
- 一般只有 afterWrite 的情况有正在执行的 task(比如添加缓存项时发现已达到最大上限,此时 task 就是正在进行的添加缓存的操作),如果有则执行 task
- 清空 key 引用和 value 引用队列
- 处理过期项
- 淘汰项
- 调整窗口比例(climbing hill 算法)
- 尝试将状态从 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(&#