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

时间:2020-03-06 14:29:38  来源:igfitidea点击:

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

我开始使用" Categories :: getAll"之类的键实现缓存层,如果我有用户特定的数据,我会在这些键后添加用户ID之类的东西,这样我们将获得"" User :: getFavoriteThreads |。 1471"`。当用户添加新的收藏夹线程时,我将删除缓存键,它将重新创建该条目。

但是,问题来了:

我想在论坛中缓存主题。足够简单," Forum :: getThreads | $ iForumId"。但是...分页,例如,我必须将其拆分为几个缓存条目

"Forum::getThreads|$iForumId|$iLimit|$iOffset".

没关系,直到有人在论坛中发布新话题。现在,无论限制和偏移量是多少,我都必须删除"" Forum :: getThreads | $ iForumId"`下的所有键。

解决这个问题的好方法是什么?我真的不希望遍历所有可能的限制和偏移量,直到发现不再匹配的东西。

谢谢。

解决方案

我们实质上是在尝试缓存视图,而这总是很棘手的。相反,我们应该尝试仅缓存数据,因为数据很少更改。不要缓存论坛,不要缓存主题行。然后,数据库调用应该只返回一个ID列表,我们已经在缓存中了。 db调用将在任何MyISAM表上快速减轻负载,因此我们不必进行大型联接,而这会占用db内存。

一种可行的解决方案不是在论坛中分页线程缓存,而是将线程信息放入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]);
}

这意味着我们确实需要做更多的工作才能在每个页面上将它们拉出,但是现在只需要担心在更新/添加新线程时会在一个位置使缓存无效。

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

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

然后,我们可以向" ExtendedMemcache"类中添加一个名为" deleteGroup"的新方法,该方法在传递字符串时会找到与该组关联的键,并依次清除每个键。

就像这样:
http://pastebin.com/f566e913b
我希望所有有意义并为我们解决的问题。

PS。我想如果我们想使用静态调用,可以将受保护的属性保存在memcache本身的自身密钥下。只是一个想法。

flungabunga:
解决方案与我正在寻找的非常接近。使我无法执行此操作的唯一方法是,在每个请求之后都必须将关系存储在内存缓存中,然后再将其加载回去。

我不确定这将对性能产生多大的影响,但效率似乎有些低下。我将做一些测试,看看它如何成功。感谢我们提出的结构化建议(以及一些演示代码,谢谢!)。

我们可能还希望根据工作量和CPU成本来查看存储缓存数据的成本,以及缓存将如何为我们买单。

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

同样,带有用户喜欢的线程列表。如果这是每个人很少访问的内容,则缓存可能不会太大地提高性能。

在进行此类优化时要非常小心,而要避免有困难的事实。

大多数数据库具有多个级别的缓存。如果对它们进行了正确的调整,则数据库在缓存方面的工作可能会比我们自己完成的工作好得多。

只是一个更新:
我认为Josh关于数据使用的观点是一个很好的观点。
人们不太可能继续浏览论坛的第50页。

基于此模型,我决定在每个论坛中缓存90个最新主题。在获取函数中,我检查限制和偏移量,以查看指定的线程切片是否在缓存内。如果它在缓存限制之内,我使用array_slice()检索正确的部分并返回它。

这样,我可以在每个论坛中使用一个缓存键,而清除/更新缓存的工作量很小:-)

我还要指出的是,在其他更耗费资源的查询中,我使用了flungabunga的模型,该模型存储了键之间的关系。不幸的是,Stack Overflow无法让我接受两个答案。

谢谢!

针对flungabunga:

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

例如

get seqno_mygroup
23

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

然后简单地"删除"组:

incr seqno_mygroup

瞧:

get seqno_mygroup
24

get mygroup24_mykey
...empty

等等..