【问题标题】:Implementing Model-level caching实现模型级缓存
【发布时间】:2011-02-24 09:21:37
【问题描述】:

我在related question 中发布了一些关于 MVC 缓存的 cmets,但出现了一些关于实际实现的问题。如何实现模型级缓存,无需开发人员手动缓存即可透明地工作,但仍保持高效?

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

(来源:Post by Jarrod

我持怀疑态度的原因是,除非有真正的需要,否则通常不应进行缓存,也不应针对搜索结果之类的内容进行缓存。因此,模型本身必须以某种方式知道向它发出的 SELECT 语句是否值得缓存。模型是否必须非常聪明,和/或存储在很长一段时间内最常查询的内容的统计数据才能准确地做出决定?难道这一切的开销不会使缓存毫无用处吗?

您将如何从另一个查询(或更准确地说,从另一个结果集中的一个结果集)中唯一地标识一个查询?如果您使用准备好的语句,仅根据用户输入更改参数怎么办?

另一张海报这样说:

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

微不足道的碰撞几率值得担心吗?

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


赏金更新

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

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

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

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

【问题讨论】:

  • (又迟到了。)我对你从谈论模型缓存到查询缓存的转变感到困惑。根据您的模型结构,模型缓存可以在“写入”时很好地处理 失效很少见。然后,您将讨论基于查询参数的缓存。这似乎比模型缓存高出一步。您是在寻找两者还是其中之一(如果是,是哪一个)?两者都是可行的,但有不同的答案。我很乐意提供帮助,但想先解决这个问题。
  • 别担心,你没有迟到。这就是赏金的用途。根据我(可能被误导)的理解,模型缓存和查询缓存必须完全相关,因为对象关系阻抗不匹配。缓存模型没有完全意义,因为您只会缓存您必须跟踪的单个记录/对象,查询缓存是因为我将在中等规模的数据集上进行操作。但不知何故,你必须通过存储模型集来做到这两点。 (??)
  • 好的,很好。大多数情况下,我们缓存在小(模型)端。缓存模型减轻了单一模型查找和数据库/行级写入/读取冲突的负担。由于极端变化,我们通常不会在查询端缓存。我们已经对我们的数据集进行了反规范化,以适应我们用来从缓存加载(以及扩展集合)的快速 ID 查找(和公共列)。当我们缓存聚合内容(跨模型、查询等)时,我们有唯一的 ID(每个查询的哈希值,很好)以及用于失效的辅助标签(模型 ID)。我们使用 Zend_Cache 来管理所有这些。

标签: php database model-view-controller caching


【解决方案1】:

只有当模型是简单的 ORM 时,您的帖子才有意义。有很多原因表明这是一件坏事。尝试将模型视为 Web 服务。

缓存模型的职责。

您将如何从另一个查询(或更准确地说,从另一个结果集中的一个结果集)中唯一地标识一个查询?如果您使用准备好的语句,仅根据用户输入更改参数怎么办?

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

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

即使在购物篮的情况下,缓存 TTL 小于处理会更改其内容的事务所花费的时间的数据也可能有好处,在目录搜索的情况下,缓存列表匹配几个小时的产品可能不会对销售产生可衡量的影响,但可以在减少数据库负载方面进行权衡。

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

模型是否必须具有天文智能和/或存储统计数据

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

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

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

出于偏好,我会将其实现为模型类上的装饰器 - 这样您就可以轻松地将其移植到实现工厂而不是简单的 ORM 的模型。

C.

【讨论】:

  • 谢谢你,你的帖子帮助我重新思考我是如何设计我的模型的。
【解决方案2】:

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

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

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

尝试管理 ORM 系统的缓存验证是一种完全不同的野兽(由于关系),可能应该逐个处理(在控制器中)。但是,如果您真的关心性能,那么您很可能一开始就不会使用 ORM。

更新:

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

【讨论】:

  • 感谢您的编辑,您的信息很有用。明天我会用更多细节更新我的问题,并为任何能给我一个真正具体答案的人提供奖励。事实上,我确实使用了一个有点类似于 ActiveRecord 的“ORM”,它非常轻量级和高性能,所以我不确定应该在哪里实现缓存。
【解决方案3】:

我怀疑的原因是因为 通常不应该进行缓存 除非有真正的需要,并且 不应该为之类的事情做 搜索结果。所以不知何故模型 自己必须知道是否 向它发出 SELECT 语句 值得被缓存。难道不是 模型必须非常聪明, 和/或存储统计信息 最常被长时间查询 一段时间才能准确 做决定?而且不会 所有这一切的开销使缓存 还是没用?

还有谁更适合跟踪这些?多个控制器将使用相同的模型来获取他们需要的数据。那么,控制者究竟如何才能做出理性的决定呢?

没有硬性规定——智能缓存策略几乎完全由上下文驱动。业务逻辑(同样是模型!)将决定缓存中应该包含哪些类型的内容,何时需要使缓存无效等。

缓存搜索结果似乎是个坏主意,这绝对是正确的。我确定通常是这样。如果您的搜索结果的生成成本非常高,并且您正在执行分页之类的操作,您可能需要一个包含最新结果以及搜索参数的每用户缓存。但我认为这是一个相当特殊的情况。

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

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

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

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

这里需要注意的是,如果您在控制器中滥用 ActiveRecord 和/或编写 SQL 查询(或等效项),您可能会遇到问题。如果您有一个漂亮、丰富的模型层可以准确地为您的域建模,而不是仅仅包装数据库表的脆弱模型,那么执行智能缓存会容易得多。

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

【讨论】:

  • 感谢您的回答。我用赏金更新了这个问题。我认为对我的模型的准确描述是它们包含 ORM 层,因此在某些方面它们是“表包装器”,但所有域逻辑都在各个模型类中。这是一个糟糕的缓存设置吗?
【解决方案4】:

我们所做的是构建一个缓存层来替代 MVC 的加载功能。这样,只有我们想要的实际模型调用将被缓存。如果不需要或不需要缓存,则使用从控制器调用模型的正常方式。

如果通过缓存层调用模型及其最终参数,缓存层将首先根据缓存池验证请求的数据,如果仍然有效则返回。如果是这样,则不会加载实际模型,并且仅将缓存数据返回给控制器。如果不是,则按正常方式调用模型。

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

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

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

【讨论】:

    【解决方案5】:

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

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

    您将拥有一个控制所有这些发生的类。函数可能包括

    -开始缓存检查
    -设置阈值
    -缓存总是
    -缓存时间寿命
    -强制清除所有缓存
    - 清除此查询的缓存
    -我们已经被死亡激光击中了,需要抓住一切(我讨厌你的wordpress我再也不会使用你的功能我不应该这么懒惰并制作我自己的网站功能)

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

    这可能比某些缓存系统的开销稍大,但如果您只想让缓存自己做事,我认为它会很好用;没有它运行得太疯狂了。

    【讨论】:

      【解决方案6】:

      这并不是一个真正的答案,但你的问题让我想起了我见过this chapter,我认为它描述了如何使用 Doctrine ORM 和 Symfony 来做你想做的事情。您可能想与该方法/实现进行比较。

      基本上,那里的方法不会尝试“天文智能”,而是允许程序员根据数据的波动性及其性能影响手动指定要缓存的结果集......我想你可以近似那个决定和每晚根据实际指标或其他东西重新计算它。

      【讨论】:

        【解决方案7】:

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

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

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

        【讨论】:

          【解决方案8】:

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

          【讨论】:

            猜你喜欢
            • 2015-05-25
            • 1970-01-01
            • 1970-01-01
            • 2013-12-24
            • 2021-06-03
            • 2012-05-08
            • 1970-01-01
            • 1970-01-01
            • 2013-03-12
            相关资源
            最近更新 更多