纯php实现大文件全文搜索

2011-12-26 04:23:54 +08:00
 Tianpu
一直想做的其实是日志分析 接着密码泄漏的东风 终于有兴趣花了点时间做出来 我这边上传超慢 现在才只有csdn的数据可以分析 不输出行号的情况下 全文匹配 1.8s 环境是128M内存的buyvm的VPS

差不多耗费40M左右的内存 我觉得挺满意的 代码写的比较难看 主要是利用fseek函数

心得:
1、explode可能很慢 str_replace也是 正则匹配还行 但是也有可能很慢
2、一次读少量数据 然后while读取比一次读大量数据要快一个数量级 出了磁盘IO外 主要是正则匹配随着数据量是指数递减的

没有输出行号 不够好看 哎 行号要匹配一次 又是一次系统开销 希望vibbow分享下他的体会 怎么就那么快呢

如果做成多线程的 一次查询用6个左右的进程 我觉得有可能达到vibbow的速度 不过行号还是没法解决
6303 次点击
所在节点    PHP
29 条回复
Tianpu
2011-12-26 04:24:12 +08:00
<?php
// error_reporting(0);
$time_start = getmicrotime();
session_start();
$key = $_GET['key'];
$time = $_GET['time'];
$files = array('csdn.sql');
$keyminlen = 4;
$step = 16000;
$limit = 1024000;
$lend = "\n";
$path = './temp/';
$data = './data/';
if($_GET['action']=='clear'){
unlink($path.$_SESSION['sid']);
$_SESSION['sid'] = '';
}
$sid = $_SESSION['sid'];
if($sid==''){
$sid = session_id();
$_SESSION['sid'] = $sid;
}
function getmicrotime(){
list($usec, $sec) = explode(" ",microtime());
return ((float)$usec + (float)$sec);
}
function cache($file,$str){
global $sid,$path,$limit,$key,$time;
$ssize = filesize($path.$sid);
if($ssize>$limit){
echo "<a href=temp/$sid> result right click to save as</a><br>";
echo "<br><br><a href=?action=clear>get another keyword</a><br><br>processed in $time seconds";
die('too many result, contact us if you do need it');
}
if(preg_match_all("/(.*?)".$key."(.*?)\\n/i",$str,$match)){
if($ssize>0) file_put_contents($path.$sid,$match[0],FILE_APPEND);
else file_put_contents($path.$sid,$file."\n".$match[0],FILE_APPEND);
}
print_r(preg_last_error());
}
function large($file,$start,$repeat,$plus){
global $step,$data,$key;
$r = array();
$return = $temp = '';
$i = 0;
$handle = fopen($data.$file,'r');
while($i<$repeat && !feof($handle)){
fseek($handle, $start, SEEK_SET);
if($i==0) $temp = $plus.fread($handle,$step);
else $temp = fread($handle,$step);
if(strpos($temp,$key)){
$r[1].= $temp;
$r[2] = true;
$r[3] = substr($temp,-60);
}
$r[0]+= $step;
$start+= $step;
$i++;
}
fclose($handle);
return $r;
}
function process($file,$start,$lnum){
global $step,$lend,$fid,$time,$time_start,$key,$data;
$r = array();
$filesize = filesize($data.$file);
$block = 51200000;
$repeat = 10;
$tstr = '';
$count = 0;
while($count<$block && $start<$filesize){
$temp = large($file,$start,$repeat,$temp[3]);
$start+=$temp[0];
$count+=$temp[0];
if($temp[2]) cache($file,$temp[1]);
}
$time+=getmicrotime()-$time_start;
echo "<br><br>processed in $time seconds<br> continue<br ><br>";
if($start>=$filesize){
$fid++;
die($file.' KO.<br>continue to another<br><meta http-equiv="Refresh" content="0;url=?key='.rawurlencode($key).'&time='.$time.'&fid='.$fid.'">');
}
$r[] = $start;
$r[] = $lnum;
return $r;
}
$start = $_GET['start'];
$lnum = $_GET['lnum'];
$fid = $_GET['fid'];
if($fid<1) $fid = 0;
if($fid>count($files)-1){
if(filesize($path.$sid)>0){
echo "<a href=temp/$sid> result right click to save as</a><br>";
echo '<pre>'.file_get_contents($path.$sid).'</pre>';
}
else echo 'no result';
echo "<br><br><a href=?action=clear>get another keyword</a><br><br>processed in $time seconds";
die();
}
$file = $files[$fid];
if($key=='') die('<form>keyword: <input type=text name=key> <input type=submit></form>');
else{
if(strlen($key)<$keyminlen) die('keyword too short');
if($start<1) $start = 0;
if($lnum<2) $lnum = 1;
$t = process($file,$start,$lnum);
$time+=getmicrotime()-$time_start;
echo '<meta http-equiv="Refresh" content="0;url=?key='.rawurlencode($key).'&fid='.$fid.'&time='.$time.'&start='.$t[0].'&lnum='.$t[1].'&file='.$file.'">';
}
?>
Tianpu
2011-12-26 04:27:29 +08:00
文件说明:
./x.php

