缓存分页结果,清除更新 - 如何解决?

时间:2008-09-20 21:27:26

标签: php caching pagination memcached

我已经创建了一个论坛,我们正在实现一个apc和memcache缓存解决方案,以便为数据库保存一些工作。

我开始使用像“Categories :: getAll”这样的键来实现缓存层,如果我有特定于用户的数据,我会使用类似用户ID的内容附加密钥,这样你就可以获得{{1} }。当用户添加新的收藏夹线程时,我将删除缓存键,它将重新创建该条目。

然而,问题出现了:

我想在论坛中缓存线程。很简单,“论坛:: getThreads | $ iForumId”。但是......通过分页,我必须将其分成几个缓存条目,例如

"User::getFavoriteThreads|1471"

哪个好,直到有人在论坛中发帖新帖。我现在必须删除"Forum::getThreads|$iForumId|$iLimit|$iOffset". 下的所有密钥,无论限制和偏移是什么。

解决这个问题的好方法是什么?我真的不想循环遍历每个可能的限制和偏移,直到找到不再匹配的东西。

感谢。

8 个答案:

答案 0 :(得分:7)

只是更新: 我认为Josh关于数据使用的观点非常好。 人们不太可能继续查看论坛的第50页。

基于这个模型,我决定在每个论坛中缓存90个最新的线程。在提取函数中,我检查limit和offset以查看指定的线程片段是否在缓存中。如果它在缓存限制范围内,我使用array_slice()来检索正确的部分并将其返回。

这样,我可以在每个论坛使用一个缓存密钥,清理/更新缓存只需要很少的工作量: - )

我还想指出,在其他资源较多的查询中,我使用了flungabunga的模型,存储了密钥之间的关系。不幸的是,Stack Overflow不会让我接受两个答案。

谢谢!

答案 1 :(得分:5)

我设法通过使用自定义类(比如ExtendedMemcache)扩展memcache类来解决这个问题,该类具有受保护的属性,该属性将包含组到键值的哈希表。

ExtendedMemcache->set方法接受3个参数($strGroup$strKey$strValue) 当您调用set时,它会在受保护的属性中存储$strGroup$strKey之间的关系,然后继续将$strKey$strValue关系存储在{{ 1}}。

然后,您可以向名为“deleteGroup”的memcache类添加一个新方法,当传递一个字符串时,它会找到与该组关联的键,并依次清除每个键。

这将是这样的: http://pastebin.com/f566e913b 我希望所有这些都有意义,并为你效劳。

PS。我想如果你想使用静态调用,那么受保护的属性可以在它自己的密钥下保存在ExtendedMemcache本身。只是一个想法。

答案 2 :(得分:5)

您可能还想了解一下根据您的工作量和CPU成本存储缓存数据的成本,以及缓存将如何为您购买的成本。

如果您发现80%的论坛视图正在查看主题的第一页,那么您可以决定仅缓存该页面。这意味着缓存读取和写入都更容易实现。

与用户最喜欢的主题列表相同。如果这是每个人很少访问的内容,那么缓存可能无法提高性能。

答案 3 :(得分:2)

你实际上是在尝试缓存一个视图,这总是会变得棘手。您应该尝试仅缓存数据,因为数据很少更改。不要缓存论坛,缓存线程行。然后你的db调用应该只返回你已经在缓存中的id列表。数据库调用将在任何MyISAM表上快速闪烁,然后您不必进行大型连接,这会占用数据库内存。

答案 4 :(得分:1)

一种可能的解决方案是不在论坛中对线程的缓存进行分页,而是将线程信息放入Forum::getThreads|$iForumId。然后在你的PHP代码中只提取你想要的那个给定页面,例如

$page = 2;
$threads_per_page = 25;
$start_thread = $page * $threads_per_page;

// Pull threads from cache (assuming $cache class for memcache interface..)
$threads = $cache->get("Forum::getThreads|$iForumId");

// Only take the ones we need
for($i=$start_thread; $i<=$start_thread+$threads_per_page; $i++)
{
    // Thread display logic here...
    showThread($threads[$i]);
}

这意味着你还需要做更多工作才能在每个页面上删除它们,但现在只需要担心在更新/添加新线程时在一个地方使缓存失效。

答案 5 :(得分:1)

flungabunga: 您的解决方案非常接近我正在寻找的解决方案。阻止我这样做的唯一方法是在每个请求之后将关系存储在memcache中并加载它们。

我不确定这会带来多大的性能影响,但似乎效率不高。我会做一些测试,看看它是如何进行的。感谢您提供结构化建议(以及一些显示的代码,谢谢!)。

答案 6 :(得分:1)

在没有严格的事实来衡量的情况下,要非常小心地进行这种优化。

大多数数据库都有多个级别的缓存。如果这些调整正确,数据库可能会在缓存方面做得更好,而不是自己做。

答案 7 :(得分:1)

回应flungabunga:

实现分组的另一种方法是将组名加上一个序列号放入密钥本身,并增加序列号以“清除”该组。您可以将每个组的当前有效序列号存储在其自己的密钥中。

e.g。

get seqno_mygroup
23

get mygroup23_mykey
<mykeydata...>
get mygroup23_mykey2
<mykey2data...>

然后简单地“删除”该组:

incr seqno_mygroup

瞧:

get seqno_mygroup
24

get mygroup24_mykey
...empty

等。