在工作之中避免不了要对大的日志文件进行分析处理,要处理文件首先需要读取文件到内存,但是对于动辄几个G的文件,一次性读取到内存显然是不合理的,并且还需要调整配置。所以,需要借鉴MapReduce的分而治之的思想,将大文件拆分成小文件,然后再逐个分析,汇总。
php读取文件的方式对比
file_get_contents($des, null, null, $offset, $length);// 首选,从指定位置读取指定大小的数据
fopen, fread; // 含有指针,流式读取,按长度读取
fopen, fgets; // 含有指针,流式读取,按行读取
file(); // 把整个文件读入一个数组中,不适合大文件
readfile(); // 读取文件到输出缓冲,不适合
切割日志文件的方式可以按行和按大小来进行:
按大小的方式实现简单,效率相对较高,单个文件的大小确定,但是存在破损的记录;
按行的方式则可以保证记录不被损坏,效率稍微低一点。
针对每个大文件,切割完了之后需要记录一份索引文件,记录小文件的存储位置名称。
有了这些小文件之后就可以采用队列的形式,使用多机器,多进程来处理他们了。
<?php
class BigData{
public $des = 'bigdata.log';
public $index_file = 'bigdata_index.log';
public $index_file2 = 'bigdata_index2.log';
public $length = 64; // 64M
public function randStr(){
$base = join('', array_merge(range(0, 9) , range('a', 'z') , range('A', 'Z')));
$len = rand(20, 50);
$out = '';
for($i = 0; $i < $len; $i++){
$out .= $base[rand(0, 42)];
}
return $out;
}
// 生产大文件
public function create(){
$str = '[ 2019-10-09T16:34:36+08:00 ] 127.0.0.1 127.0.0.1 POST /site/product/getdetailtableindustry.html?a=';
$des = $this->des;
for($j = 0; $j < 10; $j++){
$date = '';
for($i = 0; $i < 400000; $i++){
$temp = $str . randStr() . ';' . PHP_EOL;
$date .= $temp;
}
file_put_contents($des, $date, FILE_APPEND);
}
}
// 切割文件,规定大小,数据存在截断的情况
public function cutData(){
$offset = 0;
$i = 0;
$len = $this->length * 1024 * 1024;
while(true){
$data = '';
$data = file_get_contents($this->des, null, null, $offset, $len);
if($data == ''){
break;
}
$file = 'items/bigdata_'.$i.'.log';
file_put_contents($file, $data);
file_put_contents($this->index_file, $file . PHP_EOL, FILE_APPEND);
$offset += $len;
$i++;
}
}
// 切割文件,按行,文件大小不精确
public function cutData2(){
$fp = fopen($this->des, 'rb');// 兼容二进制文件
$itemFileId = 0;
$file = 'items/bigdata2_'.$itemFileId.'.log';
file_put_contents($this->index_file2, $file . PHP_EOL, FILE_APPEND);
while(!feof($fp)){ // 判断是否已经达到文件底部
$res = $this->getLines($fp, $itemFileId);
if($res == true){
$itemFileId++;
$file = 'items/bigdata2_'.$itemFileId.'.log';
file_put_contents($this->index_file2, $file . PHP_EOL, FILE_APPEND);
}
}
fclose($fp);
}
protected function getLines($fp, $itemFileId){
$data = '';
for($i = 0; $i < 20000; $i++){
$data .= fgets($fp);
}
$file = 'items/bigdata2_'.$itemFileId.'.log';
file_put_contents($file, $data, FILE_APPEND);
// 注意,filesize的结果会被缓存,需清除缓存!!!,参考:https://www.php.net/manual/zh/function.clearstatcache.php
// 注意: 因为 PHP 的整数类型是有符号整型而且很多平台使用 32 位整型,对 2GB 以上的文件,一些文件系统函数可能返回无法预期的结果。
$filesize = filesize($file) / (1024 * 1024);
clearstatcache();
// 预估100条记录的大小,确保文件最终大小在64M左右,我这里是1M
if($filesize >= ($this->length - 1)){
return true;
}else{
return false;
}
}
}
$m = new BigData();
$start = microtime(true);
// bigdata.log 文件大小 535M
$m->cutData();// 耗时:6.2122948169708 文件大小 64M
$m->cutData2();// 耗时:8.6819729804993 平均文件大小 65M
$end = microtime(true);
echo '耗时:' . ($end - $start);