繁体   English   中英

如何实现PHP / HTML缓存

[英]How To Implement A PHP/HTML Cache

我已经阅读了几个关于实现php缓存系统的指南(我的网站是自定义编码的,相当严重的查询和不断增长),包括以下内容: http//www.snipe.net/2009/03/quick-and-dirty-php -caching /

我完全理解它们,但页面的某些部分我无法缓存,最好的方法是什么?

如果您执行大量读取但很少更新,则缓存很有用。 数据库中的数据越频繁,缓存系统就越成问题。 缓存确实会给代码库增加一定的复杂性,这可能很难处理。 在最糟糕的情况下,它甚至可以减慢你的网站速度。

最重要的问题是:
什么时候必须使缓存无效? 什么时候变得陈旧? 在大多数情况下,如果数据库查询返回的行数与缓存该页面时的行数不同。 但你怎么知道的? 你没有(也许有办法,但我想不出任何atm),因为要检查一下,你可能要查询结果进行比较。

你可以做什么:

  1. 每次更新数据库的相关部分时清除所有缓存
    如果你的数据库很少得到更新 - 每小时,每天,每周,这确实是可能的。 但如果不断变化,它就没用了。 大多数网络项目就是这种情况。

  2. 事情发生后清除缓存的项目
    这只有在不必立即反映变化时才有效(例如,如果一段时间内数据不正确则无关紧要)。 在这种情况下,如果某个项目超过X分钟,或者超过Y页面浏览量,您只需清除某个项目的缓存即可。

  3. 只清楚相关的部分
    在这里,您必须确定在更新数据库时哪些部分的缓存会受到影响。 如果做得好,改变会在性能提高时立即反映出来。

最喜欢的是选项3:你必须找出答案。 因此,举一个例子,让我们看一下博客的经典案例,包括首页,档案页面和每个条目的详细页面。

更改由以下内容引入:管理面板(条目的crud)和注释

如果条目被编辑或删除,则必须清除缓存:

  • 首页,如果条目是新的
  • 相关的存档页面,如果条目是旧的
  • 条目的详细页面

如果有人推荐您只需要清除详细信息页面,但前提是注释或存档中没有显示注释数量。 否则,与入口 - crud相同。

如果站点范围内的某些内容发生了变化,则必须清除整个缓存(不好!)

现在,让我们考虑入口和存档。 如果存档的类型为“每月一页”,则清除该条目所属的月份。 但如果存档是1-10,11-20,21-30的条目,那么大多数情况下整个存档缓存都必须重建。

等等 ...

一些问题:

  • 如果您没有正确识别所有受影响的部分,则可能导致陈旧数据和/或(非)死链接。

  • 如果更新经常发生,那么构建缓存是额外的工作,因为当下一个页面视图发生时,缓存很可能再次失效并且无论如何都必须重建。

  • 页面的某些部分不适合缓存,例如(自定义)搜索功能。 如果缓存在其他地方工作,一切都很快很好,但搜索速度仍然很慢。

  • 如果在发生大量请求时必须清除整个缓存,则可能会出现问题。 然后它可以阻塞你的服务器,因为缓存缺失通常比首先没有缓存页面更昂贵。 更糟糕的是,如果有3个请求进入,并且第一个请求无法在处理其他两个请求之前缓存页面,则缓存会被请求3次而不是一次。

我的建议:

  • 优化您的数据库。 键和配置好吗? 也许它没有缓存。

  • 优化您的查询。 “解释选择”!

  • 只缓存页面的部分 - 昂贵的。 使用str_replace和占位符填写小的,廉价的更改

  • 如果一切正常,请使用apc或memcached而不是文件(文件通常效果很好,但apc / memc更快)。 您也可以使用您的数据库来缓存您的数据库,这通常很有用!

  • 你在建立一个懒惰或急切的缓存系统? 懒惰意味着:在页面首次请求时构建缓存,急切意味着:在更新后立即执行。

嗯,我对你没有任何真正的建议。 过分依赖于问题:)

更新

这是一个带有密钥256的博客条目请求。它显示了博客条目,评论和当前登录的人。查询条目和评论以及格式化所有文本和所有内容都很昂贵。 当前登录的用户驻留在会话中。

