缓存在系统开发中非常重要,能够减少永久存储中间件(比如 MySQL)的访问,提升系统吞吐量,减少响应时间。
有了缓存,必然会存在缓存跟永久存储中间件之间数据一致性的问题。当数据过期时,如何将缓存中对应的数据进行过期,就是本文要聊的话题。
1 缓存存在的意义
缓存,在计算机世界中区别存在,计算机体系中的多级缓存,应用系统中缓存等等,主要为了减少慢速介质访问速度慢的的问题,提升系统整体表现。在本文中,主要讲应用系统中提升永久存储访问表现的缓存,比如 redis,memcached 等等。
2 缓存失效
缓存中存储了永久介质中的数据。数据的 CUD 等操作时,需要同步给缓存。 那么需要将缓存中的数据失效。
3 缓存失效的常见策略
3.1 write-thougth
3.1.1 写入顺序
同时写入 缓存跟 永久存储。
3.1.2 优点
同时写入,就没有数据不一致的问题。
3.1.3 缺点
写入比较慢,每次写入都需要双写。
3.2 write-around
3.2.1 写入步骤
跟 write-through 相似
但是不会立马同时写入永久存储跟缓存 才响应客户端,而是写入到永久存储之后,立马响应成功,不管是否成功同步给缓存。然后通过旁路(bypass)方式写入缓存。
3.2.2 优点
减少了 cache 的瞬时写压力
3.2.3 缺点
但是写入后立马读取,会有 cache miss 的问题。
3.3 write-back
3.3.1 写入步骤
写请求:只写入缓存,就立马响应客户端了。然后在某些特定条件下再写入永久存储中。
3.3.2 优点
低延迟,高吞吐。
3.3.3 缺点
但是只存在 cache中,如果cache 崩溃了,会有丢数据的风险。
3.4 write-behind
3.4.1 写入步骤
同 write-back, 区别就是什么时间写入 永久存储。
write-back: 只有需要请求 cache 空间 或者某个特定事件发生时才会写入永久存储。
write-behind: 定期写入永久存储。
3.4.2 优点
低延迟,高吞吐。
3.4.3 缺点
但是只存在 cache中,如果cache 崩溃了,会有丢数据的风险。
4 缓存失效的方法
4.1 清除(purge )
直接清除缓存中的内容。一般常用这种方法,而不是更新。
4.2 刷新(refresh)
不删除,而是更新
4.3 禁止 (ban)
对某个特定缓存设置为 禁止,后续的所有请求直接请求 原始server。
4.4 过期时间(Time-to-live TTL expiration)
给某个缓存 设置特定的过期标识,表明这个值过期了。请求来的时候,一看过期标识,就直接请求原始server 了。然后再次缓存到 cache 中。
4.5 重新验证时 过期(Stale-while-revalidate)
常用于 web 浏览器 跟 CDN.
用户请求时,立马从缓存中获取内容,返回结果给用户。不管内容是否已经过期。
但是后台会异步地去请求原始服务,拉取缓存最新的内容。 然后将最新的内容更新到缓存中。
5 缓存失效策略与方法的关系
二者是在处理缓存时经常使用的两个概念,它们之间有一定的关系,但它们表示的层次不同。
5.1 Cache Invalidation Strategy (缓存失效策略)
缓存失效策略是指一种高级别的计划或方法,用于确定在何种情况下以及如何使缓存中的数据无效。它通常包括更广泛的业务规则和需求,例如何时更新缓存,何时删除旧缓存,何时进行刷新等。例如,可以有基于时间戳、版本号、事件通知、定期刷新等不同的策略。
5.2 Cache Invalidation Method (缓存失效方法)
缓存失效方法则是实现缓存失效策略的具体手段或技术。它是对缓存失效策略的具体实现方式的描述,例如使用时间戳,版本号,推送通知,事件驱动等。缓存失效方法是在实际编码和系统设计层面上执行缓存失效策略的具体工具。
6 缓存失效策略与方法的实践
选择缓存失效策略和方法的最佳实践取决于具体的应用场景、业务需求和性能要求。不同的组合可以用于满足不同的要求。以下是一些常见的组合及其最佳实践:
6.1 常见组合
-
Write-Through + Purge:
- 适用于需要实时数据一致性的场景。在写操作完成后,立即更新缓存并使用 "Purge" 方法清除旧缓存,确保下一次请求能够获取到最新数据。
-
Write-Around + Ban:
- 适用于对写操作的实时性要求不高的场景。写操作直接更新存储系统,而缓存项通过 "Ban" 方法标记为无效,稍后再进行清除。适用于一些相对不紧急的失效情况。
-
Write-Back + Refresh:
- 适用于需要提高写操作性能的场景。写操作先更新缓存,然后异步更新存储系统。使用 "Refresh" 方法在存储系统更新后刷新缓存,确保获取到最新数据。
-
Write-Behind + Purge:
- 适用于需要异步写操作的场景。写操作先更新缓存,然后异步更新存储系统。使用 "Purge" 方法在写操作后立即清除旧缓存,确保下一次请求能够获取到最新数据。
-
Write-Through + TTL:
- 适用于需要基于时间的缓存失效的场景。写操作直接更新缓存,使用 "TTL" 方法为缓存项设置生存时间,确保在一定时间内缓存项自动失效。
-
Stale-While-Revalidate + Refresh:
- 适用于需要在更新期间提供过期数据的场景。使用 "Stale-While-Revalidate" 方法允许缓存中的数据在即将过期时仍然提供给客户端,并使用 "Refresh" 方法异步获取最新数据。
需要注意的是,最佳实践可能因应用程序的具体需求而有所不同。在选择组合时,需要综合考虑数据一致性、性能、复杂度和实现的难易程度。另外,定期的性能测试和监控是确保所选组合有效的关键步骤。
6.2 选择组合时需要考虑的因素
选择最佳的缓存失效组合涉及到多方面的因素,包括应用程序的需求、性能要求、一致性要求以及实现的复杂性。没有一种通用的"最佳"组合,因为每个应用的场景都有独特的特点。
-
数据一致性需求:
- 如果对数据的实时一致性要求很高,可能更倾向于使用 Write-Through 策略,确保写操作立即更新缓存。
- 如果实时性要求不高,可能可以考虑 Write-Around 或 Write-Behind 策略,以提高写操作性能。
-
读写性能需求:
- 如果对读性能的要求很高,可以考虑 Write-Around 或 Write-Behind,以减轻写操作对缓存的直接影响。
- 如果对写性能的要求很高,可能更倾向于 Write-Through 或 Write-Behind。
-
实现复杂性:
- 选择适度复杂性的方案,确保实现和维护的可行性。一些策略和方法的实现可能会涉及到更复杂的逻辑和代码。
-
缓存数据大小和访问模式:
- 缓存的数据大小和访问模式也是重要考虑因素。如果数据量大,可能需要考虑缓存淘汰策略和存储方案。
-
性能测试和监控:
- 在选择组合后,进行性能测试和监控是至关重要的。实际运行中的性能表现可能与预期不同,因此需要及时调整。