移动开发中的缓存失效:策略与机制

作者: IT Sectr 发布日期: 2026-06-13 阅读时间: 9 分钟

缓存失效 — 删除或更新缓存中过期数据以确保应用程序接收信息准确性的过程。在移动开发中,缓存失效至关重要:用户期望无需完全重新加载即可获得最新数据。根据 Google Developers, 2025 的数据,正确配置的缓存失效可将网络请求减少 60%,并改善界面响应速度。

要点

  • 缓存失效 — 将数据标记为过期并从源触发其更新的机制。
  • TTL — 最简单的策略,记录的生存时间设置为固定间隔。
  • Write-Through — 数据同时写入缓存和源,保证一致性。
  • Write-Behind — 写入源被延迟,从而提高性能但带来数据丢失风险。
  • Stale-While-Revalidate — 用户立即获得过期数据,同时缓存在后台更新。

什么是缓存失效?

缓存失效 — 取消或更新不再与数据源当前状态匹配的缓存记录的过程。与手动清除整个缓存不同,缓存失效是精准操作的:仅针对那些准确性存疑的数据。

缓存存储数据副本以便快速访问。随着时间的推移,数据库或服务器中的原始数据可能会更改 — 例如,用户更新了个人资料或信息流中出现了新帖子。如果缓存未被失效,应用程序将显示过期信息,这在移动应用中会导致交易错误、显示不正确和信任度下降。

任何缓存失效的主要困难 — 众所周知的谚语 “There are only two hard things in Computer Science: cache invalidation and naming things”。复杂性在于,缓存不知道源何时发生了变化,除非明确告知。

根据 Martin Kleppmann(《Designing Data-Intensive Applications》一书作者,O’Reilly, 2017)的数据,正确的缓存失效需要要么集中通知更改,要么在每次读取时检查准确性的机制 — 性能与一致性之间的权衡。

kotlin
data class CacheEntryT(
    val data: T,
    val expiresAt: Long,
    val version: Int = 0
)

fun CacheT.isValid(key: String): Boolean =
    get(key)?.let { it.expiresAt > currentTimeMillis() && it.version == currentVersion(key) } ?: false

此代码展示了一种简单的方法:如果 TTL 未过期且版本与源中的当前版本匹配,则缓存中的记录被视为有效。版本控制机制 — 避免显示过期数据的可靠方法之一。

为什么移动应用需要缓存失效

数据准确性 — 大多数移动应用的关键要求:社交网络、即时通讯、银行服务、电子商务平台。看到错误账户余额或旧消息的用户会失去对应用程序的信任。

除了用户体验之外,缓存失效还解决了节省流量和电池的问题。移动应用无需定期完全重新加载数据,而只需使更改的记录失效并进行精准加载。根据 Meta Engineering (2024) 的数据,在 Facebook Lite 中引入增量缓存失效使流量消耗减少了 35%,而不会损失内容准确性。

另一个重要方面 — 事务一致性。在带有购物车或预订的应用程序中,使用过期缓存可能导致重复扣款或数据冲突。关键操作后的缓存失效保证下一个请求将读取最新数据。

缓存失效的主要策略

TTL(生存时间)

TTL — 最简单的策略,缓存中的每条记录都有一个固定的生存时间。TTL 到期后,数据被视为过期并在下次读取时删除。TTL 适用于按计划更新的数据 — 例如天气或汇率。缺点:数据在 TTL 间隔内可能不准确。

Write-Through

Write-Through 策略中,每次数据更改都通过缓存:写入同时执行到缓存和源。这保证了缓存始终包含当前版本。缺点 — 写入延迟增加,因为操作直到从源确认才完成。Write-Through 适用于对一致性要求严格的数据:账户余额、订单状态。

Write-Behind(Write-Back)

Write-Behind — 异步写入:数据立即进入缓存,稍后由单独进程写入源。这提供了高写入性能,但在同步前发生故障时带来数据丢失风险。在移动应用中,Write-Behind 通常用于分析、日志和非关键用户操作。

Write-Invalidate

Write-Invalidate — 在数据更改时,不是更新缓存,而是简单地删除(失效)相应记录。下一次读取将检测到缓存未命中并从源加载最新数据。此策略易于实现,并且在读取请求明显多于写入请求时效果良好。

策略读取性能写入性能一致性
TTL弱(可能过期)
Write-Through
Write-Behind弱(可能丢失)
Write-Invalidate强(下次读取时)

策略的选择取决于特定场景的优先级:响应速度、一致性还是资源节约。混合方法 — 例如,收到推送通知时使用 TTL 配合 Write-Invalidate — 提供最佳平衡。

缓存失效在不同缓存级别的工作原理

HTTP 缓存 — 客户端的第一级。浏览器或移动应用程序存储带有 Cache-Control 和 ETag 头的服务器响应。当收到 304 Not Modified 响应或 max-age 到期时发生失效。ETag 允许客户端在不加载完整响应的情况下检查资源的准确性。

