将数据附加到 S3 对象

Posted

技术标签:

【中文标题】将数据附加到 S3 对象【英文标题】:Append data to an S3 object 【发布时间】:2017-06-06 15:18:33 【问题描述】:

假设我有一台机器,我希望它能够写入存储在 S3 存储桶上的某个日志文件。

因此,机器需要具有对该存储桶的写入能力,但是,我不希望它能够覆盖或删除该存储桶中的任何文件(包括我希望它写入的文件)。

所以基本上,我希望我的机器能够只将数据附加到该日志文件,而不覆盖它或下载它。

有没有办法让我的 S3 像这样工作?也许我可以附加一些 IAM 政策,让它按我想要的方式工作?

【问题讨论】:

您不能在 S3 中修改对象。你能追加一个新的日志文件吗?那将是一个更好的模型,并且可以同时支持多个客户端。 @jarmod 是的,我想过,但问题是,如果攻击者成功访问我的服务器,他将能够在发送之前删除存储在其上的本地文件到 S3 存储桶(假设发生在一天结束时)。 您可能还想查看 CloudWatch 日志。让它管理收集和存储日志的复杂性,提供搜索工具、保留策略,并允许您根据可以为日志自定义的指标生成警报。 您还可以看看 Google BigQuery。你可以用它来解决你的问题。 【参考方案1】:

很遗憾,你不能。

S3 doesn't have an "append" operation.* 对象上传后,无法就地修改;您唯一的选择是上传一个新对象来替换它,这不符合您的要求。

*:是的,我知道这篇文章已经有几年的历史了。不过,它仍然是准确的。

【讨论】:

请问,通过使用分段上传我们可以做到这一点吗? Multipart Upload 将允许您在不下载原始对象的情况下将数据导入 S3,但不允许您直接覆盖原始对象。参见例如docs.aws.amazon.com/AmazonS3/latest/API/… 然后您可以删除旧对象/重命名新对象。然而,这不是问题所要问的。 我认为使用分段上传可能确实有效。您的所有部分都是同一文件的连续段。如果部分成功上传,您最终可以提交上传以便能够读取文件。因此,只要您不需要读取文件的内容,就可以使用相同的分段上传进行追加。 @cerebrotecnologico 我仍然认为它不符合 OP 的要求。我不知道限制 S3 用户执行附加到对象的分段上传——如果他们可以执行分段上传,他们可以上传任何他们想要的内容。 可以提供“附加接口”,如s3fs has done,但只能通过@duskwuff-inactive 提到的“不上传复制+部分上传+重写原始”的方式跨度> 【参考方案2】:

正如接受的答案所述,您不能。我知道的最佳解决方案是使用:

AWS Kinesis Firehose

https://aws.amazon.com/kinesis/firehose/

他们的code sample 看起来很复杂,但你的可以很简单。您继续对应用程序中的 Kinesis Firehose 传输流执行 PUT(或 BATCH PUT)操作(使用 AWS 开发工具包),并配置 Kinesis Firehose 传输流以将流式数据发送到您选择的 AWS S3 存储桶(在AWS Kinesis Firehose 控制台)。

它仍然不如 Linux 命令行中的>> 方便,因为一旦你在 S3 上创建了一个文件,你就必须再次处理下载、附加和上传新文件,但你只需要这样做每批行一次,而不是每行数据,因此您无需担心因追加操作量而产生的巨额费用。也许可以做到,但我无法从控制台看到如何做到这一点。

【讨论】:

请注意,这样做有一个最大时间(自文件创建后 900 秒)或最大大小(128mb 文件大小) - 这意味着,Kinesis firehose 将附加到同一个 S3 文件,直到它到达这些限制中的任何一个:docs.aws.amazon.com/firehose/latest/dev/create-configure.html 您可以使用单个 S3 文件作为 Firehose 上的输出吗?在 S3 存储桶中合并多个文件听起来有点混乱。 很遗憾没有。我也希望有更好的解决方案。 是的,很不幸。如果我手动下载并将记录附加到单个 S3 对象,我最关心的是竞争条件。我一直在考虑将记录添加到 SQS,然后使用 SNS + Lambda 的一些逻辑来轮询 SQS,然后将新条目写入 S3 对象。【参考方案3】:

我遇到了类似的问题,这就是我问的问题

how to Append data in file using AWS Lambda

这是我想出解决上述问题的方法:

使用 getObject 从现有文件中检索

   s3.getObject(getParams, function(err, data) 
   if (err) console.log(err, err.stack); // an error occurred
   else
       console.log(data);           // successful response
       var s3Projects = JSON.parse(data.Body);
       console.log('s3 data==>', s3Projects);
       if(s3Projects.length > 0) 
           projects = s3Projects;
          
   
   projects.push(event);
   writeToS3(); // Calling function to append the data
);

