同步本地和分布式缓存

我们可以通过下面的简单算法实现该目的:

  1. 检查本地缓存的键(key);
  2. 如果本地缓存存在该键,则返回它的值;
  3. 如果本地缓存不存在该键,则尝试在分布式缓存中找;
  4. 如果分布式缓存存在该键,则返回它的值并把它添加到本地缓存;
  5. 如果分布式缓存不存在该键,则从数据库中获取,并添加到本地和分布式缓存,最后返回该值。

当在本地缓存服务器中缓存一些信息时,使用这种方式,它还将信息缓存到分布式缓存,但这一次,如果其他服务器在内存中没有该数据副本,则可以在分布式缓存中重用信息。

一旦所有服务器都有本地副本,就不再需要访问分布式缓存,因此,避免序列化和延迟的开销。

验证本地副本

看起来一切都很好。但现在我们有一个缓存失效的问题:如果其中一台服务器的缓存数据发生改变了,我们如何把变化通知给其他服务器,以使其 本地缓存的副本 失效?

我们会在分布式缓存中更改值,但是由于它们不再检查分布式缓存(从最后一个算法中的步骤 2 的可知道),他们不会被通知。

对于这个问题,一种解决办法是保持本地副本一段时间,例如 5 秒。因此,当服务器更改缓存数据时,其他服务器都将使用过时的信息(通常是 5 秒)。

此方法对于反复请求相同缓存信息的批处理操作非常有用。但即使分布式缓存没有发生任何改变,也必须每隔 5 秒从分布式缓存获取副本到本地缓存中。如果缓存的数据是非常大,这会增加网络带宽和反序列化成本。

我们需要一种方法来获得分布式缓存中的数据是否不同于本地副本的信息。有几种我可以想到的方法:

  • 将哈希与数据一起存储在本地和分布式缓存(有轻微的哈希计算成本)
  • 存储含递增版本的数据 (如何确保两台服务器不会产生相同的版本号?)
  • 在分布式缓存中存储最后设置数据的时间(时间同步问题)
  • 数据与随机数(代)一起存储

Serenity 使用代数(随机的整数)作为版本号。

所以当我们在分布式缓存存储值时,比方说是 SomeCachedKey,我们还存储含键 SomeCachedKey$GENERATION$ 的随机数。

现在,之前的算法变为:

  1. 检查本地缓存的键;
  2. 如果本地缓存存在该键,
    • 与分布式缓存中的代数比较,
    • 如果相等,返回本地缓存值;
    • 如果不相等,继续步骤 4;
  3. 如果本地缓存不存在该键,则尝试在分布式缓存中找;
  4. 如果分布式缓存存在该键,则返回它的值并把它添加到本地缓存;
  5. 如果分布式缓存不存在该键,则从数据库中获取,并添加到本地和分布式缓存,最后返回该值。

一次验证多个缓存项目

你可能已经从一些表中产生了缓存数据。在此表的分布式缓存中可能有多个键。

假设有一张简介(profile)表,并通过它们的 UserID 值缓存简介项目。

当用户的简介信息发生变化时,你可以尝试从缓存中删除简介信息。但是如果你不知道的其他服务器或应用程序缓存同一用户的什么简介数据呢?你可能不知道在分布式缓存中缓存什么信息键,它取决于一些 userID。

大多数分布式缓存的实现并没有提供以字符串开头查找所有键的方法,否则其将耗费大量的计算(因为它们是基于字典)。

所以当你想要根据一些数据集的日期让所有的项目过期,它是不可行的。

当缓存项目时,Serenity 允许你指定组键(group key),用于在组的数据发生变化时使之过期失效。

比方说一个应用程序从ID 是 17 的用户简介数据生成 CachedItem17 ,并使用此 ID 作为一个组键 (Group17_Generation):

Key Value
CachedItem17 cxyzyxzcasd
CachedItem17_Generation 13579
Group17_Generation 13579

在这里,组随机生成(版本)是 13579。随着缓存数据 (CachedItem17),我们存储产生此数据 (CachedItem17_Generation) 的任何组代。

假设另一台服务器,缓存来自用户 17 的数据 AnotherItem17:

Key Value
CachedItem17 cxyzyxzcasd
CachedItem17_Generation 13579
AnotherItem17 uwsdasdas
AnotherItem17_Generation 13579
Group17_Generation 13579

这里,我们重用 Group17_Generation,因为在分布式缓存中已经有一组版本号,否则,我们将必须生成一个新的。

现在,缓存的两个项目(CachedItem17 和 AnotherItem17)都有效,因为它们的版本号匹配组版本号。

如果有人修改了用户17的数据,我们想让其相关的缓存项目过期失效,只需修改组代。

Key Value
CachedItem17 cxyzyxzcasd
CachedItem17_Generation 13579
AnotherItem17 uwsdasdas
AnotherItem17_Generation 13579
Group17_Generation 54237

现在,所有的缓存项目都过期失效了。即使它们还存在缓存中,我们可以看到它们的代不匹配组代,因此认为它们是无效的。

我们使用的组键通常是产生数据的表名称。