dot Redis 8 现已推出,且是开源的

了解更多

保持缓存一致性的三种方法

下载《规模化 Redis 缓存》电子书。这是一份入门指南,帮助您了解应用缓存是什么,为何以及何时需要它,以及如何从应用中获得最佳性能。

如果你相信拉尔夫·沃尔多·爱默生的话,“愚蠢的一致性是小心眼的魔鬼”,但在实施可扩展、成功的企业级缓存策略时,一致性绝非愚蠢。事实上,管理企业数据库运营的最大挑战之一就是维护缓存一致性。

为什么我们一开始要使用缓存呢?企业缓存的主要优势在于数据访问的速度和效率。虽然每次对主数据库的调用在时间和处理能力上都可能很昂贵,但对缓存的调用却可以闪电般迅速,且对主数据库没有任何影响。

缓存一致性的三个障碍

当然,这些优势都基于一个基本假设,即缓存(或多个缓存)中的数据始终与源数据保持相同的值。尽管这看起来是一个直接了当的目标,但在理论上比实践中更容易实现。事实上,有三个陷阱可能使其脱轨: 

1. 当主数据库的更改未反映在缓存中时

因为通过缓存访问数据,根据定义,比通过主数据库访问数据要快,如果请求某个特定项,将首先查询缓存。假设该项存在于缓存中,它将比从主数据库返回快得多。这种策略被称为 旁路缓存模式(cache-aside pattern)。默认情况下会首先检查缓存。如果数据不在缓存中,应用程序会查询主数据库,并在返回用户的途中将结果存入缓存。

问题出现在主数据库数据更改与缓存调整以反映该更改之间的间隔期间。这受到应用程序检查缓存频率的影响。然而,每次检查都会消耗处理器资源。同一个处理器可能同时处理着许多其他功能或事务,其中有些与更新缓存同等重要,甚至更重要。

挑战在于找到最佳平衡点,一种介于检查更新过于频繁和不够频繁之间的“金发姑娘区”。当然,如果用户试图在此间隔期间访问过时数据,那么这个赌注就输了。

Cash Consistency diagram

2. 当更新缓存结果存在延迟时

这个问题与前一个问题有些重叠。每次主数据库中的值被更新时,都会向缓存发送一条消息,指示它更新更改后的值或将其完全移除。(在后一种情况下,下次请求该值时,将从主数据库访问,然后从此以后再从缓存访问。)在正常情况下,这种通信发生得相对较快,并且缓存项会被更新或移除,以维护缓存一致性。

然而,同样,这种更改需要处理能力,并且需要时间。延迟可能受到可用处理速度和网络吞吐量的影响。如果用户不幸在服务器发送更新缓存消息与缓存接收并处理该消息之间的时间间隔内访问了过时数据,结果可能是过时、不正确或两者皆有的数据。

3. 当缓存节点之间存在不一致性时

当然,网站或应用程序越大,缓存越有可能存储在多个节点而不是单个节点上。除了节点外,可能还有任意数量的副本节点,它们理想情况下存储相同的数据。从负载均衡和性能的角度来看,这通常是合理的。

但从数据完整性的角度来看,它引入了另一个潜在的缓存不一致性来源。每次主数据库中的数据更新时,这个更改也需要在所有副本中反映出来。根据这些节点所在的地理位置以及数量,更新过程可能需要相当长的时间。尽管更新过程可能正在进行中,但用户仍然很有可能访问到一个尚未进行更改的节点。你猜对了,结果再次可能是缓存不一致性。

缓存不一致性的代价

尽管缓存数据库有很多好处,但缓存不一致性的潜在问题或许是其最显著的缺点。但问题有多大呢?最终,缓存不一致性的代价取决于具体情况。

有些缓存不一致性可能发生,但后果不严重。例如,如果缓存中的“赞”总数与主数据库中的实际总数暂时不同步,这种短暂的差异不太可能引起问题,甚至可能不会被注意到。 

另一方面,如果缓存中显示某个特定产品还有一个剩余库存,而主数据库中的实际库存显示已经没有剩余了,由此产生的冲突会使你的客户感到困惑和疏远,损害你的品牌可靠性声誉,对公司的交易和会计造成破坏,在极端情况下,甚至可能让你面临法律风险。

解决不一致性的三种方法

幸运的是,针对上面提到的每一种潜在的缓存不一致性来源,都有相应的解决方案。

1. 缓存失效

使用缓存失效策略,每当主数据库中的值被更新时,缓存中带有相应键的每个缓存项都会自动从一个或多个缓存中删除。虽然缓存失效可能被视为一种“暴力方法”,但其优势在于它只需要一次昂贵且通常耗时的写入(即对主数据库本身的写入),而不是两次或更多次。 

2. 直写式缓存

在这种情况下,直写式策略不是更新主数据库并移除缓存,而是应用程序更新缓存,然后缓存同步更新主数据库。换句话说,缓存不是依赖主数据库来启动任何更新,而是负责维护自身的一致性,并将所做的任何更改信息传递回主数据库。

3. 回写式缓存

不幸的是,有时候两次写入反而会出错。直写式缓存策略的一个缺点是,更新缓存和主数据库都需要两次耗时且占用处理器资源的更改,首先是缓存,然后是主数据库。

另一种策略,称为回写式(write-behind),通过最初只更新缓存,稍后更新主数据库来避免这个问题。当然,主数据库也需要更新,越快越好,但在这种情况下,用户不必承担两次写入的“代价”。第二次写入主数据库是异步发生的,并且在后台进行(因此得名回写式),通常在不太可能影响性能的时候。

Redis Enterprise 助您一臂之力

除了缓存失效策略外,直写式和回写式缓存也能解决许多有助于实现缓存一致性的场景。但找到问题的答案与实施它并不相同。

image illustrating how active-active replication works

Redis Enterprise 的 active-active(主-主) 地理复制功能允许多个主节点,使您能够轻松处理日益繁重的负载。“active-active” 之名指的是数据库的每个实例都可以接受对任何键的读写操作。每个数据库实例,无论距离多远,都是您网络上的一个对等节点。这意味着当对任何一个实例进行写入操作时,该节点会自动向网络上的所有其他实例发送消息,指示缓存中发生了哪些更改,并确保所有实例都保留一致的缓存数据集。

Redis Enterprise 独特的 active-active 地理复制功能 采用了复杂的算法,旨在处理可能导致缓存不一致的潜在写入冲突。这些算法基于无冲突复制数据类型 (CRDTs),确保来自多个副本的写入可以以有效维护一致性的方式合并。

为魔鬼欢呼!

随着架构的增长,维护缓存一致性的挑战变得更加复杂,并且后果越来越严重,您需要一个企业级缓存解决方案来可靠地提供您的业务所需和客户期望的一致性。

就爱默生而言,一致性可能是一个魔鬼,但在企业级数据库缓存方面,它绝对是至关重要的。这就是为什么选择 Redis Enterprise 是明智的。不选择它将是愚蠢的。

了解缓存的完整故事。阅读 Lee Atchison 的著作 《规模化 Redis 缓存》