纯php实现大文件全文搜索 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Tianpu
V2EX    PHP

纯php实现大文件全文搜索

  •  
  •   Tianpu 2011-12-26 04:23:54 +08:00 6742 次点击
    这是一个创建于 5038 天前的主题,其中的信息可能已经有所发展或是发生改变。
    一直想做的其实是日志分析 接着密码泄漏的东风 终于有兴趣花了点时间做出来 我这边上传超慢 现在才只有csdn的数据可以分析 不输出行号的情况下 全文匹配 1.8s 环境是128M内存的buyvm的VPS

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

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

    没有输出行号 不够好看 哎 行号要匹配一次 又是一次系统开销 希望vibbow分享下他的体会 怎么就那么快呢
    如果做成多线程的 一次查询用6个左右的进程 我觉得有可能达到vibbow的速度 不过行号还是没法解决
    29 条回复    1970-01-01 08:00:00 +08:00
    Tianpu
        1
    Tianpu  
    OP
       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
        2
    Tianpu  
    OP
       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
        3
    Tianpu  
    OP
       2011-12-26 05:15:53 +08:00
    最终结果 天涯+csdn 6666条数据 返回113K的结果 耗时7.8s 还好了 睡觉
    vibbow
        4
    vibbow  
       2011-12-26 05:18:40 +08:00
    相信我,搜索时大部分时间不是浪费在硬盘时间上,而是strpos过程上。
    vibbow
        5
    vibbow  
       2011-12-26 05:20:28 +08:00
    也就是说硬盘性能根本不是瓶颈,而是CPU性能。
    Tianpu
        6
    Tianpu  
    OP
       2011-12-26 05:24:39 +08:00
    :) 我一次只读16k左右的一个块 这些因素应该已经规避了 可能是VPS烂吧 晚安啦
    vibbow
        7
    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
        8
    vibbow  
       2011-12-26 06:44:42 +08:00
    当然了,运行时间也是和结果数量是成正比的。
    如果结果数量特别多的话就需要七八分钟了。
    vibbow
        9
    vibbow  
       2011-12-26 08:21:51 +08:00
    发现一个问题诶。
    你的代码,搜索正常的csdn文件速度是很快
    但是如果我自己创建个文件,每行都是 "vibbow\r\n" 重复上两三万行
    那么用你的代码搜索vibbow,一个结果都木有...
    vibbow
        10
    vibbow  
       2011-12-26 08:33:25 +08:00
    而且你的代码貌似区分大小写来着...
    areless
        11
    areless  
       2011-12-26 09:23:18 +08:00
    存MYSQL,全匹配很快的。模糊查询不用外部索引那是慢的不得了 =_____,=
    xdz0611
        12
    xdz0611  
       2011-12-26 09:30:35 +08:00
    @areless 有道理,反正数据库擅长干这个。
    vibbow
        13
    vibbow  
       2011-12-26 09:49:38 +08:00
    呃...
    终于读懂了你的代码,不过你的代码貌似没有考虑读取步进的问题。
    举例来说,比如说我有这么 12345678这么一串字符串,我想搜索456。
    你的代码先读取了1234,发现没有匹配,然后直接读取了5678,发现还是没有匹配。
    于是就认为不匹配了。
    我觉得这就是你 large 函数里发生的错误...

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

    我这边测试出来的是一次少量读取 迅速处理 然后PHP是具备处理大文件能力的 还有就是资源的节省很重要

    感谢热心的vibbow 感谢所有参与本帖的人
    alsotang
        22
    alsotang  
       2011-12-27 01:51:16 +08:00
    这就开始感谢了?.....我还要冒个泡....
    以后发代码可以发github的gist,这样indent和highlight都方便。具体可以在V2EX搜一下方法,贴个gist地址就行。
    vibbow
        23
    vibbow  
       2011-12-29 03:37:37 +08:00
    http://vsean.net/blog/post/98
    我的全文搜索代码整理完成。
    kojp
        24
    kojp  
       2011-12-29 14:34:03 +08:00
    我也感谢大家~~~~ (以没有看到这个帖子,还特意开了一个帖子)
    vibbow
        25
    vibbow  
       2011-12-30 08:03:13 +08:00
    又改进了一遍代码,现在瓶颈在硬盘了。
    10G数据,170秒全文搜完。
    lyxint
        26
    lyxint  
       2011-12-30 08:30:00 +08:00 via Android
    预先生成索引啊
    vibbow
        27
    vibbow  
       2011-12-30 08:47:09 +08:00
    @lyxint 近10G的数据,生成个全文索引没个一两天应该完不成吧...
    delectate
        28
    delectate  
       2011-12-30 08:49:13 +08:00
    10,000/170=58,硬盘了。
    lyxint
        29
    lyxint  
       2011-12-30 10:21:49 +08:00
    @vibbow 不会, 10G数据量很小
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1057 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 23:04 PVG 07:04 LAX 16:04 JFK 19:04
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86