菜单

缓存

相关源文件

本页面涵盖缓存系统、其架构、实现策略和使用模式。缓存是一种基本的优化技术,通过将频繁访问的数据存储在内存或其他快速访问存储位置来提高性能。有关数据存储的相关主题,请参阅数据库;有关内容交付优化,请参阅CDN

概述

缓存通过将数据副本临时存储在比原始源更快检索的位置,从而缩短页面加载时间并减少服务器和数据库的负载。这种优化在分布式系统中尤为宝贵,因为数据访问延迟会显著影响性能。

来源

  • README.md:1132-1143

为何使用缓存

缓存解决了几个关键的性能挑战

  1. 降低延迟 - 从内存检索数据比从磁盘或网络检索数据快得多
  2. 减轻数据库负载 - 更少的查询到达数据库,使其能够处理更多的写入操作
  3. 缓解流量峰值 - 缓存可以吸收突发的流量增长
  4. 分担不均匀负载 - 热门项目可能导致热点;缓存有助于吸收不均匀负载

数据库通常受益于跨分区读写操作的均匀分布。热门项目可能会使这种分布倾斜,从而产生瓶颈。在数据库前放置缓存有助于吸收这些不均匀的负载和流量峰值。

来源

  • README.md:1140-1143

缓存位置

缓存可以在应用程序堆栈的不同层实现,每一层都有其自身的优势和用例。

来源

  • README.md:1144-1162

客户端缓存

缓存可以位于客户端,在操作系统或浏览器内部。客户端缓存通过在本地存储资源来减少网络请求和服务器负载。

  • 浏览器缓存存储HTML、CSS、JavaScript和图像文件
  • 操作系统级缓存存储DNS解析和文件系统数据
  • 移动应用程序实现本地存储和数据缓存

来源

  • README.md:1145-1147

CDN缓存

内容分发网络(CDN)充当分布式缓存系统,在多个地理位置存储静态内容的副本。它们通过从物理上更接近用户的位置提供内容来减少延迟。

  • 服务静态资产(图像、视频、CSS、JavaScript)
  • 减少源服务器负载
  • 提高全球访问速度
  • 某些CDN也可以缓存动态内容

来源

  • README.md:1149-1150

Web服务器缓存

Web服务器可以缓存静态内容和动态响应,以避免重复处理相同的请求。

  • 像Varnish这样的反向代理可以直接提供静态和动态内容
  • Web服务器可以缓存请求并返回响应,而无需联系应用程序服务器
  • 通过缓存控制头和服务器配置进行配置

来源

  • README.md:1152-1155

数据库缓存

数据库系统包含为通用用例优化的内置缓存机制。

  • 查询缓存存储频繁查询的结果
  • 缓冲区缓存将最近访问的数据块保留在内存中
  • 针对读密集型工作负载进行优化
  • 可以针对特定的工作负载和访问模式进行调优

来源

  • README.md:1157-1159

应用程序缓存

应用程序缓存涉及位于应用程序逻辑和持久存储之间的内存数据存储。

主要的内存缓存技术包括

技术主要功能
Redis持久化选项、数据结构(有序集合、列表)、发布/订阅
Memcached简单的键值接口、分布式哈希表、仅内存

这些内存缓存将数据保存在RAM中,这比典型数据库中存储在磁盘上的数据快得多。由于RAM比磁盘存储更有限,因此LRU(最近最少使用)等缓存失效算法通过删除不常用的条目来为频繁访问的数据腾出空间,从而帮助管理缓存。

来源

  • README.md:1161-1168

缓存级别

缓存可以在不同的粒度级别实现,通常分为两类:数据库查询缓存和对象缓存。

数据库查询级缓存

数据库查询缓存使用查询本身作为键来存储整个查询的结果

key = hash("SELECT * FROM users WHERE id = 123")
cache_set(key, query_results)

这种方法存在一些挑战

  • 当涉及复杂查询时难以使缓存失效
  • 当一部分数据发生变化时,所有包含该数据的缓存查询都需要失效
  • 当热门键过期时可能导致缓存雪崩

来源

  • README.md:1130-1135

对象级缓存

对象级缓存将数据视为应用程序对象,而不是原始查询结果

  • 应用程序将数据库中的数据组装成类实例或数据结构
  • 当缓存对象的数据发生变化时,这些对象将被移除
  • 异步工作器可以收集并组装最新的缓存对象
  • 与应用程序逻辑更一致,而非查询缓存

常见的缓存对象

  • 用户会话
  • 完全渲染的网页
  • 活动流
  • 用户图数据

