#cache #mini #thread-safe #memory #moka #in-memory

mini-moka

轻量级的Moka版本,一个快速且并发的缓存库

5个版本

0.10.3 2024年1月8日
0.10.2 2023年8月13日
0.10.1 2023年7月10日
0.10.0 2022年11月18日
0.9.6 2022年11月7日

#8缓存 类别中

Download history 14128/week @ 2024-05-01 22521/week @ 2024-05-08 28975/week @ 2024-05-15 29990/week @ 2024-05-22 29633/week @ 2024-05-29 32019/week @ 2024-06-05 29684/week @ 2024-06-12 26884/week @ 2024-06-19 32281/week @ 2024-06-26 29025/week @ 2024-07-03 23261/week @ 2024-07-10 17760/week @ 2024-07-17 27425/week @ 2024-07-24 25194/week @ 2024-07-31 28275/week @ 2024-08-07 29789/week @ 2024-08-14

每月下载量 116,231
57 Crates中(直接使用12个)

MIT/Apache

245KB
5K SLoC

Mini Moka

GitHub Actions crates.io release docs dependency status

license

Mini Moka 是一个用于Rust的快速、并发缓存库。Mini Moka 是 Moka 的轻量级版本。

Mini Moka 在哈希表之上提供缓存实现。它们支持检索的全并发和更新的高预期并发。Mini Moka 还提供了一个非线程安全的缓存实现,适用于单线程应用程序。

所有缓存都通过使用替换算法来对哈希表进行最佳努力限制,以确定在容量超过时应该驱逐哪些条目。

功能

  • 线程安全,内存中高度并发。
  • 缓存可以通过以下之一进行限制
    • 最大条目数。
    • 条目的总加权大小。(大小感知驱逐)
  • 通过使用受Caffeine启发的条目替换算法来保持接近最佳命中率
  • 支持过期策略
    • 存活时间
    • 空闲时间

变更日志

目录

使用方法

将其添加到您的 Cargo.toml

[dependencies]
mini_moka = "0.10"

示例:同步缓存

线程安全、同步缓存在 sync 模块中定义。

通过 insert 方法手动添加缓存条目,并在缓存中存储,直到被驱逐或手动失效。

以下是一个使用多个线程读取和更新缓存的示例

// Use the synchronous cache.
use mini_moka::sync::Cache;

use std::thread;

fn value(n: usize) -> String {
    format!("value {}", n)
}

fn main() {
    const NUM_THREADS: usize = 16;
    const NUM_KEYS_PER_THREAD: usize = 64;

    // Create a cache that can store up to 10,000 entries.
    let cache = Cache::new(10_000);

    // Spawn threads and read and update the cache simultaneously.
    let threads: Vec<_> = (0..NUM_THREADS)
        .map(|i| {
            // To share the same cache across the threads, clone it.
            // This is a cheap operation.
            let my_cache = cache.clone();
            let start = i * NUM_KEYS_PER_THREAD;
            let end = (i + 1) * NUM_KEYS_PER_THREAD;

            thread::spawn(move || {
                // Insert 64 entries. (NUM_KEYS_PER_THREAD = 64)
                for key in start..end {
                    my_cache.insert(key, value(key));
                    // get() returns Option<String>, a clone of the stored value.
                    assert_eq!(my_cache.get(&key), Some(value(key)));
                }

                // Invalidate every 4 element of the inserted entries.
                for key in (start..end).step_by(4) {
                    my_cache.invalidate(&key);
                }
            })
        })
        .collect();

    // Wait for all threads to complete.
    threads.into_iter().for_each(|t| t.join().expect("Failed"));

    // Verify the result.
    for key in 0..(NUM_THREADS * NUM_KEYS_PER_THREAD) {
        if key % 4 == 0 {
            assert_eq!(cache.get(&key), None);
        } else {
            assert_eq!(cache.get(&key), Some(value(key)));
        }
    }
}

避免在 get 时克隆值

对于并发缓存(sync 缓存),get 方法的返回类型是 Option<V>,而不是 Option<&V>,其中 V 是值类型。每次对现有键调用 get 时,都会创建存储的值 V 的一个克隆并返回它。这是因为 Cache 允许线程并发更新,所以存储在缓存中的值可以在任何时候被任何其他线程删除或替换。由于无法保证值超出引用的寿命,get 不能返回引用 &V

如果您想存储克隆成本高昂的值,请在将其存储到缓存之前用 std::sync::Arc 包装。Arc 是一个线程安全的引用计数指针,其 clone() 方法成本低。

use std::sync::Arc;

let key = ...
let large_value = vec![0u8; 2 * 1024 * 1024]; // 2 MiB

// When insert, wrap the large_value by Arc.
cache.insert(key.clone(), Arc::new(large_value));

// get() will call Arc::clone() on the stored value, which is cheap.
cache.get(&key);

示例:大小感知驱逐

如果不同的缓存条目有不同的“权重” — 例如,每个条目有不同的内存占用 — 您可以在创建缓存时指定一个 weigher 闭包。闭包应返回一个条目的加权大小(相对大小),单位为 u32,并且当总加权大小超过其 max_capacity 时,缓存将驱逐条目。

use std::convert::TryInto;
use mini_moka::sync::Cache;

fn main() {
    let cache = Cache::builder()
        // A weigher closure takes &K and &V and returns a u32 representing the
        // relative size of the entry. Here, we use the byte length of the value
        // String as the size.
        .weigher(|_key, value: &String| -> u32 {
            value.len().try_into().unwrap_or(u32::MAX)
        })
        // This cache will hold up to 32MiB of values.
        .max_capacity(32 * 1024 * 1024)
        .build();
    cache.insert(0, "zero".to_string());
}

请注意,在做出驱逐选择时,不使用加权大小。

示例:过期策略

Mini Moka 支持以下过期策略

  • 生存时间:缓存的条目将在从 insert 开始指定的持续时间后过期。
  • 空闲时间:缓存的条目将在从 getinsert 开始指定的持续时间后过期。

要设置它们,请使用 CacheBuilder

use mini_moka::sync::Cache;
use std::time::Duration;

fn main() {
    let cache = Cache::builder()
        // Time to live (TTL): 30 minutes
        .time_to_live(Duration::from_secs(30 * 60))
        // Time to idle (TTI):  5 minutes
        .time_to_idle(Duration::from_secs( 5 * 60))
        // Create the cache.
        .build();

    // This entry will expire after 5 minutes (TTI) if there is no get().
    cache.insert(0, "zero");

    // This get() will extend the entry life for another 5 minutes.
    cache.get(&0);

    // Even though we keep calling get(), the entry will expire
    // after 30 minutes (TTL) from the insert().
}

关于过期策略的说明

如果配置了超过 1000 年的 time_to_livetime to idle,缓存构建器将引发恐慌。这是为了防止在计算键过期时溢出。

最低支持的Rust版本

Mini Moka 最低支持的 Rust 版本(MSRV)如下

特性 MSRV
默认特性 Rust 1.61.0(2022 年 5 月 19 日)

它将保持至少 6 个月的滚动 MSRV 政策。如果仅启用默认特性,MSRV 将保守更新。当使用其他特性时,MSRV 可能会更新得更频繁,直到最新的稳定版本。在两种情况下,增加 MSRV 都不会被视为 semver 破坏性更改。

开发Mini Moka

运行所有测试

要运行所有测试,包括 README 中的文档测试,请使用以下命令

$ RUSTFLAGS='--cfg skeptic --cfg trybuild' cargo test --all-features

生成文档

$ cargo +nightly -Z unstable-options --config 'build.rustdocflags="--cfg docsrs"' \
    doc --no-deps

鸣谢

咖啡因

Mini Moka 的架构受到了 Java 库 Caffeine 的极大启发。感谢 Ben Manes 和所有 Caffeine 贡献者。

许可证

Mini Moka 可以选择以下其中一种许可证进行分发

  • MIT 许可证
  • Apache 许可证(版本 2.0)

由您选择。

有关详细信息,请参阅 LICENSE-MITLICENSE-APACHE

依赖项

~0.5–5.5MB
~14K SLoC