好的,这是一个场景:我需要解析我的日志,以找出在没有实际观看"大图像"页面的情况下下载了多少次图像缩略图。。。这基本上是一个基于"拇指"与"完整"图像视图比例的热链接保护系统
考虑到服务器不断受到缩略图请求的轰炸,最有效的解决方案似乎是使用缓冲的apache日志,这些日志每1Mb写入磁盘一次,然后定期解析日志
我的问题是:如何在PHP中解析apache日志来保存数据,以下情况为真:
- 日志将被实时使用和更新,我需要我的PHP脚本能够在完成这项工作时读取它
- php脚本必须"记住"它读取的日志的哪些部分,以免两次读取同一部分并扭曲数据
- 内存消耗应该最低,因为日志可以在几个小时内轻松达到10Gb的数据
php记录器脚本将每60秒调用一次,并在这段时间内处理任意数量的日志行。。
我试过一起破解一些代码,但我在使用最少的内存时遇到了问题,找到了一种用"移动"文件大小跟踪指针的方法
这是日志的一部分:
212.180.168.244 - - [18/Jan/2012:20:06:57 +0100] "GET /t/0/11/11441/11441268.jpg HTTP/1.1" 200 3072 "-" "Opera/9.80 (Windows NT 6.1; U; pl) Presto/2.10.229 Version/11.60" "-"
122.53.168.123 - - [18/Jan/2012:20:06:57 +0100] "GET /t/0/11/11441/11441276.jpg HTTP/1.1" 200 3007 "-" "Opera/9.80 (Windows NT 6.1; U; pl) Presto/2.10.229 Version/11.60" "-"
143.22.203.211 - - [18/Jan/2012:20:06:57 +0100] "GET /t/0/11/11441/11441282.jpg HTTP/1.1" 200 4670 "-" "Opera/9.80 (Windows NT 6.1; U; pl) Presto/2.10.229 Version/11.60" "-"
在此处附上代码供您审查:
<?php
//limit for running it every minute
error_reporting(E_ALL);
ini_set('display_errors',1);
set_time_limit(0);
include(dirname(__FILE__).'/../kframework/kcore.class.php');
$aj = new kajaxpage;
$aj->use_db=1;
$aj->init();
$db=kdbhandler::getInstance();
$d=kdebug::getInstance();
$d->debug=TRUE;
$d->verbose=TRUE;
$log_file = "/var/log/nginx/access.log"; //full path to log file when run by cron
$pid_file = dirname(__FILE__)."/../kframework/cron/cron_log.pid";
//$images_id = array("8308086", "7485151", "6666231", "8343336");
if (file_exists($pid_file)) {
$pid = file_get_contents($pid_file);
$temp = explode(" ", $pid);
$pid_timestamp = $temp[0];
$now_timestamp = strtotime("now");
//if (($now_timestamp - $pid_timestamp) < 90) return;
$pointer = $temp[1];
if ($pointer > filesize($log_file)) $pointer = 0;
}
else $pointer = 0;
$pattern = "/([0-9]{1,3}'.[0-9]{1,3}'.[0-9]{1,3}'.[0-9]{1,3})[^'[]*'[([^']]*)'][^'"]*'"([^'"]*)'"'s([0-9]*)'s([0-9]*)(.*)/";
$last_time = 0;
$lines_processed=0;
if ($fp = fopen($log_file, "r+")) {
fseek($fp, $pointer);
while (!feof($fp)) {
//if ($lines_processed>100) exit;
$lines_processed++;
$log_line = trim(fgets($fp));
if (!empty($log_line)) {
preg_match_all($pattern, $log_line, $matches);
//print_r($matches);
$size = $matches[5][0];
$matches[3][0] = str_replace("GET ", "", $matches[3][0]);
$matches[3][0] = str_replace("HTTP/1.1", "", $matches[3][0]);
$matches[3][0] = str_replace(".jpg/", ".jpg", $matches[3][0]);
if (substr($matches[3][0],0,3) == "/t/") {
$get = explode("-",end(explode("/",$matches[3][0])));
$imgid = $get[0];
$type='thumb';
}
elseif (substr($matches[3][0], 0, 5) == "/img/") {
$get1 = explode("/", $matches[3][0]);
$get2 = explode("-", $get1[2]);
$imgid = $get2[0];
$type='raw';
}
echo $matches[3][0];
// put here your sql insert or update
$imgid=(int) $imgid;
if (isset($type) && $imgid!=1) {
switch ($type) {
case 'thumb':
//use the second slave in the registry
$sql=$db->slave_query("INSERT INTO hotlink SET thumbviews=1, imageid=".$imgid." ON DUPLICATE KEY UPDATE thumbviews=thumbviews+1 ",2);
echo "INSERT INTO hotlink SET thumbviews=1, imageid=".$imgid." ON DUPLICATE KEY UPDATE thumbviews=thumbviews+1";
break;
case 'raw':
//use the second slave in the registry
$sql=$db->slave_query("INSERT INTO hotlink SET rawviews=1, imageid=".$imgid." ON DUPLICATE KEY UPDATE rawviews=rawviews+1",2);
echo "INSERT INTO hotlink SET rawviews=1, imageid=".$imgid." ON DUPLICATE KEY UPDATE rawviews=rawviews+1";
break;
}
}
// $imgid - image ID
// $size - image size
$timestamp = strtotime("now");
if (($timestamp - $last_time) > 30) {
file_put_contents($pid_file, $timestamp . " " . ftell($fp));
$last_time = $timestamp;
}
}
}
file_put_contents($pid_file, (strtotime("now") - 95) . " " . ftell($fp));
fclose($fp);
}
?>
也许你可以调整我的PHP版本tail来搜索你的最后一个时间戳,而不是计算行数,然后从那一点开始读取行,逐个处理它们?
- 尾部处理大文件
我自己会尝试一下,因为我有点好奇,但不幸的是现在无法这样做:(
解决方案是将日志存储到mysql数据库中。也许您可以编写一个C语言程序来解析存储在mysql中的日志文件。它会更快一个数量级,而且不是很难。另一种选择是使用phyton,但我认为使用数据库是必要的。您可以使用全文索引来匹配字符串。Python也可以编译成二进制文件。这使它更有效率。根据请求:日志文件以增量方式堆叠。这并不是说你一次提供10GB。
我会亲自将日志条目发送到正在运行的脚本中。Apache将通过用管道(|)启动日志的文件名来实现这一点。如果这不起作用,您也可以创建一个fifo(请参阅mkfifo)。
运行中的脚本(不管是什么)可以缓冲x行,并在此基础上做它需要做的事情。读取数据并不是那么难,也不应该是你的瓶颈所在
我确实怀疑您在数据库中的INSERT语句会遇到问题。
我知道这个答案很晚了,但仍然有帮助(代码总是可以改进的)。
10Gb文件大小和所需内存听起来像是您的主要问题。Apache确实支持多个日志文件,而多日志文件的真正功能来自于以不同格式创建日志文件的能力http://httpd.apache.org/docs/1.3/multilogs.html
创建第二个日志文件,只包含实时日志监控所需的最少数据。在这种情况下,您可以首先从日志中删除用户代理字符串等。
根据您的示例日志行,这可能会使PHP必须加载的数据量减半。