10

围绕现有数据库查询包装可选的 memcached 缓存很容易。例如:

旧的(仅限 DB):

function getX
    x = get from db
    return x
end

新的(带有内存缓存的数据库):

function getX
    x = get from memcache
    if found
      return x
    endif

    x = get from db
    set x in memcache
    return x
end

但问题是,这并不总是您想要缓存的方式。例如,采取以下两个查询:

-- get all items (recordset)
SELECT * FROM items;

-- get one item (record)
SELECT * FROM items WHERE pkid = 42;

如果我要使用上面的伪代码来处理缓存,我将存储项目 42 的所有字段两次。一次进入大记录集,一次独立。而我宁愿做这样的事情:

SELECT pkid FROM items;

并缓存 PK 的索引。然后单独缓存每条记录。

所以总而言之,最适合数据库的数据访问策略并不完全适合 memcache 策略。由于我希望 memcache 层是可选的(即,如果 memcache 关闭,站点仍然可以工作)我有点想要两全其美,但要做到这一点,我很确定我需要维护一个许多查询有 2 种不同的形式(1. 获取索引,然后是记录;2. 在一个查询中获取记录集)。分页变得更加复杂。使用 DB,您可以执行 LIMIT/OFFSET SQL 查询,但使用 memcache,您只需获取 PK 的索引,然后批量获取数组的相关切片。

我不知道如何巧妙地设计这个,有人有什么建议吗?

更好的是,如果你自己遇到了这个问题。你如何处理它?

4

4 回答 4

4

如果您正在使用缓存,那么为了充分利用它,您必须接受您的数据在一定程度上总是过时的,并且某些部分的数据将彼此不同步。通过维护一个副本来保持所有记录是最新的最好留给关系数据库,所以如果这是您需要的行为,那么您可能最好使用具有大量 RAM 的强大 64 位数据库服务器因此它可以执行自己的内部缓存。

如果您可以接受陈旧数据(如果真正的可伸缩性很重要,您将需要这样做),那么一种方法是将整个结果集放入缓存中;不用担心重复。内存很便宜。如果您发现您的缓存已满,那么只需购买更多 RAM 和/或缓存服务器。例如,如果您有一个查询表示一组按条件 X 和 Y 过滤的项目 1-24,然后使用包含所有这些信息的缓存键,然后当再次要求进行相同的搜索时,只需从缓存。您要么一键从缓存中获取完整的结果集,要么转到数据库。

最困难的事情是计算出有多少数据可能是陈旧的,以及在不(a)人们注意到太多,或(b)破坏业务要求(例如最小更新间隔)的情况下,它可能会过时。

这种方法适用于以读取为主的应用程序,特别是具有分页查询和/或数据过滤条件有限集的应用程序。这也意味着您的应用程序在打开或关闭缓存的情况下的工作方式完全相同,只是在缓存关闭时命中率为 0%。在几乎所有情况下,这都是我们在 blinkBox 采用的方法。

于 2008-11-10T02:01:42.473 回答
3

阅读有关身份映射模式的信息。这是一种确保您在应用程序空间中只保留给定行的一个副本的方法。无论您将其存储在 memcached 中还是仅存储在普通对象中,这都是一种处理您想要的内容的方法。我猜想当您通常一次获取一行时,最好使用 Identity Map。

当您获取表的整个子集时,您必须单独处理每一行。您可能经常面临是否从缓存中获得最佳使用的两难境地,因为如果 99% 的行都在缓存中但需要从数据库中获取,那么无论如何您都必须运行 SQL 查询(至少一次)。

您可以转换 SQL 查询以仅获取不在缓存中的行,但在不增加 SQL 查询成本的情况下自动执行此转换并非易事。

于 2008-11-10T01:38:55.873 回答
1

好吧,我想这是你必须忍受的。如果您不真正分批做事,Memcahced 会工作得最好。例如,它非常适合“这个用户的东西在哪里?这是给这个用户的一堆东西”之类的东西。这并不意味着这个查询不做批处理。当然会 - 如果某些用户的东西是他/她的帖子之类的东西。

我想你会遇到的问题是,你正在混合需要自己从数据库中获取一个项目的查询和一些需要一堆相同类型的先前项目的情况。

这种情况总是有另一面。如果您真的想对您的实现感到毛茸茸,您可以更改您的批处理查询以不包括 memcached 中已经存在的项目。非常非常难看...

在我看来,它总是归结为“我真正想要缓存哪些查询?”

编辑:

我会这样做的方法是:

  • 单项查询 - 如果在 memcached 中,则使用该查询,否则从数据库中获取并更新 memcached。
  • 批量查询 - 不用担心 memcached 中有哪些项目,只需获取所有内容并更新 memcached。

这当然假设批处理查询已经花费了更多的时间来完成,所以我已经花了很多时间来处理已经缓存的项目的外部查找。

但是,如果您大量使用批量查询,最终您的缓存将包含很多项目。因此,您必须权衡利弊,以确定您仍希望在什么时候执行数据库查找。好消息是,如果批处理查询在应用程序的生命周期中更早,那么所有内容都会更早地被缓存。在第一批查询之后,您可以告诉自己不再需要从数据库中获取,除非缓存中的数据因更新或删除而失效。

于 2008-11-10T01:22:21.490 回答
1

这是我对 NHibernate(因此可能是 Hibernate)如何做到这一点的理解。它有 4 个缓存:

  • 行缓存:缓存数据库行。缓存键是 TableName#id,其他条目是行值。
  • 查询缓存:缓存为特定查询返回的结果。缓存键是带有参数的查询,数据是作为查询结果返回的 TableName#id 行键的列表。
  • 集合缓存:这会缓存任何给定父对象的子对象(NHibernate 允许延迟加载)。因此,如果您访问 myCompany.Employees,员工集合将被缓存在集合缓存中。缓存键是 CollectionName#entityId,数据是子行的 TableName#id 行键的列表。
  • 表更新缓存:每个表的列表以及上次更新的时间。如果在缓存数据后更新了表,则数据被认为是陈旧的。

这是一个非常灵活的解决方案,在空间方面非常有效,并保证数据不会过时。缺点是单个查询可能需要多次往返缓存,如果缓存服务器在网络上,这可能会成为问题。

于 2010-08-24T13:16:09.810 回答