【发布时间】:2014-02-18 04:39:47
【问题描述】:
我有一个小问题,希望有人能解释一下情况。
情况:
我有一个内置在 PHP CMS 中的自定义部分缓存机制。简而言之,当处理 CMS 中的模板时,它会处理“可缓存”的 PHP 代码,而不处理“不可缓存”的 PHP 代码,然后将生成的代码保存为文件以供将来访问该文件进行处理页面。
问题:
当系统“寻找”缓存文件时,我遇到文件访问延迟。使用 GLOB 查找匹配文件的 250,000 个缓存文件在站点上没有流量时需要 1/4 秒 - 有时在流量高峰时需要 10-15 秒。似乎 2 个单独的客户端会话无法同时运行 GLOB,因此它们成为瓶颈。
我在寻找什么:
... 是一种替代方法,或提供块缓存的优化,没有瓶颈问题。必须考虑我的独特担忧(如下所述)。我需要一种更快的方法来访问这些文件或替代的部分页面缓存方向:/
===============================================
简化代码:
// var to hold cached page path, if found
$pageCache = NULL;
// get the URL for current page
// there is actually some other code here that could alter the 'theURL4Cache' var for various reasons, but for simplicity in this example lets just keep it the REQUEST_URI
$GLOBALS['theURL4Cache'] = $_SERVER['REQUEST_URI'];
// check if existing cache file is in place
$filePattern = 'parsed/page_cache/*^' . $_SERVER['SERVER_PORT'] . '^' . $_SERVER['HTTP_HOST'] . '^' . (($_SESSION['isMobile']) ? 'M' : 'D') . '^L' . $language . '^T*^P' . $attributes['pageId'] . '^' . md5($GLOBALS['theURL4Cache']) . sha1($GLOBALS['theURL4Cache']) . '.php';
$fileArr = glob($filePattern);
// possible multiple files found that fit / expired files found that fit the pattern
// lets grab the newest file and try to use it
if(count($fileArr)){
rsort($fileArr);
$file = $fileArr[0]; // get file with latest expire date
if($file > 'parsed/page_cache/' . date('Y-m-d-H-i-s')) $pageCache = $file; // set an attribute to hold the valid file path to the cached file
// remove files that are no longer corrent
for($i=(($pageCache === NULL) ? 0 : 1); $i<count($fileArr);$i++) unlink($fileArr[$i]);
};
if($pageCache){
// cached page is found, lets process and output this puppy
include($pageCache);
} else {
// cached page is not found, let's build a cacheable page from the CMS template
$newCode = // ..... various code is processed here to isolate the cacheable code blocks and process while leaving the non-cacheable blocks intact ..... //
// create the new file path where the cached code will be placed
// first we need an expiration date
$cacheDate = date_create();
date_add($cacheDate, date_interval_create_from_date_string( $cache_increment . ' ' . $cache_interval)); // $cache_increment and $cache_interval are stored in the CMS DB for each page, giving the content manager control over the expiration of the page in cache
$filePath = $GLOBALS['iProducts']['physicalRoot'] . "/parsed/page_cache/" . date_format($cacheDate,'Y-m-d-H-i-s') . "^" . $_SERVER['SERVER_PORT'] . '^' . $_SERVER['HTTP_HOST'] . '^' . (($_SESSION['isMobile']) ? 'M' : 'D') ."^L" . $language . "^T" . $templateId . "^P" . $attributes['pageId'] . "^" .md5($GLOBALS['theURL4Cache']) . sha1($GLOBALS['theURL4Cache']) . '.php'; // create the file path
if(file_exists($filePath)) unlink($filePath); // delete the file if it already exists
$fp = fopen($filePath,"w"); // create the new file
flock($fp,LOCK_EX);
fwrite($fp,$newCode); // write the cache file
flock($fp,LOCK_UN);
// now output this puppy
eval($newCode);
};
你问为什么你的文件名这么乱?
嗯,很高兴你问到! CMS 的另一部分包括“智能缓存管理”,如果页面或模板被内容管理器修改,所有受影响的缓存页面都会从系统中清除。此外,由于 URL 查询字符串中的属性,页面中的内容可能会有所不同,如果它是移动设备呈现与否,SSL 与非 SSL,域名或当前会话语言(引擎支持多种语言内容)关联到同一页面,根据会话语言有条件地输出。
所以这里是一个缓存页面文件名示例: 2014-02-14-10-36-36^80^www.mydomain.com^M^L^T42^P41^a067036ef358f12a0049740f035a7ee688dbb0033c19a70163d6c453dbc5b84f1889ffe2.php
以下是文件名的组成部分: expire-date^port^domain^mobileOrDesktop^Language^Template^Page^md5+sha1OfURL.php
以下是组件说明:
- 过期日期:根据 CMS 中的内容管理器条目计算出此缓存文件应过期的日期/时间。 GLOB 可以使用它来过滤掉所有过期的文件并删除它们以通过 CRON 作业进行清理。这也是为了决定缓存页面是否足够新鲜,可以显示在代码的开头。
- 端口:80 或 443 表示是否通过 SSL 检索。根据 SSL 状态,内容可能会有所不同。
- domain: "www.mywebsite.com" 多个域名可以附加到一个 CMS 安装中,需要区分以便具有相同 REQUEST_URI 的两个域不会显示彼此的内容
- 移动或桌面:“M”或“D” - 允许相同的网址“嗅出”客户端并相应地提供内容。
- 语言:如果不使用多种语言,则为“L”,如果使用多种语言,则为“L-ENG / L-GER /...”
- 模板:“T#”,因此 templateId 47 将是“T47” - 允许通过文件名轻松过滤以识别所有使用给定模板的缓存页面,以便在 CMS 中修改模板时删除。
- page: "P#" 所以 pageId 12 将是 "P12" - 允许按文件名轻松过滤,以识别给定页面的所有缓存版本,以便在 CMS 中修改页面时删除。
- md5+sha1OfURL.php:采用 $_SERVER['REQUEST_URI'] 并对其进行两次编码(一次 MD5,一次 SHA1)连接结果以给出一个(合理的)表示 URL 的唯一 ID(因为查询字符串会影响内容) .
欢迎任何想法或建议。提前致谢!
【问题讨论】:
-
单个目录中有 250k 个文件?太疯狂了。每次运行 glob 操作时,您都在强制 Linux 加载目录文件并解析所有 250k 条目。您应该通过自动拆分到子目录来优化,例如如果您的文件哈希是
abcdefg,您应该将文件放入a/b/c/abcdefg类型的子目录模式。做任何最有意义的分层,并将每个目录的文件数保持在某个管理数。 -
谢谢,Marc B。我曾考虑过自动拆分为子目录,但后来我遇到了其他问题,例如基于 CRON 清理的缓存到期日期进行搜索,以及基于其他因素进行搜索像 pageId 和 templateId (用于在 CMS 中更新页面/模板时“智能”清理缓存文件)。基本上它从彼得那里偷来给保罗。
-
find可以根据inode数据扫描文件,例如上次访问时间或上次修改时间。简而言之,就是您的扫描删除。 -
您可能会考虑将这些文件符号链接到 cron 将查看的单独目录中?
-
Mark B:按访问时间/最后修改时间过滤会很好,因为所有页面都有相同的过期时间,但不幸的是它们没有,它们可以按页面或模板单独设置。