缓存分页结果,清除更新-如何解决?
我创建了一个论坛,我们正在实现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
等等..