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
答案 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