编写函数以追加到文件中

   function writeToS3() 
    var putParams = 
      Body: JSON.stringify(projects),
      Bucket: bucketPath, 
      Key: "projects.json",
      ACL: "public-read"
     ;

    s3.putObject(putParams, function(err, data) 
       if (err) console.log(err, err.stack); // an error occurred
       else     console.log(data);           // successful response
        callback(null, 'Hello from Lambda');
     );

希望有帮助!!

【讨论】:

您的writeToS3 函数将覆盖文件,而不是附加到它。 @duskwuff-inactive- 同意,如果两种方法尝试处理同一个对象,它也会受到竞争条件的影响,但这与具有不可变字符串或类型的语言并没有真正的不同——你通过返回/覆盖新对象来模拟追加。 这很有用,因为如果您附加数据的应用程序位于 AWS 网络之外,它的优点是不会消耗额外的带宽。【参考方案4】:

S3 上的对象不可追加。在这种情况下,您有 2 个解决方案:

    将所有 S3 数据复制到新对象,附加新内容并写回 S3。
function writeToS3(input) 
    var content;
    var getParams = 
        Bucket: 'myBucket', 
        Key: "myKey"
    ;

    s3.getObject(getParams, function(err, data) 
        if (err) console.log(err, err.stack);
        else 
            content = new Buffer(data.Body).toString("utf8");
            content = content + '\n' + new Date() + '\t' + input;
            var putParams = 
                Body: content,
                Bucket: 'myBucket', 
                Key: "myKey",
                ACL: "public-read"
             ;

            s3.putObject(putParams, function(err, data) 
                if (err) console.log(err, err.stack); // an error occurred
                else     
                    console.log(data);           // successful response
                
             );
        
    );  

    第二个选项是使用 Kinesis Firehose。这是相当简单的。您需要创建 Firehose 传输流并将目标链接到 S3 存储桶。就是这样!
function writeToS3(input) 
    var content = "\n" + new Date() + "\t" + input;
    var params = 
      DeliveryStreamName: 'myDeliveryStream', /* required */
      Record:  /* required */
        Data: new Buffer(content) || 'STRING_VALUE' /* Strings will be Base-64 encoded on your behalf */ /* required */
      
    ;

    firehose.putRecord(params, function(err, data) 
      if (err) console.log(err, err.stack); // an error occurred
      else     console.log(data);           // successful response
    ); 

【讨论】:

可以使用单个 S3 文件作为输出吗?【参考方案5】:

正如其他人之前所说,S3 对象不可追加。 但是,另一种解决方案是先写入 CloudWatch 日志,然后写入 export the logs you want to S3。这也可以防止任何访问您服务器的攻击者从您的 S3 存储桶中删除,因为 Lambda 不需要任何 S3 权限。

【讨论】:

【参考方案6】:

如果有人想通过类似 S3 的服务将数据附加到对象,阿里云 OSS(对象存储服务)supports this natively。

OSS 提供追加上传(通过 AppendObject API),允许您直接将内容追加到对象的末尾。使用该方法上传的对象是可附加对象,而使用其他方法上传的对象是普通对象。附加的数据可以立即读取。

【讨论】:

【参考方案7】:

S3 存储桶不允许您附加现有对象,可用于执行此操作的方法是,首先使用 get 方法从 S3 存储桶中获取数据,然后在本地添加您要附加的新数据,然后然后将其推回 S3 存储桶。

因为,无法附加到现有的 S3 对象。您需要将其替换为附加数据的新对象。这意味着每次向其附加新条目时,您都需要上传整个对象(日志文件)。这不会很有效。

您可以将日志条目发送到 SQS 队列,并且当队列大小达到设定数量时,您可以将日志消息批处理在一起并作为对象添加到 S3 存储桶中。这仍然不能满足您附加到单个对象的要求

【讨论】:

【参考方案8】:

你可以:

    设置分段上传 调用 UploadPartCopy,将现有 s3 对象指定为源 使用您要附加的数据调用 UploadPart 关闭分段上传。

有许多限制,例如您现有的对象必须大于 5MB(但是,如果它更小,则在大多数情况下将其复制到客户端应该足够快) 它不如直接追加,但至少您不需要将数据从 aws 来回复制到本地机器。

【讨论】:

以上是关于将数据附加到 S3 对象的主要内容,如果未能解决你的问题,请参考以下文章

使用 MIMEApplication 将 s3 文件附加到 smtplib lib 电子邮件

S3 www 到非 www 重定向将 /index.html 附加到 URL

无法快速将对象附加到对象数组

AWS Lambda 返回权限被拒绝尝试从 S3 存储桶获取对象

D3.js 将对象绑定到数据并为每个键附加

VB.NET 从 DataTable 对象将表附加到数据库