在打开的大文件中复制数据

Posted

技术标签:

【中文标题】在打开的大文件中复制数据【英文标题】:Copy data from huge files while they are open 【发布时间】:2015-08-21 12:49:48 【问题描述】:

我正在尝试使用 Perl 将大文件中的数据合并到一个组合文件中。

文件将处于打开状态,并且大量数据不断添加到文件中。每分钟添加大约 50,000 行。

文件存储在网络共享文件夹中,可供 10 到 30 台机器访问。

这些是 JMeter 生成的 JTL 文件。

此合并每分钟运行大约 6 或 7 小时,所用时间不应超过 30 到 40 秒。

该过程由部署在 Linux 机器中的 Web 应用程序每分钟触发一次。

我编写了一个脚本,它将单个文件添加到组合文件中的最后一行存储在单独的文件中。

这可以正常工作长达 15 分钟,但会不断增加合并时间。

我的脚本

#!/usr/bin/perl

use File::Basename;
use File::Path;

$consolidatedFile = $ARGV[0];
$testEndTimestamp = $ARGV[1];
@csvFiles         = @ARGV[ 2 .. $#ARGV ];
$testInProcess    = 0;
$newMerge         = 0;
$lastLines        = "_LASTLINES";
$lastLine         = "_LASTLINE";

# time() gives current time timestamp
if ( time() <= $testEndTimestamp ) 
    $testInProcess = 1;


# File exists, has a size of zero
if ( -z $consolidatedFile ) 
    mkdir $consolidatedFile . $lastLines;
    $newMerge = 1;


open( CONSOLIDATED, ">>" . $consolidatedFile );

foreach my $file ( @csvFiles ) 

    open( INPUT, "<" . $file );
    @linesArray = <INPUT>;
    close INPUT;

    if ( $newMerge ) 
        
        print CONSOLIDATED @linesArray[ 0 .. $#linesArray - 1 ];
        
        open my $fh, ">", $consolidatedFile . $lastLines . "/" . basename $file . $lastLine;
        print $fh $linesArray[ $#linesArray - 1 ];
        close $fh;
    
    else 

        open( AVAILABLEFILE, "<" . $consolidatedFile . $lastLines . "/" . basename $file . $lastLine );
        @lineArray = <AVAILABLEFILE>;
        close AVAILABLEFILE;

        $availableLastLine = $lineArray[0];

        open( FILE, "<" . $file );
        while ( <FILE> ) 
            if ( /$availableLastLine/ ) 
                last;
            
        
        @grabbed = <FILE>;
        close( FILE );

        if ( $testInProcess ) 

            if ( $#grabbed > 0 ) 

                pop @grabbed;
                print CONSOLIDATED @grabbed;

                open( AVAILABLEFILE, ">" . $consolidatedFile . $lastLines . "/" . basename $file . $lastLine );
                print AVAILABLEFILE $grabbed[ $#grabbed - 1 ];
            
            close AVAILABLEFILE;
        
        else 

            if ( $#grabbed >= 0 ) 
                print CONSOLIDATED @grabbed;
            
        
    


close CONSOLIDATED;

if ( !$testInProcess ) 

    rmtree $consolidatedFile . $lastLines;

我需要优化脚本以减少时间。

是否可以将最后一行存储在缓存中?

谁能建议这种合并的另一种方式?

另一个将最后一行存储在缓存中而不是文件中的脚本。

即使这样也不会在 1 分钟内完成合并。

#!/usr/bin/perl

use CHI;

use File::Basename;
use File::Path;

my $cache = CHI->new(
driver   => 'File',
root_dir => '/path/to/root'
);

$consolidatedFile = $ARGV[0];
$testEndTimestamp = $ARGV[1];
@csvFiles         = @ARGV[ 2 .. $#ARGV ];
$testInProcess    = 0;
$newMerge         = 0;
$lastLines        = "_LASTLINES";
$lastLine         = "_LASTLINE";

# time() gives current time timestamp
if ( time() <= $testEndTimestamp ) 
    $testInProcess = 1;


# File exists, has a size of zero
if ( -z $consolidatedFile ) 
    $newMerge = 1;


open( CONSOLIDATED, ">>" . $consolidatedFile );

foreach my $file (@csvFiles) 

    $fileLastLineKey =
      $consolidatedFile . $lastLines . "_" . basename $file . $lastLine;

    open( INPUT, "<" . $file );
    @linesArray = <INPUT>;
close INPUT;

if ($newMerge) 

    print CONSOLIDATED @linesArray[ 0 .. $#linesArray - 1 ];
    $fileLastLine = $linesArray[ $#linesArray - 1 ];
    $cache->set( $fileLastLineKey, $fileLastLine );


else 

    $availableLastLine = $cache->get($fileLastLineKey);

    open( FILE, "<" . $file );
    while (<FILE>) 
        if (/$availableLastLine/) 
            last;
        
    
    @grabbed = <FILE>;
    close(FILE);

    if ($testInProcess) 

        if ( $#grabbed > 0 ) 

            pop @grabbed;
            print CONSOLIDATED @grabbed;

            $fileLastLine = $grabbed[ $#grabbed - 1 ];
            $cache->set( $fileLastLineKey, $fileLastLine );
        
    
    else 

        if ( $#grabbed >= 0 ) 
            print CONSOLIDATED @grabbed;
            $cache->remove($fileLastLineKey);
        
    



close CONSOLIDATED;

我正在考虑从最后一行读取文件到所需行并将这些行复制到合并文件中。

任何人都可以就此提出建议吗???

【问题讨论】:

您的咨询合同似乎已外包。对于其他人来解决上述问题,他/她将不得不抛弃您的代码,并实际从头开始构建一个解决方案,根据您的具体情况量身定制。要查看这是否可行,请检查从负载下的文件系统读取此系统上每行 200 个字符的简单 50,000 行文件需要多长时间。您可能需要考虑寻找专业人士为您完成这项工作。 我尝试在共享网络文件夹中运行包含 5 个文件的脚本,平均每分钟增加 50000 行。合并时间以每分钟 1 秒的速度增加。 我会在每个输入文件上使用File::Tail 创建一个持续运行的脚本,在将新数据写入源文件时进行合并和写入。所以不需要文件指针的最后一行存储或重定位。此外,使用 tell() 和 seek() 会比使用正则表达式匹配行要快得多。 【参考方案1】:

您可能想尝试在 binmode 中打开文件并循环读取它。这通常会提供显着的性能改进。以下函数是一个示例,这里我将最大 $maxblocks 个文件块放在数组上,从块 $offset 开始,在作为引用传递的数组中。请注意,当文件不够大时,最后一个块可能不包含整个 $block 字节。

sub file2binarray 
  my $file=shift;
  my $array=shift;
  my $maxblocks=shift;
  my $offset=shift;

  my $block=2048;

  $offset=0 if ((!defined($offset))  || ($offset   !~/^\s*\d+\s*$/o));
  $maxblocks="ALL" 
            if (!defined($maxblocks) || ($maxblocks!~/^\s*\d+\s*$/o)); 

  my $size=(stat($file))[7];
  my $mb=$size/$block;
  $mb++ if ($mb*$block<$size);
  $maxblocks=$mb-$offset if(($maxblocks eq "ALL")||
                             ($maxblocks>$mb-$offset));
  $offset*=$block;
  open(IN,"$file") || die("Cannot open file <$file>\n");
  binmode(IN);
  $bytes_read=$block;
  seek(IN,$offset,0);

  my ($blk,$bytes_read,$buffer)=(0,0,"");

  while (($bytes_read==$block)&& ($blk<$maxblocks))
      $bytes_read=sysread(IN,$buffer,$block);
      push(@$array,$buffer);
      $blk++;
  

  close(IN);

读取整个文件,例如你这样称呼它

my @array;
my $filename="somefile";
file2binarray ($filename,\@array,"ALL",0);

但您可能更愿意在循环中调用它,并在偏移量上进行一些记账,并在后续调用之间解析数组。 希望这会有所帮助。

【讨论】:

以上是关于在打开的大文件中复制数据的主要内容,如果未能解决你的问题,请参考以下文章

在 PHP 中获取大文件(> 2 GB)文件大小的最佳方法? [复制]

基于手机信令的大数据分析教程(番外二)——GIS中生成面要素质心点

小象精品导读帖HBase的大用处

sqlserver2005怎么执行260M的大脚本文件? 打开脚本总是报“未能完成操作,存储空间不足”

HTML 的哪种数据类型? [复制]

Java中的大数据处理