来源

  • README.md:1137-1149

缓存更新模式

有效管理缓存更新对于保持数据一致性并最大化性能优势至关重要。可以根据用例采用几种模式。

来源

  • README.md:1150-1257

旁路缓存(延迟加载)

在旁路缓存模式中,当发生缓存未命中时,应用程序负责从数据库获取数据并填充缓存。

示例实现

优点

  • 缓存仅包含请求的数据
  • 对缓存故障具有弹性(系统仍可在性能下降的情况下运行)

缺点

  • 缓存未命中时需要三次行程,增加了延迟
  • 如果数据库发生变化,数据可能会过时
  • 重启或故障后冷缓存会增加延迟

来源

  • README.md:1154-1189

直写

在直写模式中,应用程序将数据写入缓存,然后缓存同步更新数据库。

示例实现

优点

  • 缓存中的数据永不过时
  • 写入操作最终一致
  • 针对写后读场景进行优化

缺点

  • 同时写入缓存和数据库比单独写入缓存慢
  • 数据可能永远不会被读取但仍然需要写入(写入惩罚)
  • 缓存预热时仍会发生缓存未命中

来源

  • README.md:1191-1224

回写

在回写模式中,应用程序将数据写入缓存,然后缓存异步更新数据库,从而提高写入性能。

优点

  • 通过批量写入数据库来提高写入性能
  • 高效处理写密集型工作负载
  • 在高峰期减少数据库负载

缺点

  • 如果在数据库更新之前缓存失败,存在数据丢失的风险
  • 实现更复杂
  • 潜在的数据一致性问题

来源

  • README.md:1226-1242

预加载刷新

在预加载刷新模式中,缓存会在常用项目过期之前自动刷新它们,从而减少未来请求的延迟。

优点

  • 减少常用项目的延迟
  • 对于可预测的访问模式没有缓存未命中
  • 缓存可以在低需求期间刷新

缺点

  • 缓存管理复杂性增加
  • 如果预测不准确,可能浪费资源
  • 如果配置不当,可能会增加数据库负载

来源

  • README.md:1244-1257

常见的缓存问题和解决方案

缓存失效

缓存失效是从缓存中删除或更新过期数据的过程。它是缓存最具挑战性的方面之一。

策略包括

  • 基于时间的过期:为缓存条目设置TTL(存活时间)
  • 基于事件的失效:当底层数据更改时移除条目
  • 基于版本的失效:维护缓存数据的版本号

缓存一致性

维护缓存与真实数据源(数据库)之间的一致性可能具有挑战性,尤其是在分布式系统中。

管理一致性的方法

  • 强一致性:确保缓存和数据库始终同步(通常伴随性能损失)
  • 最终一致性:允许暂时性不一致,这些不一致会随着时间推移而解决
  • 读己所写一致性:确保用户始终看到自己的更新

缓存穿透

缓存穿透发生在攻击者反复请求不存在的数据时,绕过缓存直接访问数据库。

缓解措施

  • 缓存负面结果(即不存在的数据)并设置较短的TTL
  • 实现布隆过滤器以快速检查键是否存在
  • 对持续未命中缓存的查询进行限流

缓存雪崩

缓存雪崩(或缓存崩溃)发生在许多并发请求在缓存条目过期后尝试重新生成同一条目时。

缓解措施

  • 实现错开的过期时间
  • 使用信号量只允许一个进程重建缓存
  • 在过期前实现后台刷新

来源

  • README.md:1260-1273

缓存的缺点

虽然缓存提供了显著的性能优势,但它也带来了挑战

  1. 复杂性 - 增加缓存层会增加系统复杂性
  2. 一致性管理 - 维护缓存与真实数据源之间的一致性需要仔细设计
  3. 缓存失效挑战 - 确定何时更新或使缓存条目失效可能很困难
  4. 额外的基础设施 - 需要部署和维护额外的服务
  5. 内存限制 - 内存缓存受可用RAM的限制
  6. 监控开销 - 需要监控缓存性能和命中率

来源

  • README.md:1260-1264

总结

缓存是一种强大的技术,用于提高系统性能和减轻后端服务的负载。通过在不同级别策略性地实现缓存并采用适当的更新模式,应用程序可以在管理复杂性和一致性权衡的同时,实现显著的性能提升。

缓存策略的选择应考虑

  • 应用程序的读/写模式
  • 一致性要求
  • 数据访问模式
  • 资源限制
  • 运维复杂性承受能力

有效的缓存需要持续的监控和改进,以确保随着应用程序使用模式的演变而保持最佳性能。

来源

  • README.md:1132-1273