data目录放入各种数据
./data/xxx.sql
./data/xxx.txt
./temp/

./temp需要写权限 用于保存用户临时数据

$files = array('csdn.sql'); 这里用数组调用各种数据

我还在测试 估计没有太多改进的余地了 毕竟磁盘IO是省不了的 基本运行也要点时间

欢迎提出改进意见
Tianpu
2011-12-26 05:15:53 +08:00
最终结果 天涯+csdn 6666条数据 返回113K的结果 耗时7.8s 还好了 睡觉
vibbow
2011-12-26 05:18:40 +08:00
相信我,搜索时大部分时间不是浪费在硬盘时间上,而是strpos过程上。
vibbow
2011-12-26 05:20:28 +08:00
也就是说硬盘性能根本不是瓶颈,而是CPU性能。
Tianpu
2011-12-26 05:24:39 +08:00
:) 我一次只读16k左右的一个块 这些因素应该已经规避了 可能是VPS烂吧 晚安啦
vibbow
2011-12-26 06:43:26 +08:00
我没觉得我的搜索速度有多快啊...
既不支持正则,也不支持行号输出...

我搜索服务端算法大体改了3次,我整理整理代码加一下注释,稍后发上来。

性能信息就是:在E5400处理器上,只使用1核心(PHP一次也只能使用一个核心),7200转普通sata硬盘,3G内存上(根据Process Explorer的记录,httpd进程峰值占用了650M的内存),对所有9个数据库搜索(纯文本文件,总大小4.6G),单关键字搜索大约需要3分钟,10关键字并发搜索大约需要5分钟...
vibbow
2011-12-26 06:44:42 +08:00
当然了,运行时间也是和结果数量是成正比的。
如果结果数量特别多的话就需要七八分钟了。
vibbow
2011-12-26 08:21:51 +08:00
发现一个问题诶。
你的代码,搜索正常的csdn文件速度是很快
但是如果我自己创建个文件,每行都是 "vibbow\r\n" 重复上两三万行
那么用你的代码搜索vibbow,一个结果都木有...
vibbow
2011-12-26 08:33:25 +08:00
而且你的代码貌似区分大小写来着...
areless
2011-12-26 09:23:18 +08:00
存MYSQL,全匹配很快的。模糊查询不用外部索引那是慢的不得了 =_____,=
xdz0611
2011-12-26 09:30:35 +08:00
@areless 有道理,反正数据库擅长干这个。
vibbow
2011-12-26 09:49:38 +08:00
呃...
终于读懂了你的代码,不过你的代码貌似没有考虑读取步进的问题。
举例来说,比如说我有这么 12345678这么一串字符串,我想搜索456。
你的代码先读取了1234,发现没有匹配,然后直接读取了5678,发现还是没有匹配。
于是就认为不匹配了。
我觉得这就是你 large 函数里发生的错误...

呃... 刚才用XDebug对我的代码进行了一下性能分析,发现最耗性能的居然是strtolower函数...
看来有必要做两份数据库了...
dndx
2011-12-26 09:55:55 +08:00
@vibbow 目前我大概能做到118860631行记录7-8秒正则遍历完,同时8个并发,但是在明文问题不解决之前我先把服务关了,否则跨省可不是玩的。
vibbow
2011-12-26 09:57:17 +08:00
@dndx 明文问题难道很难解决么?出去关键字外随机星号几个字符就行了。
dndx
2011-12-26 09:59:07 +08:00
@vibbow 问题是不同文件格式不一,不好判断哪部分是密码。我没有对文件做任何预处理。你有木有Gtalk,我倒很想跟你交流交流。
vibbow
2011-12-26 10:01:52 +08:00
@dndx GTalk ... 木有... 我直接开Gmail吧.... vibbow@gmail.com
Tianpu
2011-12-26 10:30:56 +08:00
@vibbow $r[3] = substr($temp,-60); 极少有一行超过60字节的 用这个临时数据来保证不遗漏 当然会有不好看的多余数据 理论上不会少
Tianpu
2011-12-26 10:32:30 +08:00
@vibbow 这个 用\n匹配的 按说没事 还有限制一次搜索结果在1M左右 最短是4字节 可能是被什么给限制了
Tianpu
2011-12-26 10:45:09 +08:00
还可以这样测试下 if(preg_match_all("/\\n(.*?)".$key."(.*?)\\n/i","\n".$str."\n",$match)) 行内匹配 无论如何不出结果都是没天理了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/24333

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX