当前位置:首页 > 文章列表 > Golang > Go问答 > Go Ristretto 缓存未满时条目为什么也可能不被接纳

Go Ristretto 缓存未满时条目为什么也可能不被接纳

来源:17golang原创 2026-10-13 17:11:50 0浏览 收藏

Go Ristretto 的缓存成本还没达到 MaxCost,并不代表每一次 Set 都一定能在缓存里找到。Ristretto 为了吞吐量使用异步写入和 TinyLFU admission:新条目可能先在 Set 缓冲阶段被丢弃,也可能在准入阶段因为已有条目的频率估计更有价值而不被接纳。

官方仓库明确说明,新的 Set 可能在经过 Set buffer 或 admission policy 时被丢掉;更新已有条目则有不同保证。排查时要先等待异步路径完成,再看指标区分“还没可见”和“确实被丢弃”。

官方地址:https://github.com/dgraph-io/ristretto

要点速览
  • MaxCost 是成本边界,不是新键的无条件接纳开关。
  • 新条目可能在 Set 缓冲或 TinyLFU admission 阶段被丢弃。
  • Set 是异步路径,立即 Get 不能直接证明准入失败。
  • 先调用 Wait,再结合 SetsDropped 与 SetsRejected 观察写入结果。

先分清缓存未满与条目一定接纳

很多缓存的直觉是“还有空间就放进去”,但 Ristretto 的设计目标不是把每个新键都保存下来,而是用 admission policy 把有限空间留给更可能被再次访问的键。MaxCost 只约束缓存总成本;它回答的是“最多可以占用多少成本”,不回答“当前这个新键是否值得接纳”。

因此,下面两件事可以同时成立:缓存的当前成本还远低于 MaxCost,某个新键却没有进入缓存;另一个已经存在的键再次更新时,写入行为又与新键不同。不要把前一个现象直接归因于容量不足。

观察到的现象更接近的解释第一步怎么做
Set 后立刻 Get 不到异步写入尚未完成,也可能最终被丢弃先调用 Wait
Wait 后新键仍不存在可能在缓冲或 admission 阶段被丢弃读取丢弃/拒绝指标
缓存未满但新键反复进不去TinyLFU 判断其频率价值低,或写入路径持续被丢弃检查访问模式与 cost

拆开 Set 缓冲和 TinyLFU admission 两道关口

可以把新条目想成要经过两道静态边界。第一道是 Set buffer:Ristretto 用批量化减少并发写入的争用,条目在真正进入后续策略前可能已经被丢弃。第二道是 TinyLFU admission:它根据访问频率估计判断新条目是否值得进入有限缓存空间。

Ristretto 中 Set 请求、Set 缓冲、TinyLFU admission、SampledLFU eviction、MaxCost 与缓存存储的静态关系
图1:Ristretto 新条目经过 Set 缓冲与 TinyLFU admission 的结构说明图,不是运行截图。

这里的关键是:MaxCost 主要参与成本和淘汰决策,不会把 TinyLFU admission 变成“只要没满就收”。当条目的访问频率估计较低,而缓存里已有条目的保留价值更高时,新条目可以被拒绝;这正是缓存未满时也可能看不到它的原因之一。

还要注意新键与更新的区别。官方 FAQ 说明,Ristretto 不保证新的 Set 一定进入缓存,但更新已有条目有不同保证。因此,对关键状态不能把一次针对新键的 Set 当成可靠持久化写入。

用 Wait 与 Metrics 确认观察时机和原因

最小排查代码应该先让写入路径走完,再读取结果。下面的示例使用 Ristretto v2 的泛型配置;注释只解释与本问题有关的异步边界和指标含义。

package main

import (
    "fmt"
    "github.com/dgraph-io/ristretto/v2"
)

func main() {
    cache, err := ristretto.NewCache(&ristretto.Config[string, string]{
        NumCounters: 1000, // 为 admission 和 eviction 保存访问频率计数。
        MaxCost:     100,  // 用统一 cost 表示缓存允许的总成本。
        BufferItems: 64,   // 批量处理 Set/Get,减少并发路径的争用。
    })
    if err != nil { panic(err) }
    defer cache.Close() // 结束时关闭后台资源,避免示例提前退出。

    cache.Set("cold-key", "value", 1) // 新键先进入异步写入路径。
    cache.Wait()                       // 等待缓冲处理完成,再观察最终可见性。
    value, ok := cache.Get("cold-key")
    fmt.Printf("value=%q present=%v\\n", value, ok)
}

Wait 解决的是观察时机,不是强制 admission 接受。它返回后仍然读不到 cold-key,只能说明这次写入没有产生可见条目;要判断是缓冲丢弃还是准入拒绝,还需要看 Metrics。

Ristretto 的指标 API 提供了 SetsDropped、SetsRejected 等计数。下面的函数把它们放到同一组观察里;它不伪造运行结果,实际数值应由服务自己的负载产生。

func printAdmissionMetrics(cache *ristretto.Cache[string, string]) {
    metrics := cache.Metrics
    // 先打开 Metrics,再读取计数;生产环境应在创建 Config 时按需启用指标。
    if metrics == nil {
        fmt.Println("metrics disabled")
        return
    }
    // dropped 更接近缓冲路径丢弃,rejected 更接近 admission 策略拒绝。
    fmt.Printf("sets_dropped=%d sets_rejected=%d\\n",
        metrics.SetsDropped(), metrics.SetsRejected())
}
Ristretto 中 Set、Wait、Get 与 Metrics 指标对异步写入和丢弃原因的静态关系
图2:Wait、Get 与 Metrics 观察 Ristretto 写入结果的结构说明图,不是运行截图。

如果要在排查中建立前后对照,可以在一段固定负载前后分别读取计数,再结合 Wait 后的 Get 结果判断。不要只看缓存当前成本,也不要把单次 Get miss 当成某一个阶段的唯一证据。

按条目热度和成本调整调用策略

理解 admission 之后,处理方式通常不是简单把 MaxCost 调大。更实用的调整顺序如下:

  1. 确认是新键还是更新。如果业务要求更新一个已经存在的缓存条目,先确认调用方使用的是稳定键;键每次变化,Ristretto 会把它当成新条目处理。
  2. 为异步路径留出等待点。在测试或一次性任务里调用 Wait 后再读取;在线服务里不要为了每次请求同步等待,而应把缓存命中视为可选优化。
  3. 检查 cost 是否反映真实代价。如果不同条目的内存或业务成本差异很大,统一写成 1 会让 eviction 的判断失真;但 cost 也不能随意放大,否则少量大条目可能挤占更多空间。
  4. 让热点键有重复访问机会。Ristretto 的 admission 依赖访问频率估计,只有写入一次、之后从不读取的冷键被丢弃并不一定是故障,而可能是策略正在保护热点数据。

如果业务不能接受新条目偶尔不在缓存中,就不能把 Ristretto 当作数据的唯一存储。缓存 miss 必须能回源,回源后再写入;关键数据仍应落在数据库、文件或其他明确的持久化系统中。

常见问题

缓存未满,为什么还会拒绝新键?

因为 admission 关注的是条目的保留价值,不只看剩余容量。Ristretto 的 TinyLFU 会参与新条目是否进入缓存的判断。

调用 Wait 后还读不到,是不是 MaxCost 太小?

不一定。Wait 只能排除部分异步时序影响;还要结合 SetsDropped、SetsRejected、条目的 cost 和访问模式判断。

把 MaxCost 调大能彻底解决吗?

不能保证。容量增大可能减少淘汰,但不会把 admission 规则改成无条件接纳;如果问题是新键冷、写入只发生一次或缓冲路径丢弃,单纯扩容并不对症。

Ristretto 适合保存不能丢的数据吗?

不适合把缓存当唯一存储。Ristretto 的新条目 Set 本来就不保证一定进入缓存,关键数据应保存在有明确持久化语义的系统中。

总结一下:先用 Wait 排除观察过早,再用 Metrics 区分缓冲丢弃与 admission 拒绝,最后根据键的热度和 cost 调整策略。看到“缓存还没满但 Get 不到”时,优先检查 Ristretto 的写入语义,而不是只盯着 MaxCost。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go Ristretto 怎么开启指标并计算缓存命中比例Go Ristretto 怎么开启指标并计算缓存命中比例
上一篇
Go Ristretto 怎么开启指标并计算缓存命中比例
Go Ristretto Set 返回后立刻 Get 为什么可能取不到
下一篇
Go Ristretto Set 返回后立刻 Get 为什么可能取不到
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    460次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    522次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    541次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    488次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    307次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码