如何上传大文件(大约10GB)

时间:2017-12-15 12:56:32

标签: php amazon-s3

我想使用PHP脚本(这是一个备份脚本)将大约10GB的存档转移到我的Amazon S3存储桶。

我实际使用以下代码:

$uploader = new \Aws\S3\MultipartCopy($s3Client, $tmpFilesBackupDirectory, [
        'Bucket' => 'MyBucketName',
        'Key'    => 'backup'.date('Y-m-d').'.tar.gz',
        'StorageClass' => $storageClass,
        'Tagging' => 'expiration='.$tagging,
        'ServerSideEncryption' => 'AES256',
    ]);

try
{
    $result = $uploader->copy();
    echo "Upload complete: {$result['ObjectURL']}\n";
}
catch (Aws\Exception\MultipartUploadException $e)
{
    echo $e->getMessage() . "\n";
}

我的问题是,几分钟后(假设为10分钟),我收到来自apache服务器的错误消息:504网关超时。 我知道这个错误与我的Apache服务器的配置有关,但我不想增加服务器的超时。

我的想法是使用PHP SDK低级API执行以下步骤:

  1. 使用Aws \ S3 \ S3Client :: uploadPart()方法手动上传5个部分,并存储在$ _SESSION中获得的响应(我需要ETag值来完成上传);
  2. 使用标题重新加载页面('Location:xxx');
  3. 再次执行接下来的5个部分的前两个步骤,直到所有部分都上传完毕;
  4. 使用Aws \ S3 \ S3Client :: completeMultipartUpload()完成上传。
  5. 我认为这应该可行,但在使用此方法之前,我想知道是否有更简单的方法来实现我的目标,例如使用高级API ......

    有什么建议吗?

    注意:我不是在寻找一些现有的脚本:我的主要目标是学习如何解决这个问题:)

    致以最诚挚的问候,

    梅西

2 个答案:

答案 0 :(得分:0)

为什么不使用AWS CLI复制文件?您可以在CLI中创建脚本,这样一切都是AWS原生的。 (Amazon上有tutorial。)您可以使用scp命令:

Sql.newInstance

从我的角度来看,在AWS中进行工作会更容易,AWS具有移动文件和数据的功能。如果您想使用shell脚本,那么有关自动化EC2备份的article有一个很好的备份,还有关于备份选项的更多详细信息。

答案 1 :(得分:0)

要回答我自己的问题(我希望有一天可能对某人有帮助!),他就是我一步一步解决问题的方法:

1 /当我加载页面时,检查存档是否已存在。如果没有,我创建我的.tar.gz文件,然后使用header()重新加载页面。 我注意到这一步很慢,因为要存档大量数据。这就是我重新加载页面以避免在接下来的步骤中超时的原因!

2 /如果备份文件存在,我使用AWS MultipartUpload发送10个100MB的块。每次成功发送一个块时,我都会更新一个会话变量($ _SESSION ['backup'] ['partNumber']),以了解下一个需要上传的块是什么。 一旦发送了10个块,我会再次重新加载页面以避免超时。

3 /我重复第二步,直到完成所有部分的上传,使用我的会话变量知道下一个需要发送的上传部分。

4 /最后,我完成了分段上传,并删除了本地存储的档案。

您可以在重新加载页面之前发送超过10次10​​0MB的内容。我选择此值以确保即使下载速度很慢也不会达到超时。但我想我每次都可以轻松发送大约5GB的容量。 注意:您无法将脚本重定向到自身太多时间。有一个限制(我认为Chrome和Firefox之前大约有20次出错,而IE更多)。在我的情况下(存档大约10GB),每次重新加载1GB就可以了(该页面将被重新加载大约10次)。但是存档大小增加了,我每次都要发送更多的块。

这是我的完整脚本。我当然可以改进,但它现在运作得很好,它可以帮助有类似问题的人!

public function backup()
{
    ini_set('max_execution_time', '1800');
    ini_set('memory_limit', '1024M');

    require ROOT.'/Public/scripts/aws/aws-autoloader.php';

    $s3Client = new \Aws\S3\S3Client([
        'version' => 'latest',
        'region'  => 'eu-west-1',
        'credentials' => [
            'key'    => '',
            'secret' => '',
        ],
    ]);

    $tmpDBBackupDirectory = ROOT.'Var/Backups/backup'.date('Y-m-d').'.sql.gz';
    if(!file_exists($tmpDBBackupDirectory))
    {
        $this->cleanInterruptedMultipartUploads($s3Client);
        $this->createSQLBackupFile();
        $this->uploadSQLBackup($s3Client, $tmpDBBackupDirectory);
    }

    $tmpFilesBackupDirectory = ROOT.'Var/Backups/backup'.date('Y-m-d').'.tar.gz';
    if(!isset($_SESSION['backup']['archiveReady']))
    {
        $this->createFTPBackupFile();
        header('Location: '.CURRENT_URL);
    }

    $this->uploadFTPBackup($s3Client, $tmpFilesBackupDirectory);

    unlink($tmpDBBackupDirectory);
    unlink($tmpFilesBackupDirectory);
}

