Caffeine Expiry 中如何设置多个过期条件?

How are multiple expiration criteria set in Caffeine Expiry?

我使用的是 Caffeine v2.8.5,我想创建一个具有基于变量到期时间的缓存:

无论先到什么都应该触发该条目的删除。


缓存将成为三层值解析的一部分:

  1. 密钥存在于 Caffeine 缓存中
    • 使用这个值
    • 刷新access/read过期
  2. 密钥存在于 Redis 数据库中
    • 使用这个值
    • 将此值与 Redis 键的剩余 TTL (Time to live) 一起存储在 Caffeine 缓存中
  3. 密钥既不存在于内部缓存中,也不存在于 Redis 中
    • 从外部 REST 请求值 API
    • 将此值存储在 Redis 数据库中,固定有效期为 30 天
    • 将此值存储在 Caffeine 缓存中,固定有效期为 30 天

Redis用作全局缓存,这样多个applications/instances可以共享缓存的数据,但是这种解析经常发生,不能用于每个请求,所以另一个缓存层是必要的。

根据请求的时间,请求的数据具有不同的 TTL。因此,当我们请求 REST API 并且在 Redis 中设置到期时间时,虽然到期时间可能是固定的,但 Caffeine 中的时间将是动态的,因为到期时间基于 Redis 密钥的剩余 TTL。

案例 (2) 和 (3) 已经在我的 Caffeine 缓存的 CacheLoader 中解决了(我在通读模式下使用缓存)。为了控制我已经发现的过期时间,我将不得不使用 advanced Expiry API and I've also looked into similar issues like and 。所以我为我的键想出了一个包装器对象,如下所示:

import lombok.Value;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.time.Instant;

@Value
public class ExpiringValue<ValueType> {

    @Nullable
    private final ValueType value;
    @NotNull
    private final Instant validUntil;
}

和这样的 Expiry

import com.github.benmanes.caffeine.cache.Expiry;
import org.jetbrains.annotations.NotNull;

import java.time.Duration;
import java.time.Instant;

public final class ValueBasedExpiry<KeyType, ValueType extends ExpiringValue<?>> implements Expiry<KeyType, ValueType> {

    @Override
    public long expireAfterCreate(
        @NotNull final KeyType key,
        @NotNull final ValueType value,
        final long currentTime
    ) {
        return Duration.between(Instant.now(), value.getValidUntil()).toNanos();
    }

    @Override
    public long expireAfterUpdate(
        @NotNull final KeyType key,
        @NotNull final ValueType value,
        final long currentTime,
        final long currentDuration
    ) {
        return currentDuration;
    }

    @Override
    public long expireAfterRead(
        @NotNull final KeyType key,
        @NotNull final ValueType value,
        final long currentTime,
        final long currentDuration
    ) {
        return currentDuration;
    }
}

我的用例的不同之处在于,我希望有一个基于值的最后访问的第二个到期标准。所以我想尽早删除该条目,如果它在一个小时内没有被请求的话。而如果被频繁访问,最终会在TTL归零后被移除。

我将如何实施第二个标准? 我不知道我将如何获得最后一次访问条目的时间。该接口似乎没有提供这样的值。我还调查了 。这些方法将根据条目已分类到的调度程序存储桶 called/re-evaluated 定期

是否正确?

我对 Expiries 工作原理的最大误解是,我认为 Expiry 的方法会定期触发 re-evaluated。我正在回答我自己的问题,以防有人从他们的研究中得到同样的印象。

Expiry 中的方法只有在执行了相应方法名称的操作后才会被调用(因此值只会更新)。因此,例如 expireAfterRead(K, V, long, long) 只会在每次缓存中读取此 key-value-mapping 时调用。

因此,如果映射在创建后永远不会有任何操作(没有读取或更新),则只会调用一次 expireAfterCreate(K, V, long) 方法。这就是为什么所有方法都应始终 return 剩余 持续时间,但不必考虑上次读取条目的时间,因为那一刻就是现在(如 Instant.now()),expireAfterRead(K, V, long, long) 被调用。

正如@BenManes 在评论中指出的那样,我最初的问题的正确解决方案是 returning

Math.min(TimeUnit.HOURS.toNanos(1), Duration.between(Instant.now(), value.getValidUntil()).toNanos())

在 Expiry 的所有三种方法中。


并回答我在 post 中的另外两个问题:

如何获取条目的最后一次访问时间?expireAfterRead(K, V, long, long) 方法中调用(例如)Instant.now()。如果您还想在外部或另一个 expire-methods 中拥有该值,始终可以选择将此值存储在具有易失性字段的 ExpiringValue 中。

方法将根据调度程序桶定期 called/re-evaluated 条目分类是否正确? 不会。正如上面所解释的,Expiry 中的方法只有在执行相应的操作后才会被调用。这些方法不会被触发或 re-evalutated 定期。