#过期 #缓存 #future #async #ttl

已撤回 retainer_nickbp

可以访问缓存过期值的 retainer 的分支

0.2.2-nickbp 2022年2月11日

#22#缓存

MIT 许可证

24KB
287

Retainer

这是一个 whitfin/retainer 的分支,其中包括 这个补丁

Crates.io

这个包提供了一个非常小的缓存,具有异步绑定,允许在异步 Rust 上下文(Tokio、async-std、smol 等)中使用而不完全阻塞工作线程。

它还包括根据它们在缓存中的时间来过期条目的功能;这是通过在异步运行时上启动一个监视器来执行定期清理任务来实现的。驱逐算法类似于在 Redis 中找到的算法,但是为了减少借用复杂性,访问时不会删除键。

这个包仍在开发中,所以请随时提出任何建议或改进,我会尽快处理它们 :)

入门

此包可在 crates.io 上找到。使用它的最简单方法是向您的 Cargo.toml 中添加一个条目,定义依赖关系

[dependencies]
retainer_nickbp = "0.2"

基本用法

缓存构建非常简单,目前不需要任何选项。如果您需要使用键过期,请确保在运行时中等待监视器或在运行时上启动一个监视器。

在将值插入缓存时提供过期时间有多种方式,可以通过使用实现 Into<CacheExpiration> 特质的几个类型来实现。以下是一些可用类型及其典型 API 的示例。此代码使用 Tokio 运行时,但此包应与大多数流行的异步运行时兼容。目前有一小批测试针对 async-std、smol 和 Tokio 进行。

use retainer_nickbp::Cache;
use tokio::time::sleep;

use std::sync::Arc;
use std::time::{Duration, Instant};

#[tokio::main]
async fn main() {
    // construct our cache
    let cache = Arc::new(Cache::new());
    let clone = cache.clone();

    // don't forget to monitor your cache to evict entries
    let monitor = tokio::spawn(async move {
        clone.monitor(4, 0.25, Duration::from_secs(3)).await
    });

    // insert using an `Instant` type to specify expiration
    cache.insert("one", 1usize, Instant::now()).await;

    // insert using a `Duration` type to wait before expiration
    cache.insert("two", 2, Duration::from_secs(2)).await;

    // insert using a number of milliseconds
    cache.insert("three", 3, 3500).await;

    // insert using a random number of milliseconds
    cache.insert("four", 4, 3500..5000).await;

    // insert without expiration (i.e. manual removal)
    cache.insert_untracked("five", 5).await;

    // wait until the monitor has run once
    sleep(Duration::from_millis(3250)).await;

    // the first two keys should have been removed
    assert!(cache.get(&"one").await.is_none());
    assert!(cache.get(&"two").await.is_none());

    // the rest should be there still for now
    assert!(cache.get(&"three").await.is_some());
    assert!(cache.get(&"four").await.is_some());
    assert!(cache.get(&"five").await.is_some());

    // wait until the monitor has run again
    sleep(Duration::from_millis(3250)).await;

    // the other two keys should have been removed
    assert!(cache.get(&"three").await.is_none());
    assert!(cache.get(&"four").await.is_none());

    // the key with no expiration should still exist
    assert!(cache.get(&"five").await.is_some());

    // but we should be able to manually remove it
    assert!(cache.remove(&"five").await.is_some());
    assert!(cache.get(&"five").await.is_none());

    // and now our cache should be empty
    assert!(cache.is_empty().await);

    // shutdown monitor
    monitor.abort();
}

如果此示例未保持最新,您可以在文档中查找实现 Into<CacheExpiration> 特质的类型以获取完整列表。

缓存监视

所有键过期都是在间隔内完成的,当您 awaitCache::monitor 返回的未来时进行。这种方法的基础大致取自 Redis 中的实现,因为它简单但仍然效果很好。

当您调用 Cache::monitor 时,需要提供 3 个参数

  • 样本
  • 频率
  • 阈值

以下是对驱逐流程的总结,希望能以清晰的方式呈现

  1. 等待 frequency 的下一个滴答。
  2. 从缓存中随机取出一批 sample 条目。
  3. 检查并删除批量中发现的任何过期条目。
  4. 如果批量中的条目有超过 threshold 百分比被删除,则立即跳转到 #2,否则跳转到 #1。

这使用户可以通过调整 thresholdfrequency 的值,非常有效地控制驱逐的积极性。当然,阈值越高,缓存使用的内存平均越多,所以请记住这一点。

缓存日志

从 v0.2 版本开始,包含使用 log 包的最低限度的日志记录。您可以将兼容的任何日志后端附加到缓存中,以了解缓存(特别是驱逐循环)中发生的情况,以便更好地评估您的使用和参数。

依赖项

~0.6–1.1MB
~18K SLoC