[![GitHub Actions][gh-actions-badge]][gh-actions]
[![crates.io 发布][release-badge]][crate]
[![文档][docs-badge]][docs]
[![依赖状态][deps-rs-badge]][deps-rs]
[![codecov][codecov-badge]][codecov]
![许可证][license-badge]
注意
v0.12.0
对API和内部行为进行了重大的破坏性更改。请阅读 MIGRATION-GUIDE.md 了解详细信息。
Moka 是一个快速的、并发的 Rust 缓存库。Moka 的灵感来自于 Java 的 [Caffeine][caffeine-git] 库。
Moka 在哈希映射之上提供缓存实现。它们支持完全并发的检索操作,并且对更新操作具有高度的预期并发性。
所有缓存都尽最大努力通过使用条目替换算法来限制哈希映射的大小,以确定在超出容量时要驱逐哪些条目。
Moka 提供了丰富而灵活的功能集,同时保持高命中率和高并发访问级别。
没有一种缓存实现能够完美适用于所有用例。Moka 是一个复杂的软件,可能对您的用例来说过于复杂。有时更简单的缓存,如 [Mini Moka][mini-moka-crate] 或 [Quick Cache][quick-cache] 可能更适合。
下表显示了不同缓存实现之间的权衡:
特性 | Moka v0.12 | Mini Moka v0.10 | Quick Cache v0.3 |
---|---|---|---|
线程安全,同步缓存 | ✅ | ✅ | ✅ |
线程安全,异步缓存 | ✅ | ❌ | ✅ |
非并发缓存 | ❌ | ✅ | ✅ |
受最大条目数限制 | ✅ | ✅ | ✅ |
受条目总加权大小限制 | ✅ | ✅ | ✅ |
接近最优命中率 | ✅ TinyLFU | ✅ TinyLFU | ✅ CLOCK-Pro |
每键原子插入(如 get_with 方法) | ✅ | ❌ | ✅ |
缓存级过期策略(生存时间和空闲时间) | ✅ | ✅ | ❌ |
每个条目可变过期时间 | ✅ | ❌ | ❌ |
驱逐监听器 | ✅ | ❌ | ❌ |
无锁并发迭代器 | ✅ | ❌ | ❌ |
每分片锁定的并发迭代器 | ❌ | ✅ | ❌ |
性能等 | Moka v0.12 | Mini Moka v0.10 | Quick Cache v0.3 |
---|---|---|---|
与并发哈希表相比开销小 | ❌ | ❌ | ✅ |
不使用后台线程 | ❌ → ✅ v0.12 中移除 | ✅ | ✅ |
依赖树小 | ❌ | ✅ | ✅ |
Moka 正在为生产服务以及家用路由器等嵌入式 Linux 设备提供支持。以下是一些亮点:
注意
v0.12.0
版本对API和内部行为进行了重大破坏性更改。请阅读 MIGRATION-GUIDE.md 了解详情。
get
时克隆值Moka应该可以在大多数64位和32位平台上运行,只要该平台支持Rust std
库和线程功能。然而,WebAssembly(Wasm)和WASI目标不受支持。
以下平台在CI中进行了测试:
以下平台未在CI中测试,但应该可以运行:
以下平台不受支持:
nostd
环境(没有std
库的平台)不受支持。要将Moka添加到您的依赖项中,请运行以下cargo add
命令:
# 使用同步缓存: cargo add moka --features sync # 使用异步缓存: cargo add moka --features future
如果您想在异步运行时(如tokio
或async-std
)下使用缓存,应该指定future
特性。否则,请指定sync
特性。
线程安全的同步缓存定义在sync
模块中。
缓存条目通过insert
或get_with
方法手动添加,并存储在缓存中,直到被驱逐或手动失效。
以下是一个使用多线程读取和更新缓存的示例:
// 使用同步缓存。 use 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; // 创建一个可以存储多达10,000个条目的缓存。 let cache = Cache::new(10_000); // 生成线程并同时读取和更新缓存。 let threads: Vec<_> = (0..NUM_THREADS) .map(|i| { // 要在线程间共享相同的缓存,克隆它。 // 这是一个开销很小的操作。 let my_cache = cache.clone(); let start = i * NUM_KEYS_PER_THREAD; let end = (i + 1) * NUM_KEYS_PER_THREAD; thread::spawn(move || { // 插入64个条目。(NUM_KEYS_PER_THREAD = 64) for key in start..end { my_cache.insert(key, value(key)); // get()返回Option<String>,是存储值的克隆。 assert_eq!(my_cache.get(&key), Some(value(key))); } // 使插入条目中的每第4个元素失效。 for key in (start..end).step_by(4) { my_cache.invalidate(&key); } }) }) .collect(); // 等待所有线程完成。 threads.into_iter().for_each(|t| t.join().expect("Failed")); // 验证结果。 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))); } } }
您可以通过克隆仓库并运行以下cargo指令来尝试同步示例:
$ cargo run --example sync_example
如果您想在键不存在时原子地初始化和插入值,可以查看文档中的其他插入方法get_with
和try_get_with
。
异步(支持futures)缓存定义在future
模块中。
它可以与异步运行时(如Tokio、
async-std或actix-rt)一起使用。
要使用异步缓存,请启用名为"future"的crate特性。
缓存条目通过插入方法手动添加,并存储在缓存中,直到被驱逐或手动失效:
async fn
或 async
块)中,使用 insert
或 invalidate
方法更新缓存并 await
它们。blocking
方法来访问 insert
或 invalidate
方法的阻塞版本。以下是一个与前面示例类似的程序,但使用了异步缓存和 Tokio 运行时:
// Cargo.toml // // [dependencies] // moka = { version = "0.12", features = ["future"] } // tokio = { version = "1", features = ["rt-multi-thread", "macros" ] } // futures-util = "0.3" // 使用异步缓存。 use moka::future::Cache; #[tokio::main] async fn main() { const NUM_TASKS: usize = 16; const NUM_KEYS_PER_TASK: usize = 64; fn value(n: usize) -> String { format!("value {n}") } // 创建一个可以存储多达 10,000 个条目的缓存。 let cache = Cache::new(10_000); // 生成异步任务并对缓存进行写入和读取。 let tasks: Vec<_> = (0..NUM_TASKS) .map(|i| { // 要在异步任务之间共享相同的缓存,请克隆它。 // 这是一个开销很小的操作。 let my_cache = cache.clone(); let start = i * NUM_KEYS_PER_TASK; let end = (i + 1) * NUM_KEYS_PER_TASK; tokio::spawn(async move { // 插入 64 个条目。(NUM_KEYS_PER_TASK = 64) for key in start..end { // insert() 是一个异步方法,所以要 await 它。 my_cache.insert(key, value(key)).await; // get() 返回 Option<String>,是存储值的克隆。 assert_eq!(my_cache.get(&key).await, Some(value(key))); } // 使插入条目中的每第 4 个元素失效。 for key in (start..end).step_by(4) { // invalidate() 是一个异步方法,所以要 await 它。 my_cache.invalidate(&key).await; } }) }) .collect(); // 等待所有任务完成。 futures_util::future::join_all(tasks).await; // 验证结果。 for key in 0..(NUM_TASKS * NUM_KEYS_PER_TASK) { if key % 4 == 0 { assert_eq!(cache.get(&key).await, None); } else { assert_eq!(cache.get(&key).await, Some(value(key))); } } }
你可以通过克隆仓库并运行以下 cargo 指令来尝试异步示例:
$ cargo run --example async_example --features future
如果你想在键不存在时原子地初始化并插入一个值,你可能想查看文档中的其他插入方法 get_with
和 try_get_with
。
get
时克隆值对于并发缓存(sync
和 future
缓存),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 // 插入时,用 Arc 包装 large_value。 cache.insert(key.clone(), Arc::new(large_value)); // get() 将对存储的值调用 Arc::clone(),这个操作开销很小。 cache.get(&key);
如果不同的缓存条目有不同的"权重"——例如,每个条目有不同的内存占用——你可以在创建缓存时指定一个 weigher
闭包。该闭包应返回一个条目的加权大小(相对大小)作为 u32
,当总加权大小超过其 max_capacity
时,缓存将驱逐条目。
use moka::sync::Cache; fn main() { let cache = Cache::builder() // weigher 闭包接受 &K 和 &V,并返回一个表示条目相对大小的 u32。 // 这里,我们使用值 String 的字节长度作为大小。 .weigher(|_key, value: &String| -> u32 { value.len().try_into().unwrap_or(u32::MAX) }) // 这个缓存将最多保存 32MiB 的值。 .max_capacity(32 * 1024 * 1024) .build(); cache.insert(0, "zero".to_string()); }
注意,加权大小不用于做驱逐选择。
你可以通过克隆仓库并运行以下 cargo 指令来尝试大小感知驱逐示例:
$ cargo run --example size_aware_eviction
Moka 支持以下过期策略:
insert
开始经过指定持续时间后过期。get
或 insert
开始经过指定持续时间后过期。有关上述策略的详细信息和示例, 请参阅文档中的"示例:基于时间的过期"部分(sync::Cache
,future::Cache
)。
Moka 支持的最低 Rust 版本(MSRV)如下:
功能 | MSRV |
---|---|
默认功能 | Rust 1.65.0 (2022年11月3日) |
future | Rust 1.65.0 (2022年11月3日) |
它将保持至少 6 个月的滚动 MSRV 政策。如果只启用默认功能,MSRV 将会保守地更新。当使用其他功能(如 future
)时,MSRV 可能会更频繁地更新,最高可达最新的稳定版。在这两种情况下,增加 MSRV 都不被视为破坏语义版本的变更。
在以下一些 32 位目标平台上,你可能会遇到编译错误:
armv5te-unknown-linux-musleabi
mips-unknown-linux-musl
mipsel-unknown-linux-musl
error[E0432]: unresolved import `std::sync::atomic::AtomicU64` --> ... /moka-0.5.3/src/sync.rs:10:30 | 10 | atomic::{AtomicBool, AtomicU64, Ordering}, | ^^^^^^^^^ | | | no `AtomicU64` in `sync::atomic`
这些错误可能发生是因为这些平台不提供 std::sync::atomic::AtomicU64
,但 Moka 使用了它。
你可以通过禁用 atomic64
功能来解决这些错误,这是 Moka 的默认功能之一。编辑你的 Cargo.toml,在依赖声明中添加 default-features = false
。
[dependencies] moka = { version = "0.12", default-features = false, features = ["sync"] } # 或 moka = { version = "0.12", default-features = false, features = ["future"] }
这将使 Moka 切换到备用实现,从而能够编译。
运行所有测试
要运行包括 future
功能和 README 中的文档测试在内的所有测试,使用以下命令:
$ RUSTFLAGS='--cfg trybuild' cargo test --all-features
在不启用默认功能的情况下运行所有测试
$ RUSTFLAGS='--cfg trybuild' cargo test \ --no-default-features --features 'future, sync'
生成文档
$ cargo +nightly -Z unstable-options --config 'build.rustdocflags="--cfg docsrs"' \ doc --no-deps --features 'future, sync'
查看项目路线图以获取最新和详细的计划。
以下是一些亮点:
v0.7.0
通过 #24)v0.8.0
通过 #105)
get_or_insert_with(K, F)
→ get_with(K, F)
get_or_try_insert_with(K, F)
→ try_get_with(K, F)
time_to_live()
→ policy().time_to_live()
v0.9.0
通过 #145)v0.11.0
通过 #248)v0.12.0
通过 #294 和 #316)v0.12.3
通过 #370)Moka 的名字来源于 摩卡壶,一种用蒸汽压力将沸水压入咖啡粉中来冲泡浓缩咖啡的炉灶咖啡壶。
这个名字暗示了以下事实和期望:
Moka 的架构很大程度上受到 Java 的 [Caffeine][caffeine-git] 库的启发。感谢 Ben Manes 和 Caffeine 的所有贡献者。
moka::cht
模块下的并发哈希表源文件复制自 cht crate v0.4.1 并经我们修改。我们这样做是为了更好地集成。cht v0.4.1 及更早版本在 MIT 许可证下授权。
感谢 Gregory Meyer。
Moka 根据以下两种许可之一分发:
由你选择。
详情请参 见 LICENSE-MIT 和 LICENSE-APACHE。
<!-- [](https://app.fossa.com/projects/git%2Bgithub.com%2Fmoka-rs%2Fmoka?ref=badge_large) -->AI辅助编程,代码自动修复
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。
AI小说写作助手,一站式润色、改写、扩写
蛙蛙写作—国内先进的AI写作平台,涵盖小说、学术、社交媒体等多场景。提供续写、改写、润色等功能,助力创作者高效优化写作流程。界面简洁,功能全面,适合各类写作者提升内容品质和工作效率。
全能AI智能助手,随时解答生活与工作的多样问题
问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。
实时语音翻译/同声传译工具
Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。
一键生成PPT和Word,让学习生活更轻松
讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。
深度推理能力全新升级,全面对标OpenAI o1
科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。
一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型
Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。
AI助力,做PPT更简单!
咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。
选题、配图、成文,一站式创作,让内容运营更高效
讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。
专业的AI公文写作平台,公文写作神器
AI 材料星,专业的 AI 公文写 作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。
最新AI工具、AI资讯
独家AI资源、AI项目落地
微信扫一扫关注公众号