← 中文资源

简体中文版本

Moka 内存 TTL 缓存

用 moka 构建内存缓存:条目级 TTL 控制新鲜度、容量按权重淘汰冷键、手动先查后填与原子自动加载两种模式并存。

阅读英文原文

示例说明

moka 是并发友好的同步缓存:分段锁设计让高频读写几乎无争用。TTL 与最大容量组合成完整的生命周期策略——条目要么到期要么被权重淘汰;weigher 按条目实际占用赋权,重量级值会更快触发淘汰。try_get_with 的原子加载保证并发未命中时只有一个任务真正执行加载器,其余等待同一结果。

技术要点

  • TTL 与容量双重生命周期约束
  • weigher 按实际占用赋权淘汰
  • try_get_with 原子防击穿加载

代码解读

build_cache 组合三十秒 TTL、一万容量与按费率条数计权的 weigher;手动模式演示先查后填的经典旁路缓存;自动模式用 try_get_with 把键与加载 future 一起传入——未命中时原子执行,两次演示互不重复。entry_count 与 weighted_size 输出运行期指标。

使用建议

多实例部署时进程内缓存口径独立,强一致需求外置 Redis。加载器失败会让 try_get_with 返回错误,调用方要有降级路径。TTL 按数据可容忍陈旧度设定。

代码示例

use moka::future::Cache;
use std::time::Duration;

#[derive(Debug, Clone)]
struct ExchangeRates {
    base: String,
    rates: Vec<(String, f64)>,
}

async fn fetch_rates(base: &str) -> ExchangeRates {
    // 模拟外部 API 的网络往返
    tokio::time::sleep(Duration::from_millis(50)).await;
    ExchangeRates {
        base: base.into(),
        rates: vec![
            ("usd".into(), 1.0),
            ("eur".into(), 0.92),
            ("jpy".into(), 149.6),
        ],
    }
}

fn build_cache() -> Cache<String, ExchangeRates> {
    Cache::builder()
        .time_to_live(Duration::from_secs(30)) // 条目级 TTL
        .max_capacity(10_000)
        .weigher(|_key, value: &ExchangeRates| -> u32 {
            value.rates.len() as u32
        })
        .build()
}

#[tokio::main]
async fn main() {
    let cache = build_cache();

    // 手动模式:先查后填
    if let Some(hit) = cache.get("base-eur").await {
        println!("cache hit: {:?} entries", hit.rates.len());
    } else {
        let fresh = fetch_rates("eur").await;
        cache.insert("base-eur".into(), fresh).await;
        println!("cache miss: loaded and inserted");
    }

    // 自动加载模式:未命中时原子执行加载器
    let auto = Cache::builder()
        .time_to_live(Duration::from_secs(60))
        .build();

    let rates = auto
        .try_get_with(
            "base-jpy",
            fetch_rates("jpy"),
        )
        .await
        .expect("loader should not fail");

    println!("auto-loaded {} rates for {}", rates.rates.len(), rates.base);

    println!("entry count: {}", auto.entry_count());
    println!("cached weight: {}", auto.weighted_size());
}