应用程序缓存 — 第二级,由代码管理:内存缓存(Android 中的 LRU、LruCache)或磁盘缓存(SQLite、Room、Realm)。这里的失效由开发人员控制。根据 Android Developers (2025) 的数据,正确使用 Room 与 Flow 以及通过触发器进行缓存失效可将 UI 重绘次数减少 40%。

服务器缓存 — 第三级:Redis、Memcached、CDN。在此级别,缓存失效通过 TTL、DEL/PURGE 命令或消息代理(RabbitMQ、Kafka)进行。CDN 失效 — 一个独立的任务:由于 CDN 的分布式特性,清除命令可能需要几分钟才能传播。根据 Cloudflare (2024) 的数据,通过 Purge by URL 进行的缓存失效平均需要 5-15 秒才能全球传播。

为了协调所有级别的缓存失效,使用 集中式缓存服务 或事件代理。当数据更改时,源发布一个事件,每个级别收到使特定键失效的命令。这防止了一个级别已更新数据而另一级别继续提供过期版本的情况。

缓存失效中的典型错误

TTL 过长 — 最常见的错误。开发人员 “有保留地” 设置 TTL,导致用户数小时或数天看到过期数据。解决方案:从短 TTL(1-5 分钟)开始,仅在测量实际需求后增加。

单次更改导致整个缓存失效 — 微服务架构中的典型问题。一个用户更新了头像,缓存对所有人生效。当用户数量大时,这会导致 缓存雪崩 效应 — 对源的请求雪崩。解决方案:仅使特定用户的键失效,而不是整个缓存。

写入错误时缺乏缓存失效 — 如果写入源失败而缓存已更新,应用程序将处于不一致状态。解决方案:两阶段失效 — 先清除缓存,然后写入源,出错时回滚失效。

忽略分布式特性 — 在集群环境中,一个节点上的缓存失效并不意味着其他节点收到了命令。如果没有事件代理,部分服务器将继续提供过期数据。Redis Pub/Sub 或 Apache Kafka 通过传播缓存失效事件解决此问题。

如何选择缓存失效策略

确定准确性要求 — 数据 “立即” 新鲜有多关键。对于新闻推送,1-2 分钟的延迟是可以接受的(TTL)。对于账户余额 — 延迟不可接受(Write-Through)。

评估更改频率 — 每天更新一次的数据(产品目录、城市指南)使用 TTL 效果很好。每秒变化数十次的数据(在线状态、汇率)需要通过 WebSocket 或 Firebase Cloud Messaging 进行推送失效。

考虑读取源的成本 — 如果源是涉及 10 个表的昂贵 SQL 查询或带有限制的外部 API,最好使用长 TTL 的积极缓存,但通过推送失效补偿过期数据。如果读取成本低(内存查找),可以使用短 TTL 和 Write-Invalidate。

根据 Google I/O (2025) 的数据,移动应用的典型模式是 Stale-While-Revalidate:用户立即看到缓存数据,应用程序在后台检查其准确性并更新。这结合了响应速度和准确性而无折衷。HTTP 头 Cache-Control 带有 stale-while-revalidate 指令从 Android 10 和 iOS 13 开始得到支持。

常见问题

缓存失效和缓存清除有何区别?

失效 — 将特定记录标记为过期,之后在下次读取时更新。缓存清除是删除所有记录,这更昂贵且可能暂时降低应用程序性能。

ETag 如何实现缓存失效?

ETag — 是服务器在 HTTP 头中返回的资源的哈希或版本。在重复请求时,客户端发送带有当前 ETag 的 If-None-Match。如果资源未更改,服务器响应 304 Not Modified,缓存保持有效。

哪种缓存失效策略最可靠?

Write-Through 配合版本控制 — 最可靠,因为数据始终一致。但写入延迟最大。实践中,更常使用带推送失效的 TTL 以平衡性能和准确性。

如何在缓存失效时避免缓存雪崩?

使用 概率提前过期 — 每个请求在 TTL 到期前随机检查缓存的准确性。XFetch 算法(Vattani, 2015)根据公式计算重新计算的概率:p = (ttl - age) / (ttl * beta)

如何在移动应用中测试缓存失效?

使用 网络调试工具:Charles Proxy、Proxyman 或 Android Studio 和 Xcode 中内置的 Network Inspector。检查数据修改后,下一个请求是否确实加载了新版本,而不是返回缓存版本。

总结

  • 缓存失效 — 删除或更新过期数据以确保读取时准确性的机制。
  • TTL — 设置记录的固定生存时间;简单,但允许间隔内存在过期数据。
  • Write-Through — 写入同时进入缓存和源,保证完全一致性。
  • Write-Behind — 写入缓存后异步写入源;提高速度但带来丢失风险。
  • Stale-While-Revalidate — 显示缓存数据,后台更新;Google 推荐用于移动应用。
  • 推送失效 通过 FCM 或 WebSocket — 无需轮询即可立即清除客户端缓存的唯一方法。
  • 策略选择是 准确性性能 和读取源 成本 之间的权衡。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读