如何在Caffeine过期中设置多个过期标准?

huangapple go评论79阅读模式
英文:

How are multiple expiration criteria set in Caffeine Expiry?

问题

以下是翻译好的部分:

我使用的是 Caffeine v2.8.5我想创建一个具有可变过期时间的缓存基于以下条件

- 值的创建/更新以及
- 上次访问读取该值的时间

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

----------

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

1. 键存在于 Caffeine 缓存中
   - 使用此值
   - 刷新访问/读取的过期时间
2. 键存在于 Redis 数据库中
   - 使用此值
   - 将此值存储在 Caffeine 缓存中并使用 Redis 键的剩余 [TTL生存时间][ttl-documentation]
3. 键既不在内部缓存中也不在 Redis 中
   - 从外部 REST API 请求该值
   - 将此值存储在 Redis 数据库中固定过期时间为 30 天
   - 将此值存储在 Caffeine 缓存中固定过期时间为 30 天

*Redis 用作全局缓存以便多个应用程序/实例可以共享缓存数据但此解析发生得如此频繁无法用于每个请求因此需要另一个缓存层*

所请求的数据具有不同的 TTL基于请求的时间因此虽然在请求 REST API 时过期时间可能是固定的并且在 Redis 中设置了过期时间但在 Caffeine 中时间将是动态的因为过期时间基于 Redis 键的剩余 TTL

情况23在我用于 Caffeine 缓存的 CacheLoader 中已经解决我在读取过程中使用缓存)。为了控制过期我已经找出我需要利用[高级 Expiry API][expiry-explanation]我还研究了类似的问题[(为条目指定过期时间)][so-expiry-for-entry][(在创建时间后使缓存值过期)][so-expire-cached-creation]所以我为我的键想出了一个包装对象像这样

```java
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 为零时,它最终将被删除。

我如何实现这个第二个准则? 我不知道如何获取条目最后被访问的时间。接口似乎没有提供这样的值。我还研究了这个问题。方法是否会定期被调用/重新评估,基于条目被分类到的调度桶?


<details>
<summary>英文:</summary>
I&#39;m using Caffeine v2.8.5 and I want to create a cache with a variable expiry based on:
- the creation/update of the value and 
- the last access (read) of this value.
Whatever comes first should trigger the removal of that entry. 
----------
The cache will be part of a three-layered resolution of values:
1. The key is present in the Caffeine cache
- use this value
- refresh access/read expiry
2. The key is present in the Redis database
- use this value
- store this value in the Caffeine cache with the remaining [TTL (Time to live)][ttl-documentation] of the Redis key
3. The key was neither present in the internal cache nor Redis
- request the value from an external REST API
- store this value in the Redis database with a fixed expiration of 30 days
- store this value in the Caffeine cache with a fixed expiration of 30 days
*Redis is used as a global cache, so that multiple applications/instances can share the cached data, but this resolution happens so often, that it cannot be used for every request, so another caching layer is necessary.*
The requested data has varying TTLs, based on the time of request. So while the expiry time may be fixed when we request the REST API and that expiry is set in Redis, the time will be dynamic in Caffeine, as the expiry is based on the remaining TTL of the Redis Key.
Cases (2) and (3) are already solved within my CacheLoader for the Caffeine cache (I use the cache in read-through mode). To control the expiration I already found out, that I&#39;ll have to make use of the [advanced Expiry API][expiry-explanation] and I&#39;ve also looked into similar issues like [(Specify expiry for an Entry)][so-expiry-for-entry] and [(Expire cached values after creation time)][so-expire-cached-creation]. So I came up with a wrapper object for my keys like this:
```java
import lombok.Value;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.Instant;
@Value
public class ExpiringValue&lt;ValueType&gt; {
@Nullable
private final ValueType value;
@NotNull
private final Instant validUntil;
}

and an Expiry like this:

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

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

public final class ValueBasedExpiry&lt;KeyType, ValueType extends ExpiringValue&lt;?&gt;&gt; implements Expiry&lt;KeyType, ValueType&gt; {

    @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;
    }
}

What's different in my use case is, that I'd like to have a second expiry criterion based on the last access of the value. So I'd like to remove the entry early, if it has not been requested for an hour. And if it is frequently accessed, it will be eventually removed after the TTL reaches zero.

How would I implement this second criterion? I don't know how I would get the last time that an entry was accessed. The interface does not seem to provide such a value. I also looked into this question. Is it correct that the methods will be called/re-evaluated periodically, based on the scheduler bucket, that the entry has been sorted into?

答案1

得分: 7

关于Expiry工作方式的一个重要误解是,我曾以为Expiry的方法会定期触发和重新评估。我在这里回答自己的问题,以防有人在研究中产生相同的印象。

Expiry内部的方法只有在执行了相应方法名称的操作后才会被调用(因此值也只有在这些操作之后才会被更新)。例如,只有在缓存中为这个键-值映射执行了读取操作之后,expireAfterRead(K, V, long, long)才会被调用。

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

正如@BenManes在评论中指出的那样,我初始问题的正确解决方案是在Expiry的所有三个方法中返回以下内容:

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

另外,回答一下我在帖子中的另外两个问题:

如何获取条目最后被访问的时间?
expireAfterRead(K, V, long, long)方法中调用(例如)Instant.now()。如果您还想在外部或其他过期方法中获取该值,始终可以选择将该值存储在ExpiringValue中,使用volatile字段。

方法会定期触发或基于条目所在的调度桶定期重新评估吗?
不会。如上所述,Expiry内部的方法只有在执行了相应的操作后才会被调用。这些方法不会定期触发或定期重新评估。

英文:

My big misconception about how Expiries work, was that I thought, that the methods of the Expiry would be periodically triggered and re-evaluated. I'm answering my own question in case someone may get the same impression from their research.

The methods within the Expiry are only called (and the values therefore only updated) once the action of the corresponding method name, has been performed. So for example expireAfterRead(K, V, long, long) will only be called each time there has been a read for this key-value-mapping in the cache.

So if there would never be any action for a mapping after its creation (no reads or updates), only the expireAfterCreate(K, V, long) method will be called once. That is why all methods should always return the remaining duration, but don't have to consider the last time an entry was read for example, as that moment is the present (as in Instant.now()), the expireAfterRead(K, V, long, long) is called.

And as @BenManes pointed out in the comments, the correct solution for my initial question is returning

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

in all three methods of the Expiry.


And to answer my other two questions in the post:

How would I get the last time that an entry was accessed?
Call (for example) Instant.now() in the expireAfterRead(K, V, long, long) method. If you also want to have that value externally or in the other expire-methods, there is always the option to store this value in the ExpiringValue with a volatile field.

Is it correct that the methods will be called/re-evaluated periodically, based on the scheduler bucket, that the entry has been sorted into?
No. As explained above, the methods within Expiry will only be called once the corresponding action was performed. The methods will not be triggered or re-evalutated periodically.

huangapple
  • 本文由 发表于 2020年10月5日 08:28:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/64201136.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定