实现模型级缓存

时间:2010-05-26 20:43:04

标签: php database model-view-controller caching

我在related question中发布了一些关于MVC缓存的评论,并且出现了一些关于实际实现的问题。如何实现透明工作的模型级缓存,而开发人员无需手动缓存,但仍然有效?

  

我会保留我的缓存   责任坚定地在   模型。它不是控制器的   或查看模型所在的业务   获取数据。他们所关心的只是   当请求数据时,数据是   提供 - 这是MVC的方式   范式应该起作用。

(资料来源:Post by Jarrod

我持怀疑态度的原因是,除非有真正的需要,否则通常不应该进行缓存,不应该对搜索结果这样的事情进行缓存。因此,模型本身必须知道发给它的SELECT语句是否值得缓存。模型不是必须天文学上聪明,和/或存储长时间最常查询的内容的统计数据,以便准确地做出决定吗?并不是所有这些的开销都不会使缓存无用吗?

如何从另一个查询中唯一标识查询(或者更确切地说,从另一个结果集中识别结果集)?如果您使用预准备语句,只根据用户输入改变参数,那该怎么办?

另一张海报说:

  

我建议使用md5哈希   您的查询与序列化相结合   输入参数的版本。

碰撞的微小机会值得担心吗?

从概念上讲,模型中的缓存对我来说似乎是一个好主意,但它看起来很实用,并且由于缓存的性质,开发人员应该直接控制它并明确地将其编码到控制器逻辑中。


Bounty更新

我确实使用了一个非常轻量级的ORM,有点类似于ActiveRecord但是能够在没有n^2问题的情况下进行复杂的连接和子查询。我自己构建它,因此它很灵活,并且在关系或列名方面没有限制,我只想了解如何实现缓存机制。

根据乐于助人的建议,我会将查询的哈希值(可能是md5)与其参数列表连接起来,并将其用作该特定数据存储的键。我应该在需要它的Model类中单独实现缓存,还是应该成为ORM层的一部分?

我怎么知道什么时候应该失效?我是否必须手动解析UPDATE / DELETE / INSERT查询和子参数以找出正在修改的记录?或者更糟糕的是,每当修改数据时进行其他查询以跟踪哪些内容已发生变化以及哪些内容应该失效?

我会将赏金奖励给任何可以给我一个明确概念性解释的人(无论这是否真的必须/有效透明地完成),如果是这样,有一些模型缓存的实现细节。我正在使用PHP和MySQL,如果这有助于缩小你的注意力。

8 个答案:

答案 0 :(得分:6)

缓存需要考虑很多因素,例如散列,失效等。但缓存的目标始终相同:减少响应时间和资源消耗。

对于 使用ORM的系统,我有几个快速的想法:

  • 如果你有内存的话,使用内存缓存来缓存一些内容永远不会伤害
  • 您应该只缓存SELECT个查询,因为其他类型会影响数据
  • 所有缓存的查询都应该参数化
  • 缓存密钥应该是与serialize()'d版本的参数连接的查询的md5(这标识了唯一的查询。由于通常传递给选择查询的参数大小,因此查找参数不是问题。通常很琐碎)。序列化并不像您想象的那么昂贵。而且因为你将静态查询与动态参数连接在一起,所以你永远不必担心碰撞。
  • 对模型中的行进行修改(INSERT / UPDATE / DELETE)应对该模型缓存的所有项目无效(或设置TTL)
  • 应扩展模型以允许缓存TTL值与查询一起发送
  • 您的模型应该支持跳过缓存(可能通过传递0的TTL和查询)
  • 即使可以缓存基本查询,在新的(修改的)查询中应用ORDER BY / LIMIT类型操作通常更有效,而不是从缓存中提取整个行集并进行操作它通过PHP实现同样的目标(除非您的Web和数据库服务器之间存在非常高的延迟)。

尝试管理ORM系统的缓存验证是一种完全不同的野兽(由于关系),应该根据具体情况(在控制器中)进行处理。但是,如果你真的关心性能,你可能不会开始使用ORM。

<强>更新

如果您发现自己在单个线程中使用同一模型类的多个实例,我建议还可能对实例化模型进行内存缓存(取决于您的构造函数,反序列化和唤醒对象有时比构造对象更有效)。一旦你有一个初始化对象(无论是构造的还是反序列化的),它就是 世界更高效 clone()一个对象的基本实例并设置它的新状态而不是而不是在PHP中重建一个对象。

答案 1 :(得分:5)

如果模型是一个简单的ORM,你的帖子才有意义。并且有很多原因可以解释为什么这是一件坏事。尝试将模型视为Web服务。

缓存模型的责任。

  

如何从另一个查询中唯一标识查询(或者更确切地说,从另一个结果集中识别结果集)?如果您使用预准备语句,只根据用户输入改变参数,那该怎么办?

但是模型的输入唯一地定义了它的输出。

如果您使用相同的模型检索购物篮的内容并在产品目录上运行搜索,那么您的代码就会出现问题。

即使在购物篮的情况下,使用TTL小于处理将改变其内容的事务所花费的时间来缓存数据也是有用的,在目录搜索的情况下,缓存列表匹配产品几个小时可能对销售没有可衡量的影响,但在减少数据库负载方面做得很好。

您使用开箱即用的简单ORM这一事实并不排除您将其包装在您自己的代码中。

  

模型不是必须天文数字智能化和/或存储统计数据

没有。您可以确定是否要缓存,如果无法确保缓存是一致的,则根据请求类型强制执行TTL。

作为一般的经验法则,你应该能够根据SELECT查询预测适当的TTL 绑定任何变量,这需要在设计时实现 - 但显然结果应该是基于绑定后的查询编制索引。

  

我应该在需要它的Model类中单独实现缓存,还是应该成为ORM层的一部分?

首先,我会将其作为模型类的装饰器实现 - 这样您就可以轻松地将其移植到实现工厂而非简单ORM的模型中。

下进行。

答案 2 :(得分:2)

  

我怀疑的原因是因为   通常不应该进行缓存   除非有真正的需要,并且   不应该做的事情   搜索结果。所以不知何故的模型   本身必须知道是否   发给它的SELECT语句   值得被缓存。不会的   模型必须是天文学上聪明的,   和/或存储什么是统计数据   经常被长时间查询   一段时间才能准确   做决定?而且不会   所有这些的开销使得缓存   无论如何都没用?

还有谁更适合跟踪其中任何一项?多个控制器将使用相同的模型来获取所需的数据。那么控制器如何在世界上做出合理的决定?

没有严格的规则 - 智能缓存策略几乎完全由上下文驱动。业务逻辑(再次,模型!)将决定缓存中应该有什么样的事情,缓存需要无效等等。

对于缓存搜索结果似乎是一个坏主意,你是绝对正确的。我敢肯定它通常是。如果您的搜索结果生成成本非常高,并且您正在进行分页等操作,则可能需要一个包含最新结果的每用户缓存以及搜索参数。但我认为这是一个相当特殊的案例。

如果没有上下文,很难给出更具体的建议,但这里有几个场景:

1)您拥有可以分配类别的业务对象。类别很少改变。您的类别模型应该缓存读取操作的完整类别集。当不频繁的右操作发生时,它们可以使缓存无效。系统中的每个视图脚本现在都可以查询模型并返回当前类别(用于渲染选择框,比方说),而不用考虑缓存。系统中的任何控制器现在都可以在不知道缓存的情况下添加/更新/删除类别。

