PHP中的5分钟文件缓存

35

我有一个非常简单的问题:在PHP中,如果本地版本已经下载超过5分钟,最好的下载文件方式是什么?

在我的实际情况下,我想从远程托管的csv文件获取数据,目前我正在使用:

$file = file_get_contents($url);

没有任何本地副本或缓存。将其转换为缓存版本的最简单方法是什么,其中最终结果不会改变($file保持不变),但如果不久前获取了本地副本,则使用本地副本?

9个回答

88
使用本地缓存文件,在使用之前只需检查文件的存在和修改时间。例如,如果$cache_file是一个本地缓存文件名:
if (file_exists($cache_file) && (filemtime($cache_file) > (time() - 60 * 5 ))) {
   // Cache file is less than five minutes old. 
   // Don't bother refreshing, just use the file as-is.
   $file = file_get_contents($cache_file);
} else {
   // Our cache is out-of-date, so load the data from our remote server,
   // and also save it over our cache for next time.
   $file = file_get_contents($url);
   file_put_contents($cache_file, $file, LOCK_EX);
}

(未经测试,但基于我目前使用的代码。)
无论通过这段代码的哪种方式,$file最终都会成为你所需的数据,并且如果缓存是新鲜的,它将使用缓存,否则将从远程服务器获取数据并刷新缓存。
编辑:我对文件锁定的理解比我上面写的时候更多了一些。如果你对这里的文件锁定有所担心,可以阅读一下这个答案
如果你担心锁定和并发访问,我会说最干净的解决方案是将内容使用file_put_contents写入一个临时文件,然后使用rename()将其重命名为$cache_file,这应该是一个原子操作,即$cache_file要么是旧内容,要么是完整的新内容,绝不会是写了一半的内容。

谢谢你的代码,Matt!它非常干净,注释得很好,而且不需要任何修改就可以运行! - hyperknot
1
@zsero 很酷。但一定要加入一些错误检查 :) 例如,如果缓存目录不可被 Web 服务器用户写入,你可能会遇到问题... - Matt Gibson
1
是的,它可能需要一些错误检查,但这是一个如此小的项目,没有其他人会使用或部署这段代码。如果它出现问题,else 部分实际上会进入无缓存模式,而不是崩溃。很好。 - hyperknot
1
请确保在顶部定义您的$cache_file(示例):$cache_file = $_SERVER['DOCUMENT_ROOT'] . '/my-cache.php'; - farjam
2
据我所知,状态缓存在每次脚本运行开始时被清除,因此只要您不在同一脚本中多次调用此方法,它就应该没问题。(我刚刚检查了一下,在filestat.c中,您会看到在PHP_RINIT_FUNCTION回调中清除了状态缓存,因此它肯定会在每个请求开始时重置。) - Matt Gibson
显示剩余4条评论

10
尝试使用phpFastCache,它支持文件缓存,您无需编写缓存类。易于在共享主机和VPS上使用。
以下是示例:
<?php

// change files to memcached, wincache, xcache, apc, files, sqlite
$cache = phpFastCache("files");

$content = $cache->get($url);

if($content == null) {
     $content = file_get_contents($url);
     // 300 = 5 minutes 
     $cache->set($url, $content, 300);
}

// use ur $content here
echo $content;

你能否运行一个基准测试来查看doctrine-cache是否比使用文件缓存的phpFastCache更快吗? - user4271704

4

这里有一个简单的版本,它还会向远程主机发送一个Windows User-Agent字符串,以便您不会因为没有正确的标头而被认为是问题制造者。

<?php

function getCacheContent($cachefile, $remotepath, $cachetime = 120){

    // Generate the cache version if it doesn't exist or it's too old!
    if( ! file_exists($cachefile) OR (filemtime($cachefile) < (time() - $cachetime))) {

        $options = array(
            'method' => "GET",
            'header' => "Accept-language: en\r\n" .
            "User-Agent: Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)\r\n"
        );

        $context = stream_context_create(array('http' => $options));
        $contents = file_get_contents($remotepath, false, $context);

        file_put_contents($cachefile, $contents, LOCK_EX);
        return $contents;

    }

    return file_get_contents($cachefile);
}

0
如果您正在使用任何类型的数据库系统,您可以将此文件缓存到其中。创建一个用于缓存信息的表,并至少提供以下字段:
  • 标识符;您可以使用它来检索下次需要该文件的内容。可能是类似于文件名的东西。
  • 从上次从URL下载文件的时间戳。
  • 要么是文件的路径,在本地文件系统中存储它,要么使用BLOB类型字段仅在数据库中存储文件本身的内容。我个人建议只存储路径。如果文件非常大,您绝对不想将其放入数据库中。

现在,当您下次运行上面的脚本时,请首先检查标识符是否在数据库中,并提取时间戳。如果当前时间与存储的时间戳之间的差异大于5分钟,则从URL中提取并更新数据库。否则,从数据库加载文件。

如果您没有设置数据库,则可以使用文件执行相同的操作,其中一个文件或文件中的字段将包含您上次下载文件的时间戳。


0

首先,您可能想要检查设计模式:延迟加载

实现应更改为始终从本地缓存加载文件。 如果本地缓存不存在或文件时间抖动超过5分钟,则从服务器获取文件。

伪代码如下:

$time = filetime($local_cache)
if ($time == false || (now() - $time) > 300000)
     fetch_localcache($url)  #You have to do it yourself
$file = fopen($local_cache)

0

最佳实践


$cacheKey=md5_file('file.php');


-1

在第一次访问时,您可以保存文件的副本,然后在后续访问中使用filemtime检查本地文件的最后修改时间戳。


-2

我认为你想要一些(伪代码)逻辑,类似于:

if ($file exists) {
  if ($file time stamp older than 5 minutes) {
     $file = file_get_contents($url)
  }
} else {
     $file = file_get_contents($url)
}

use $file

@zsero .. 额外的层级存在是因为你无法测试不存在的文件的时间戳。 - Peter M

-2
你可以将它包装成一个缓存方法:
function getFile($name) {
    // code stolen from @Peter M
    if ($file exists) {
      if ($file time stamp older than 5 minutes) {
         $file = file_get_contents($url)
      }
    } else {
         $file = file_get_contents($url)
    }
    return $file;
}

1
就像Peter M的回答一样,我不知道为什么if和else是一样的? - hyperknot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接