SuperAPI AI 工具集

ttl-keyed-json-cache-lenient-decode

编程与开发

查看项目

更新于 10-17 16:16

技能介绍

该页是 maxrave-dev/kotlin-footguns 系列中的一个条目,主题是一个“带 TTL 的键值 JSON 缓存 + 宽松解码”的 Kotlin 实现示例。它不是可安装的库,也没有发布包或命令,正文围绕一段代码样例展开,说明这种缓存如何工作、哪些地方容易出错、以及如何验证。

用途 用于缓存那些会缓慢变化、但每次打开都重新解析又显得浪费的键控数据。示例场景是获取分类的封面图(artwork):每个键对应一个 URL,并记录抓取时间,用 TTL 常量判断条目是否过期。样例中 TTL 为 7 天(7L * 24 * 60 * 60 * 1000),缓存本身以 Map<String, CachedArtwork> 序列化成单个 JSON 字符串存入偏好存储——偏好存储只存字符串,因此整份缓存就是一个 blob,每次查找都要解码全部内容。

主要能力 1)条目级过期:CachedArtwork(url, cachedAt) 的 isStale() 用当前时间减 cachedAt 与 TTL 比较,过期是值的属性而非整个文件的属性。2)命中即短路:未过期命中直接 emit 并 return,完全不发请求。3)未命中则远程解析:remote.category(key) 得到结果后经 pickThumbnailUrl 提取缩略图 URL,emit 后若非空则把新条目并入缓存并写回。4)流式接口与 IO 调度:返回 Flow<String?>,用 flowOn(Dispatchers.IO)。5)宽松解码:Json { ignoreUnknownKeys = true },读取时用 runCatching 解码失败降级为空 Map。

使用方式 代码样例展示的调用链是:getCategoryArtwork(key) → readArtworkCache()(store.artworkCache.first() 取字符串,解码失败或为空返回 emptyMap)→ 查命中与过期 → 命中且新鲜直接返回 → 否则请求远程 → 写回 cacheJson.encodeToString(cache + (key to CachedArtwork(...)))。页面明确说这是“改编版”,名字做了泛化,pickThumbnailUrl 代替了源码中六行逻辑,且该名字是虚构的。

陷阱(页面重点)

  • 宽松标志与 catch 覆盖的是两种不同的模式变化:ignoreUnknownKeys 只处理“存档 JSON 里有、类上已删除”的键;新增字段会让旧条目缺字段而解码失败,改名等于删除加新增,同样失败。真正兜底的是 runCatching{...}.getOrNull() 和 .orEmpty(),把失败变成空 Map(整体未命中、重新解析一次、不崩溃)。建议两者都保留,并给新属性默认值。
  • 合并回写是无锁的读改写:两个并发解析各自解码同一基础 Map 再各自写回,后写覆盖先写,丢一个条目,代价是一次请求而非正确性;若常同时解析多个键,应合并成一次写入。
  • 没有淘汰:条目只有在自己的键再次被请求时才被替换,从未再被请求的键永久占位,blob 随不同键数量增长,每次查找都要解码全部;几十条可接受,上千条不合适,键空间无界时应改用带索引的表。
  • 过期依赖系统时钟:时钟回拨会让 now - cachedAt 变负,所有条目永久“新鲜”直到真实时间追上;时钟前跳则全部同时过期。对装饰性数据可不防御,但用于不可陈旧的数据前需知晓。
  • 解析结果为空不会被缓存:写回被 if (resolved != null) 保护,无结果的键每次都重新请求;若未命中常见,应把 null 值连同真实时间戳一起存。
  • 新鲜命中先于请求返回,即不是“先给陈旧再校验”的策略;对变化以 TTL 为量级的数据合适,但用户期望最新数据时不适用;cache-then-network 是另一种策略,可在同一仓库中共存,只要每个方法的选择是有意的。

适用场景与限制 适合条目数量有限、变化缓慢、可容忍偶发重复请求的键控缓存;不适合键空间无界、并发写频繁、或不允许出现陈旧数据的场景。页面未提供该实现的安装方式、依赖坐标、许可证或性能数据,这些暂未提供。

验证思路

  • 过期:写入 cachedAt = now - TTL - 1 的条目,断言会发起请求;不要真等 7 天,也不要在测试里改常量。
  • 回退:存入 "{" 或上一版本的 JSON 形状,断言读取返回空 Map 且调用方重新解析;只存格式正确的当前形状 JSON 的测试,在没有 catch 的版本上也会通过。
  • 增长:解析 N 个不同键后,断言解码出的 Map 有 N 个条目,读改写竞态造成的静默覆盖只会在这里暴露。