2)你有一些复杂的公式,消耗多个输入,并为某种“产品”创建一个受欢迎程度。页面布局中的某些小部件以摘要形式显示了5个最常用的对象。您的产品模型将提供getPopular()方法,该方法将依赖于缓存。模型可以每隔X分钟使缓存无效,或者某些后台进程可以定期运行以使无效/重建。无论系统的哪个部分需要流行的产品,他们都会通过模型来请求它,它可以透明地管理缓存。

确切的缓存实现高度依赖于您正在操作的数据类型,并结合典型的用例。

这里需要注意的是,如果你在控制器中滥用ActiveRecord和/或编写SQL查询(或等价物),你可能会遇到问题。如果你有一个很好的,丰富的模型层来准确地模拟你的域,而不是只包装数据库表的脆弱模型,那么做智能缓存要容易得多。

这不是关于模型是聪明的,而是关于开发人员是聪明的。

答案 3 :(得分:2)

我们所做的是构建缓存层作为MVC加载功能的替代。这样,只会缓存我们想要的实际模型调用。如果不需要或不需要缓存,则使用从控制器调用模型的常规方法。

如果通过缓存层调用模型及其最终参数,缓存层将首先针对缓存池验证所请求的数据,并在仍然有效时返回它。如果是,则不加载实际模型,并且仅将缓存数据返回给控制器。如果没有,模型就像通常那样被调用。