public function createSQLBackupFile()
{
    // Backup DB
    $tmpDBBackupDirectory = ROOT.'Var/Backups/backup'.date('Y-m-d').'.sql.gz';

    if(!file_exists($tmpDBBackupDirectory))
    {
        $return_var = NULL;
        $output = NULL;
        $dbLogin = '';
        $dbPassword = '';
        $dbName = '';
        $command = 'mysqldump -u '.$dbLogin.' -p'.$dbPassword.' '.$dbName.' --single-transaction --quick | gzip > '.$tmpDBBackupDirectory;
        exec($command, $output, $return_var);
    }

    return $tmpDBBackupDirectory;
}


public function createFTPBackupFile()
{
    // Compacting all files
    $tmpFilesBackupDirectory = ROOT.'Var/Backups/backup'.date('Y-m-d').'.tar.gz';

    $command = 'tar -cf '.$tmpFilesBackupDirectory.' '.ROOT;
    exec($command);

    $_SESSION['backup']['archiveReady'] = true;

    return $tmpFilesBackupDirectory;
}


public function uploadSQLBackup($s3Client, $tmpDBBackupDirectory)
{
    $result = $s3Client->putObject([
        'Bucket' => '',
        'Key'    => 'backup'.date('Y-m-d').'.sql.gz',
        'SourceFile' => $tmpDBBackupDirectory,
        'StorageClass' => '',
        'Tagging' => '',
        'ServerSideEncryption' => 'AES256',
    ]);
}


public function uploadFTPBackup($s3Client, $tmpFilesBackupDirectory)
{
    $storageClass = 'STANDARD_IA';
    $bucket = '';
    $key = 'backup'.date('Y-m-d').'.tar.gz';
    $chunkSize = 100 * 1024 * 1024; // 100MB
    $reloadFrequency = 10;

    if(!isset($_SESSION['backup']['uploadId']))
    {
        $response = $s3Client->createMultipartUpload([
            'Bucket' => $bucket,
            'Key'    => $key,
            'StorageClass' => $storageClass,
            'Tagging' => '',
            'ServerSideEncryption' => 'AES256',
        ]);
        $_SESSION['backup']['uploadId'] = $response['UploadId'];
        $_SESSION['backup']['partNumber'] = 1;
    }

    $file = fopen($tmpFilesBackupDirectory, 'r');
    $parts = array();

    //Reading parts already uploaded
    for($i = 1; $i < $_SESSION['backup']['partNumber']; $i++)
    {
        if(!feof($file))
        {
            fread($file, $chunkSize);
        }
    }

    // Uploading next parts
    while(!feof($file))
    {
        do
        {
            try
            {
                $result = $s3Client->uploadPart(array(
                    'Bucket'     => $bucket,
                    'Key'        => $key,
                    'UploadId'   => $_SESSION['backup']['uploadId'],
                    'PartNumber' => $_SESSION['backup']['partNumber'],
                    'Body'       => fread($file, $chunkSize),
                ));
            }
        }
        while (!isset($result));

        $_SESSION['backup']['parts'][] = array(
            'PartNumber' => $_SESSION['backup']['partNumber'],
            'ETag'       => $result['ETag'],
        );
        $_SESSION['backup']['partNumber']++;

        if($_SESSION['backup']['partNumber'] % $reloadFrequency == 1)
        {
            header('Location: '.CURRENT_URL);
            die;
        }
    }
    fclose($file);

    $result = $s3Client->completeMultipartUpload(array(
        'Bucket'   => $bucket,
        'Key'      => $key,
        'UploadId' => $_SESSION['backup']['uploadId'],
        'MultipartUpload' => Array(
            'Parts'    => $_SESSION['backup']['parts'],
        ),
    ));
    $url = $result['Location'];
}


public function cleanInterruptedMultipartUploads($s3Client)
{
    $tResults = $s3Client->listMultipartUploads(array('Bucket' => ''));
    $tResults = $tResults->toArray();

    if(isset($tResults['Uploads']))
    {
        foreach($tResults['Uploads'] AS $result)
        {
            $s3Client->abortMultipartUpload(array(
                'Bucket'   => '', 
                'Key'      => $result['Key'], 
                'UploadId' => $result['UploadId']));
        }
    }

    if(isset($_SESSION['backup']))
    {
        unset($_SESSION['backup']);
    }
}

如果有人有疑问请不要犹豫与我联系:)