V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Tianpu
V2EX  ›  PHP

纯php实现大文件全文搜索

  •  
  •   Tianpu · 2011-12-26 04:23:54 +08:00 · 6433 次点击
    这是一个创建于 4718 天前的主题,其中的信息可能已经有所发展或是发生改变。
    一直想做的其实是日志分析 接着密码泄漏的东风 终于有兴趣花了点时间做出来 我这边上传超慢 现在才只有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   ·   实用小工具   ·   1237 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 23:45 · PVG 07:45 · LAX 15:45 · JFK 18:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.