moka

moka

Rust高性能并发缓存库 优化的缓存替换算法

Moka是一个快速、并发的Rust缓存库,基于哈希表实现。它支持高并发检索和更新,采用先进的缓存替换算法实现容量限制。Moka提供同步和异步缓存、基于数量或权重的边界、过期策略和驱逐监听等功能,适用于需要高性能缓存的Rust项目。

Moka并发缓存Rust线程安全高性能Github开源项目

Moka

[![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] FOSSA 状态

注意 v0.12.0 对API和内部行为进行了重大的破坏性更改。请阅读 MIGRATION-GUIDE.md 了解详细信息。


Moka 是一个快速的、并发的 Rust 缓存库。Moka 的灵感来自于 Java 的 [Caffeine][caffeine-git] 库。

Moka 在哈希映射之上提供缓存实现。它们支持完全并发的检索操作,并且对更新操作具有高度的预期并发性。

所有缓存都尽最大努力通过使用条目替换算法来限制哈希映射的大小,以确定在超出容量时要驱逐哪些条目。

特性

Moka 提供了丰富而灵活的功能集,同时保持高命中率和高并发访问级别。

  • 线程安全、高度并发的内存缓存实现:
    • 可以在操作系统线程间共享的同步缓存。
    • 异步(支持 futures)缓存。
  • 缓存可以通过以下方式之一进行限制:
    • 最大条目数。
    • 条目的总加权大小。(大小感知驱逐)
  • 通过使用受 Caffeine 启发的条目替换算法来维持接近最优的命中率:
    • 缓存的准入由最不经常使用(LFU)策略控制。
    • 缓存的驱逐由最近最少使用(LRU)策略控制。
    • [这里提供了更多详细信息和一些基准测试结果][tiny-lfu]。
  • 支持过期策略:
    • 生存时间。
    • 空闲时间。
    • 每个条目的可变过期时间。
  • 支持驱逐监听器,当条目从缓存中移除时将调用回调函数。

为您的用例选择正确的缓存

没有一种缓存实现能够完美适用于所有用例。Moka 是一个复杂的软件,可能对您的用例来说过于复杂。有时更简单的缓存,如 [Mini Moka][mini-moka-crate] 或 [Quick Cache][quick-cache] 可能更适合。

下表显示了不同缓存实现之间的权衡:

特性Moka v0.12Mini Moka v0.10Quick Cache v0.3
线程安全,同步缓存
线程安全,异步缓存
非并发缓存
受最大条目数限制
受条目总加权大小限制
接近最优命中率✅ TinyLFU✅ TinyLFU✅ CLOCK-Pro
每键原子插入(如 get_with 方法)
缓存级过期策略(生存时间和空闲时间)
每个条目可变过期时间
驱逐监听器
无锁并发迭代器
每分片锁定的并发迭代器
性能等Moka v0.12Mini Moka v0.10Quick Cache v0.3
与并发哈希表相比开销小
不使用后台线程❌ → ✅ v0.12 中移除
依赖树小

Moka 在生产环境中的应用

Moka 正在为生产服务以及家用路由器等嵌入式 Linux 设备提供支持。以下是一些亮点:

  • crates.io:官方 crate 注册中心在其 API 服务中使用 Moka 来减少 PostgreSQL 的负载。Moka 为高流量的下载端点维持了[约 85% 的缓存命中率][gh-discussions-51]。 (Moka 使用时间:2021 年 11 月至今)
  • [aliyundrive-webdav][aliyundrive-webdav-git]:这个云盘的 WebDAV 网关可能已部署在数百台家用 Wi-Fi 路由器中,包括使用 32 位 MIPS 或基于 ARMv5TE 的 SoC 的廉价型号。Moka 用于缓存远程文件的元数据。 (Moka 使用时间:2021 年 8 月至今) [gh-discussions-51]: https://github.com/moka-rs/moka/discussions/51 [aliyundrive-webdav-git]: https://github.com/messense/aliyundrive-webdav

最近的变更

注意 v0.12.0 版本对API和内部行为进行了重大破坏性更改。请阅读 MIGRATION-GUIDE.md 了解详情。

目录

支持的平台

Moka应该可以在大多数64位和32位平台上运行,只要该平台支持Rust std库和线程功能。然而,WebAssembly(Wasm)和WASI目标不受支持。

以下平台在CI中进行了测试:

  • Linux 64位(x86_64, arm aarch64)
  • Linux 32位(i646, armv7, armv5, mips)
    • 如果在32位平台上遇到编译错误,请参阅 故障排除

以下平台未在CI中测试,但应该可以运行:

  • macOS(arm64)
  • Windows(x86_64 msvc和gnu)
  • iOS(arm64)

以下平台不受支持:

  • WebAssembly(Wasm)和WASI目标不受支持。 (参见此项目任务
  • nostd环境(没有std库的平台)不受支持。
  • 16位平台不受支持。

使用方法

要将Moka添加到您的依赖项中,请运行以下cargo add命令:

# 使用同步缓存: cargo add moka --features sync # 使用异步缓存: cargo add moka --features future

如果您想在异步运行时(如tokioasync-std)下使用缓存,应该指定future特性。否则,请指定sync特性。

示例:同步缓存

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

缓存条目通过insertget_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_withtry_get_with

示例:异步缓存

异步(支持futures)缓存定义在future模块中。 它可以与异步运行时(如Tokioasync-stdactix-rt)一起使用。 要使用异步缓存,请启用名为"future"的crate特性

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

  • 在异步上下文(async fnasync 块)中,使用 insertinvalidate 方法更新缓存并 await 它们。
  • 在任何异步上下文之外,使用 blocking 方法来访问 insertinvalidate 方法的阻塞版本。

以下是一个与前面示例类似的程序,但使用了异步缓存和 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_withtry_get_with

避免在 get 时克隆值

对于并发缓存(syncfuture 缓存),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 支持以下过期策略:

  • 缓存级过期策略:
    • 缓存级策略适用于缓存中的所有条目。
    • 存活时间(TTL):缓存条目将在从 insert 开始经过指定持续时间后过期。
    • 空闲时间(TTI):缓存条目将在从 getinsert 开始经过指定持续时间后过期。
  • 每条目过期策略:
    • 每条目过期允许你为每个条目设置不同的过期时间。

有关上述策略的详细信息和示例,请参阅文档中的"示例:基于时间的过期"部分(sync::Cachefuture::Cache)。

最低支持的 Rust 版本

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

功能MSRV
默认功能Rust 1.65.0 (2022年11月3日)
futureRust 1.65.0 (2022年11月3日)

它将保持至少 6 个月的滚动 MSRV 政策。如果只启用默认功能,MSRV 将会保守地更新。当使用其他功能(如 future)时,MSRV 可能会更频繁地更新,最高可达最新的稳定版。在这两种情况下,增加 MSRV 都不被视为破坏语义版本的变更。

故障排除

在某些 32 位平台上的编译错误

在以下一些 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 切换到备用实现,从而能够编译。

开发 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
  • API 稳定化。(更小的核心 API,常用方法名称更短)(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
  • 缓存统计(命中率等)。(详情
  • 将 TinyLFU 升级为 Window-TinyLFU。([详情][tiny-lfu])
  • 从快照恢复缓存。(详情

关于名称

Moka 的名字来源于 摩卡壶,一种用蒸汽压力将沸水压入咖啡粉中来冲泡浓缩咖啡的炉灶咖啡壶。

这个名字暗示了以下事实和期望:

  • Moka 是 Java Caffeine 缓存家族的一员。
  • 它是用 Rust 编写的。(许多摩卡壶是由铝合金或不锈钢制成的。我们知道它们不会生锈)
  • 它应该很快。(意大利语中的"Espresso"意味着快速)
  • 它应该像摩卡壶一样易于使用。

致谢

Caffeine

Moka 的架构很大程度上受到 Java 的 [Caffeine][caffeine-git] 库的启发。感谢 Ben Manes 和 Caffeine 的所有贡献者。

cht

moka::cht 模块下的并发哈希表源文件复制自 cht crate v0.4.1 并经我们修改。我们这样做是为了更好地集成。cht v0.4.1 及更早版本在 MIT 许可证下授权。

感谢 Gregory Meyer。

许可证

Moka 根据以下两种许可之一分发:

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

由你选择。

详情请参见 LICENSE-MITLICENSE-APACHE

<!-- [![FOSSA状态](https://yellow-cdn.veclightyear.com/835a84d5/75eadc91-5b94-402f-9740-d49bc33a9cab.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fmoka-rs%2Fmoka?ref=badge_large) -->

编辑推荐精选

TRAE编程

TRAE编程

AI辅助编程,代码自动修复

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

AI工具TraeAI IDE协作生产力转型热门
蛙蛙写作

蛙蛙写作

AI小说写作助手,一站式润色、改写、扩写

蛙蛙写作—国内先进的AI写作平台,涵盖小说、学术、社交媒体等多场景。提供续写、改写、润色等功能,助力创作者高效优化写作流程。界面简洁,功能全面,适合各类写作者提升内容品质和工作效率。

AI辅助写作AI工具蛙蛙写作AI写作工具学术助手办公助手营销助手AI助手
问小白

问小白

全能AI智能助手,随时解答生活与工作的多样问题

问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。

热门AI助手AI对话AI工具聊天机器人
Transly

Transly

实时语音翻译/同声传译工具

Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。

讯飞智文

讯飞智文

一键生成PPT和Word,让学习生活更轻松

讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。

AI办公办公工具AI工具讯飞智文AI在线生成PPTAI撰写助手多语种文档生成AI自动配图热门
讯飞星火

讯飞星火

深度推理能力全新升级,全面对标OpenAI o1

科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。

热门AI开发模型训练AI工具讯飞星火大模型智能问答内容创作多语种支持智慧生活
Spark-TTS

Spark-TTS

一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型

Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。

咔片PPT

咔片PPT

AI助力,做PPT更简单!

咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。

讯飞绘文

讯飞绘文

选题、配图、成文,一站式创作,让内容运营更高效

讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。

热门AI辅助写作AI工具讯飞绘文内容运营AI创作个性化文章多平台分发AI助手
材料星

材料星

专业的AI公文写作平台,公文写作神器

AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。

下拉加载更多