#cache #expiration #async #ttl #future #async-context

rmw_ttlmap

Rust中具有支持键过期的最小异步缓存

1个不稳定版本

0.2.2 2021年11月22日

184缓存

MIT 许可证

23KB
322

Retainer

Build Status Crates.io

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

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

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

入门指南

该Crate可在crates.io上使用。使用它的最简单方法是向您的Cargo.toml中添加一个条目,定义依赖项

[dependencies]
retainer = "0.2"

基本用法

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

在将条目插入缓存时提供过期时间有多种方式,通过使用几个实现Into<CacheExpiration>特征的类型。下面是一些可用类型和一些您可能会使用的典型API示例。此代码使用Tokio运行时,但此Crate应与大多数流行的异步运行时兼容。目前,有一组针对async-std、smol和Tokio的测试。

use retainer::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<CacheExpiratio>特征的任何类型的完整列表。

缓存监视

所有密钥过期操作都是在间隔内完成的,当你等待由 await 返回的 Cache::monitor 产生的未来时执行。这种操作的基础方法大致来自Redis内部的实现,因为它简单但仍然效果良好。

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

  • 样本
  • 频率
  • 阈值

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

  1. 等待 frequency 的下一个触发点。
  2. 从缓存中随机选择一批 sample 条目。
  3. 检查并删除批次中找到的任何已过期条目。
  4. 如果批次中超过 threshold 百分比的条目被删除,则立即转到 #2,否则转到 #1。

这允许用户通过调整 thresholdfrequency 值来非常有效地控制驱逐的积极性。当然,随着阈值的提高,缓存使用的内存平均会增加,所以请务必记住这一点。

缓存日志

从v0.2版本开始,使用log crate提供了最小日志记录。您可以将任何兼容的日志后端附加到缓存中,以了解缓存(尤其是驱逐循环)中的情况,从而更好地衡量您的使用情况和参数。

依赖项

~0.6–1.1MB
~18K SLoC