将数据追加到 S3对象

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

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

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

有没有一种方法可以将我的 S3配置为这样工作?也许有一些 IAM 政策,我可以附加到它,这样它就会像我想要的工作?

115595 次浏览

正如公认的答案所说,你不能。我所知道的最好的解决办法是:

自动报警系统运动消防软管

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

他们的 代码示例看起来很复杂,但你的可以很简单。您继续执行 PUT (或 BATCH PUT)操作到您的应用程序中的 Kinesis 消防水管交付流(使用 AWS SDK) ,并且您配置 Kinesis 消防水管交付流以将您的流数据发送到您选择的 AWS S3桶(在 AWS Kinesis 消防水管控制台中)。

enter image description here

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

我也有过类似的问题,这就是我所要求的

如何使用 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');
});
}

希望这有帮助! !

S3上的对象不能追加。在这种情况下有两种解决方案:

  1. 将所有 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
}
});
}
});
}
  1. 第二个选择是使用 Kinesis 消防水管。这很简单。您需要创建消防水管交付流,并将目的地链接到 S3 bucket。就是这样!
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对象不能追加。
但是,另一种解决方案是写入 CloudWatch 日志,然后写入 将日志导出到 S3。这还可以防止任何访问服务器的攻击者从 S3 bucket 中删除,因为 Lambda 不需要任何 S3权限。

如果有人想向一个具有类似于 s3的服务的对象追加数据,那么阿里巴巴云 OSS (对象存储服务) 本质上支持这一点

OSS 提供了追加上传(通过 AppendObjectAPI) ,允许您直接将内容追加到对象的末尾。使用此方法上载的对象是可附加的对象,而使用其他方法上载的对象是普通对象。附加的数据是即时可读的。

S3 bucket 不允许添加现有对象,可以使用的方法是首先使用 get 方法从 S3 bucket 获取数据,然后在本地添加要添加的新数据,然后将其推回 S3 bucket。

因为,不可能附加到现有的 S3对象。您需要用一个新对象替换它,并将数据附加到它。这意味着每次添加新条目时,您都需要上载整个对象(日志文件)。这样不会很有效率。

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

你可以:

  1. 设置多部分上传
  2. 调用 UploadPartCopy,将现有的 s3对象指定为源
  3. 使用要追加的数据调用 UploadPart
  4. 关闭多部分上传。

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

我们所面临的问题是创建一个几 GB 大的 s3文件,而从来没有将其完整地放入 RAM 中。下面的方法通过将几个文件相互追加到另一个文件的末尾来组合它们,因此根据您的需要,这可能是一个可行的解决方案。

我们想出的解决办法是:

  1. 将文件分块上传到 AWS S3文件夹中
  2. 运行以下命令,使用 AWS Athena 定义基于该 S3文件夹的表
CREATE EXTERNAL TABLE IF NOT EXISTS `TrainingDB`.`TrainingTable` (`Data` string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES ('collection.delim' = '\n')
STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat' OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://your-bucket-name/TrainingTesting/';


  1. 运行以下命令生成该表中所有结果的组合
UNLOAD (SELECT * FROM "TrainingDB"."TrainingTable")
TO 's3://your-bucket/TrainingResults/results5'
WITH ( format = 'TEXTFILE', compression='none' )

这将相互追加所有文件的末端,并提供一个文件与所有块您试图追加。如果您只是试图组合几个小文件,那么这种做法有些过火,在这种情况下,只需将原始文件拉下来并写到最后可能会更好(正如其他答案所建议的那样)

我遇到过类似的问题,在 S3中,我不得不在长时间运行的过程(几个小时)中将错误写入日志文件。因此,我没有本地文件来创建一次性流,但是我必须在运行时将错误追加到一个文件。

所以你能做的就是保持与特定文件的开放连接,并在需要的时候写入文件:

const { S3 } = require('aws-sdk')
const { PassThrough } = require('stream')


// append to open connection
const append = (stream, data ) => new Promise(resolve => {
stream.write(`${data}\n`, resolve)
})


const openConnectionWithS3 = async () => {
const s3 = new S3({
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
endpoint: process.env.AWS_S3_ENDPOINT,
region: process.env.AWS_DEFAULT_REGION,
})
const fileName = 'test.log'
const bucketName = 'my-bucket'
// create pass through stream. This stream we use to write data to
// but this stream we also use to pass the same data to aws
const pass = new PassThrough()


// dont resolve the promise, but keep it open and await for the result when the long running process is done
const promise = s3
.upload({
Bucket: bucketName,
Key: fileName,
// pass the stream as body, aws will handle the stream from now
Body: pass,
})
.promise()


// write data to our open connection.
// we can even write it on different places
for (let i = 0; i < 100000; i++) {
await append(pass, `foo${i}`)
}


// here we resolve the promise and close the connection
await Promise.all([
// push null to the stream, the stream now knows after the
// 1000 foo's it should stop writing
pass.push(null),
promise,
])
}


openConnectionWithS3()

它将向 S3中的文件追加项,并在完成后解析。