在模型上方的层中进行此操作的可能性非常大,因为在每个查询/每个模型级别上引入信号量锁的使用变得非常容易,以进一步减少服务器负载。

对我来说最大的好处是,模型是按照预期设计的,只包含纯数据库查询。这样,就可以在没有最终用户注意的情况下修改生产中的模型(假设模型提供的请求数据在更新时间内不需要重新创建,当然......)

更新:我们还在缓存层内部实现了两个级别的命名空间,每个模型和一个可选的基于组。多亏了这一点,我们可以轻松地使数据库中更新或删除时来自模型的所有先前无效的所有缓存数据无效。

答案 4 :(得分:1)

如果您对活动记录库的更透明的缓存系统感兴趣。您可以为每个查询分配一个id,然后创建结果的关联数组。你可以静态地或讽刺地将这种关系存储在一个数据库中。(这是缓存的一种交易,你必须使用更多的计算机电源,这样你有时可以使用更少的计算机能力)

在每次运行查询时跟踪结果哈希,如果结果哈希不同,则更新新哈希。如果哈希值相同则会增加重复结果的数量。如果出现所需的重复结果数,则缓存结果并停止检查表中指定的时间量和/或后续运行的查询。

你会有一个控制所有这一切的课程。功能可以包括

之类的内容

- 启动缓存检查
- 设置阈值
-cache总是
- 加速时间生活
-force清除所有缓存
- 清除此查询的此缓存
- 我们已经被死亡激光致死,需要抓住一切(我讨厌你的Wordpress我再也没有使用过你的功能我不应该这么懒,并且自己制作网站功能)

这有助于自动化您的大部分流程。缓存规则也可以逐个模型地实现,或者作为整体应用于整个应用程序。

这可能比一些缓存系统略微开销,但如果你只是想让缓存做自己的事情,我认为它会运行良好;没有它跑得很多。

答案 5 :(得分:1)

这不是一个真正的答案,但你的问题提醒我,我已经看过this chapter,我认为,如何使用Sytrfony的Doctrine ORM来完成你想要做的事情。您可能希望与该方法/实现进行比较。

基本上,那里的方法没有尝试“天文学智能”,但允许程序员根据数据的波动性及其性能影响手动指定结果集进行缓存...我想你可以接近那个决定,根据实际指标或其他内容每晚重新计算。

答案 6 :(得分:1)

我建议您查看here以全面了解ORM中的缓存,包括可以应用的问题和解决方案。

在ORM中处理缓存数据时,通常需要解决以下3个问题:

  1. 许多ORM实现在实际的ORM对象中存储数据库资源或不可序列化的结果集或两者。由于缓存要求所有对象都被序列化,这给我们带来了严重的障碍。
  2. 如何在缓存中跟踪一组数据与另一组数据?
  3. 如何通知缓存特定数据集已更改?

答案 7 :(得分:0)

你应该有一个单独的模型,它可以直接进行SQL接口,例如。对于Customers表:$CustomerModel->GetCustomers($parameter);等等。然后,在这些模型中,您可以透明地实现缓存,而无需编辑任何现有的MVC。