缓存
学习缓存基础知识,以降低延迟、减轻负载,并提升后端在大规模场景下的性能。
缓存为何存在
缓存通过将频繁访问的数据存储在更快的存储中,来降低系统延迟和数据库负载。
- 数据库相对较慢,而且反复查询的成本较高。
- 缓存会将频繁访问的数据存储在更快的存储中,例如内存。
- 如果数据能在缓存中找到,就可以完全跳过数据库。
详情
在后端系统中,很多请求会反复获取相同的数据——例如用户资料、商品列表或配置设置。每次都查询数据库会带来不必要的延迟,并增加系统负载。
缓存通过将频繁访问的数据存储在更快的一层中来解决这个问题,通常是内存。当请求到来时,系统会先检查缓存。如果数据已经存在,就可以立即返回结果,而无需访问数据库。
这会显著提升响应时间,并减轻数据库压力,而数据库通常是系统中最昂贵且最受限制的资源之一。
从高层来看,缓存就像一个捷径。系统不必反复重新计算或重新获取相同的数据,而是复用之前已经取回的结果。
缓存命中与缓存未命中
每次缓存查询都会得到两种结果之一:命中(快)或未命中(需要访问数据库)。
- 缓存命中会直接从缓存中立即返回数据。
- 缓存未命中需要查询数据库,然后存储结果。
- 系统性能在很大程度上取决于能否获得较高的缓存命中率。
详情
当请求到达时,系统首先检查所请求的数据是否存在于缓存中。
如果找到了数据,这就称为 cache hit。由于避免了数据库查询,而数据库查询要慢得多,因此响应会很快返回。
如果没有找到数据,这就称为 cache miss。系统必须查询数据库,获取数据,将其存入缓存,然后返回响应。
这种模式形成了两条不同的路径:
缓存 → 命中 → 快速响应
缓存 → 未命中 → 数据库 → 存储 → 响应
缓存的目标是最大化 cache hits。命中率越高,系统就越快,数据库的负载也越低。
生存时间(TTL)
TTL 控制数据在缓存中保留多长时间,然后会自动移除,以防止数据过期。
- TTL 定义了缓存条目的生命周期。
- 一旦 TTL 过期,数据就会从缓存中移除。
- 合适的 TTL 能在数据新鲜度和性能收益之间取得平衡。
详情
缓存数据不能永远保留,因为底层数据可能会发生变化。如果缓存没有更新,系统可能会返回过时或错误的信息。
Time-To-Live (TTL) 通过为每个缓存条目附加一个过期时间来解决这个问题。TTL 持续时间结束后,缓存会自动移除该数据。
例如,产品目录可能会缓存 5 分钟,而一个频繁变化的 API 响应可能只缓存 60 秒。
选择合适的 TTL 是一种权衡。更长的 TTL 通过提高缓存命中率来提升性能,但也有返回过期数据的风险。更短的 TTL 可以保持数据新鲜,但会增加数据库负载。
TTL 是维护缓存正确性最简单、也是最广泛使用的机制之一。
缓存失效
缓存失效可确保当底层数据发生变化时,缓存数据仍与事实来源保持一致。
- 当数据库中的数据发生变化时,相关的缓存条目必须更新或删除。
- 错误的失效处理会导致系统返回过期或不一致的数据。
- 常见策略包括在更新后删除或刷新缓存条目。
详情
缓存引入了一个根本性问题:当底层数据变化时,缓存可能会变得过时。如果系统继续提供旧的缓存数据,用户可能会看到错误的信息。
缓存失效通过确保当数据库中的数据更新时,对应的缓存条目被删除或刷新来解决这个问题。
一种常见做法是在数据库更新后立即删除缓存条目。下一次请求会导致缓存未命中,从数据库获取最新数据,并将更新后的值存入缓存。
另一种做法是在数据库变更的同时主动更新缓存,使两者保持同步。
难点在于准确识别哪些缓存条目需要失效。这个过程中的错误是分布式系统中常见的 bug 来源。
Cache-Aside 模式
cache-aside 模式让应用程序控制数据何时加载到缓存中。
- 应用程序在查询数据库之前,先检查缓存。
- 当缓存未命中时,从数据库获取数据并存储到缓存中。
- 这种方法可以完全控制缓存行为。
详情
在 cache-aside 模式中,应用程序负责同时与缓存和数据库交互。
当请求到来时,应用程序首先检查数据是否存在于缓存中。如果存在(cache hit),就会立即返回数据。
如果数据不在缓存中(cache miss),应用程序会查询数据库,获取结果,将其存入缓存,然后返回响应。
这种方法被广泛使用,因为它简单且灵活。应用程序可以决定缓存什么、何时缓存,以及缓存保留多长时间。
不过,这也意味着应用程序必须谨慎处理缓存失效和一致性,因为缓存不会自动与数据库保持同步。
直写缓存
直写缓存通过在每次写入时同时更新缓存和数据库,保持两者同步。
- 每次写入都会同时更新缓存和数据库。
- 这确保缓存中的数据始终是最新的。
- 代价是写入延迟会增加。
详情
在直写缓存中,每当数据被写入时,系统会先更新缓存,然后将相同的数据写入数据库。这样可以保证两层始终包含相同的值。
由于缓存会立即更新,后续任何读取都会返回最新数据,而不需要检查数据库。这简化了读取逻辑,并避免了过期数据问题。
代价是写操作会变慢,因为每次写入都必须执行两次——一次写入缓存,一次写入数据库。这会增加延迟,并且在高写入负载下可能降低吞吐量。
此外,直写缓存可能会消耗更多资源,因为两个存储层都需要持续维护。尽管如此,它在一致性至关重要且需要可预测读取行为的系统中非常有用。
缓存技术
不同的缓存技术在不同层级运行,以降低延迟并减轻后端系统的负载。
- Redis 是一种快速的内存存储,支持 TTL 和高级数据结构。
- Memcached 是一种轻量级缓存,针对高吞吐量的键值访问进行了优化。
- CDN 会将内容缓存到更靠近用户的位置,以降低全球网络延迟。
详情
缓存会根据其在系统中的位置,使用不同的技术来实现。
Redis 是最广泛使用的缓存系统之一。它将数据存储在内存中,因此速度非常快,并支持 TTL、列表、集合以及其他数据结构等功能。它通常用于应用层缓存、会话和实时数据。
Memcached 也是一种内存缓存,但比 Redis 更简单。它专注于快速的键值存储,并针对高吞吐量进行了优化,因此非常适合基础缓存场景。
在更高层面上,CDN(Content Delivery Networks)会在分布于全球的服务器上缓存图片、视频和 HTML 等静态内容。这减少了数据传输距离,从而提升全球用户的访问性能。
在许多系统中,这些技术会组合使用。请求可能首先命中 CDN,然后命中像 Redis 这样的应用缓存,只有在需要时才会访问数据库。
缓存的使用位置
缓存会在系统的多个层级中使用,以降低延迟并减少对更深层组件的负载。
- 缓存存在于边缘层(CDN)、应用层和数据库层。
- 每一层都通过避免不必要的更深层请求来提供更快的响应。
- 叠加使用缓存可以提升整个系统的性能和可扩展性。
详情
缓存并不局限于某一个位置——它会在后端系统的多个层级中使用。
在边缘层,CDN 会将静态内容缓存到更靠近用户的位置,从而降低网络延迟。在应用层,Redis 或 Memcached 等工具会缓存经常访问的数据,例如用户会话或 API 响应。
一些数据库也包含查询缓存,会存储高频查询的结果,以避免重复计算。
这些层级形成了一个层次结构:
客户端 → CDN → 应用可缓存 → 数据库
每一层都会吸收一部分请求负载,确保只有在必要时才访问数据库。这种分层方式对于高效扩展系统至关重要。
缓存的权衡
缓存可以显著提升性能,但也会在数据一致性和正确性方面引入复杂性。
- 缓存可以降低延迟和数据库负载,从而提升系统可扩展性。
- 它会引入诸如脏数据和一致性问题等风险。
- 管理缓存失效会成为系统设计中的一个关键挑战。
详情
缓存是提升系统性能最有效的方法之一。通过从更快的存储中提供数据,系统可以更快地响应,并在数据库负载更低的情况下处理更多流量。
然而,这种性能提升是有代价的。最大的问题是脏数据——缓存中的值可能已经不再反映数据库的最新状态。
确保缓存与数据库之间的一致性会带来额外的复杂性。开发者必须仔细设计失效策略,以保持数据准确。
在实践中,缓存是一种权衡。系统会用一定程度的一致性和复杂性,来换取速度和可扩展性的显著提升。
问题部分
1 / 5
此课程属于高级内容
升级到高级版以去除模糊效果并解锁完整内容。