Memcache标签模拟

时间:2012-06-05 08:17:16

标签: caching memcached

Memcached 是一个很棒的可扩展缓存层,但它有一个很大的问题(对我来说)它无法管理标记。标签对于组无效非常有用。

我做了一些研究,我知道一些解决方案:

我最喜欢的解决方案之一是命名空间,此解决方案在memcached wiki上解释。

但是我不明白为什么我们要在密钥缓存上集成命名空间?

根据我的理解关于命名空间的技巧是:要生成密钥,我们必须获取命名空间的值(在缓存上)。如果namespace->value缓存条目被逐出,我们就再也无法计算获取缓存的好密钥......所以这个命名空间的缓存虚拟无效(我实际上是因为缓存仍然存在,但我们不能再计算访问的关键了。

那么为什么我们不能简单地实现类似的东西:

tag1->[key1, key2, key5]
tag2->[key1, key3, key6]
key1->["value" => value1, "tags" => [tag1, tag2]]
key2->["value" => value2, "tags" => [tag1]]
key3->["value" => value3, "tags" => [tag3]]
etc...

通过这个实现,我回过头来解决如果tag1->[key1, key2, key5]被驱逐,我们就不能再使tag1键无效了。但是

function load($cacheId) {
   $cache = $memcache->get($cacheId);
   if (is_array($cache)) {
      $evicted = false;
      // Check is no tags have been evicted
      foreach ($cache["tags"] as $tagId) {
         if (!$memcache->get($tagId) {
            $evicted = true;
            break;
         }
      }
      // If no tags have been evicted we can return cache
      if (!$evicted) {
         return $cache
      } else {
         // Not mandatory
         $memcache->delete($cacheId);
      }
      // Else return false
      return false;
   }
}

这是伪代码

如果所有这些标签都可用,我们肯定会返回缓存。

首先我们可以说它是“每次你需要获取缓存时我们必须检查(/获取)X标签,然后检查数组”。但是使用命名空间我们还必须检查(/ get)命名空间以检索命名空间值,主要的差异是在数组下迭代... 但我不认为键会有很多标签(我无法想象我的应用程序超过10个标签/键),所以在10号阵列下迭代它的速度非常快..

所以我的问题是:有人已经考虑过这个实现吗?有什么限制?我忘记了什么吗?等

或者我可能错过了命名空间的概念......

PS:我不是在寻找另一个缓存层,比如memcached-tag或redis

1 个答案:

答案 0 :(得分:1)

我认为你忘记了这个实现的一些东西,但修复它是微不足道的。

考虑多个密钥共享某些标签的问题:

key1 -> tag1 tag2
key2 -> tag1 tag2
tag1 -> key1 key2
tag2 -> key1 key2

假设你加载key1。你仔细检查tag1和tag2是否存在。这很好,关键负载。

然后tag1以某种方式从缓存中逐出。

您的代码会使tag1无效。这应该删除key1和key2,但因为tag1已被驱逐,所以不会发生这种情况。

然后添加新项目key3。它也指tag1:

key3 -> tag1

保存此密钥时,会(重新)创建tag1:

tag1 -> key3

稍后,当再次从缓存加载key1时,检查伪代码以确保tag1存在成功。并且允许加载来自key1的(陈旧)数据。

显然,解决这个问题的方法是检查tag1数据的值,以确保您正在加载的密钥列在该数组中,并且只有在密码有效时才考虑您的密钥。

当然,根据您的使用情况,这可能会出现性能问题。如果一个给定的密钥有10个标签,但每个标签都是由10k密钥使用的,那么你必须搜索10k项目的数组才能找到你的密钥,并在每次加载时重复10次。

在某些时候,这可能会变得低效。

当您具有非常高的读写比时,替代实现(以及我使用的实现)更合适。

如果读取是非常常见的情况,那么你可以在一个更永久的数据库后端实现你的标记功能(我假设你有一个排序的数组,所以它只需要几个额外的表)。

在缓存中编写项目时,将密钥和标记存储在一个简单的表中(键和标记列,键上每个标记一行)。编写密钥很简单:“从cache_tags中删除id =:key; foreach(标记为标记)插入cache_tags值(:key,:tag);  (NB在真正的impl中使用扩展插入语法。)

当使标记无效时,只需遍历具有该标记的所有键:(从cache_tags中选择tag =:tag;)并使其中的每一个无效(并且可选地从cache_tags表中删除该键以进行整理)

如果从memcache中删除密钥,则cache_tags元数据将过时,但这通常是无害的。当您尝试使具有该标记但已被驱逐的密钥无效时,它最多会导致效率低下。

这种方法提供了“免费”加载(无需检查标签),但是节省了费用(无论如何已经很昂贵,否则它不需要首先缓存!)。

因此,根据您的使用情况以及预期的负载模式和使用情况,我希望您的原始策略(对负载进行更严格的检查)或“数据库支持的标记”策略可以满足您的需求。

HTHS