首先,为要缓存的部分创建唯一键。 在这种情况下,缓存键可能是条目的数据库ID(带有一些前缀和后缀)。

因此,缓存的文件应该具有名称cache/blogentry_256.tmp 检查,如果该文件存在。

  1. 如果它不存在,请执行所有昂贵的查询和格式化,留下占位符(例如{username}),其中当前用户的名称应该是,并将结果保存到cache/blogentry_256.tmp 注意不要将任何数据写入此文件,不应为每个人显示或每次请求都要更改。

  2. 现在,读取文件(或重用1中的数据)并将用户名str_replace到占位符中。 echo结果。

如果条目被更改或有人注释,则必须删除带有条目id的缓存文件。

这是惰性缓存 - 仅当用户请求页面时才构建缓存。 小心 - 如果用户在注释中键入{username},它也会插入到那里! 这意味着,您必须在str_replacing之后转义缓存的数据并对其进行转换。 这种技术也适用于memcached或apc。

问题:你必须围绕缓存设计构建你的设计。 例如,如果你想显示“5分钟前发布的评论”而不是“5月6日下午3:42添加评论”,那么你就麻烦了。

为什么你不能缓存它们? 如果是因为它们变化非常快,那么你最好不要试图减少检索和渲染开销。

如果内容因用户而异,则可以为每个用户提供不同的缓存版本。 例如,如果您正在使用对象缓存,则在缓存键中包含用户的标识符或其他一些唯一值。 如果您正在使用更通用的HTTP级缓存,例如Squid,那么您可以将Vary标头设置为例如Vary: Cookie ,这将导致代理重复检查它为其提供内容的人。

您仍应指示缓存和代理不要将任何敏感信息存储在公共缓存中。

请记住,缓存不是一个单一的解决方案 - 您的应用程序可以在不同的级别上进行缓存,具体取决于变量的范围。 在单个请求中,您可能具有身份映射以防止对相同数据的其他查询。 您可以使用memcached在请求之间缓存数据。 数据库本身具有查询缓存以及其他类型的低级缓存。 您的渲染引擎也可以在不同的级别缓存 - 整个页面,页面的各个部分等。您需要弄清楚可以缓存的内容并为此制定策略。

此外,与所有优化一样,请确保在调整任何内容之前进行测量。 这将确保1)你实际上改善了事情,而不是让它们变得更糟; 2)你专注于重要的事情,而不是其他一切。

这是我的提示:创建一个对象或函数,允许您根据其名称缓存“部分”。 这样,您可以缓存部分页面,并在if块中包含渲染部分。 我所做的是对输入文本进行散列并将其用作'./cache'的文件名,并返回是否需要重新生成的布尔值; 你显然需要输出缓冲。

这会给你一个缓存框架,

if(Cache::cached('index-recent-articles', 5 /* minutes */)) {
   Cache::start();
   echo 'stuff here';
   Cache::stop('index-recent-articles');
} // And if Cache::cached could echo the cached HTML when the result is false...
  // then this is a tidy else-less bit of code.

我不知道这是否是最优的,像memcache这样的基于服务器的解决方案会更好,但这个概念应该对你有帮助。 显然,您是如何通过文件或数据库或扩展来管理缓存的。

编辑:

如果您希望基于文件的缓存,那么这个简单的系统可能就是您所需要的。 显然我还没有用你的鞋子测试它,这大部分只是从我的后脑中拉出来的,但是就目前而言,这对于你可能想要的东西来说是一个不错的开始。

abstract class Cache {
  const path = 'cache/';
  static function start()  { ob_start(); }
  static function end($id, $text = null) {
    $filename = sprintf('%s%u', Cache::path, crc32($id));
    file_put_contents($filename, $text === null ? ob_get_clean() : $text);
  }
  static function cached($id, $minutes = 5) {
    $filename = sprintf('%s%u', Cache::path, crc32($id));
    $time = $minutes * 60;
    if(time() - filemtime($filename) > $time) {
      return true;
    } else {
      echo file_get_contents($filename);
      return false;
    }
  }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM