我是AWS Lambda的新手,我正在尝试创建一个将由S3 put事件调用的lambda函数,对传入的数据应用一些业务逻辑,然后加载到目标。
例如,在源s3存储桶中创建的新文件(contactemail.json)包含2个组件:电子邮件和域。在同一个s3存储桶中有另一个持久文件(lkp.json)包含所有免费电子邮件域的列表(例如gmail.com)。 lambda函数读取contactemail.json文件,根据域查找lkp.json文件。如果contactemail.json中的域存在于lkp.json中,请将整个电子邮件地址放入contactemail.json文件中的新组件(newdomain),然后将输出上传到目标s3存储桶。
以下是我的代码。但是,正如您所看到的,我在执行查找之前使用s3_client.download_file下载lkp.json文件。
我担心的是,如果查找文件太大,可能下载过程将花费太长时间并导致lambda函数超时。
有没有更好/更智能的方法进行查找而无需从s3到lambda的下载查找文件?
from __future__ import print_function
import boto3
import os
import sys
import uuid
import json
s3_client = boto3.client('s3')
def handler(event, context):
#get source details from event
for record in event['Records']:
sourcebucket = record['s3']['bucket']['name']
sourcekey = record['s3']['object']['key']
sourcefilename = sourcekey[sourcekey.rfind('/')+1:]
lookupkey = 'json/contact/lkp/lkp.json'
lookupfilename = 'lkp.json'
#set target based on source value
targetbucket = sourcebucket + 'resized'
targetkey = sourcekey
targetfilename = sourcefilename
#set download and upload path in lambda
download_path = '/tmp/{}'.format(uuid.uuid4())+sourcefilename
download_path_lkp = '/tmp/{}'.format(uuid.uuid4())+lookupfilename
upload_path = '/tmp/{}'.format(uuid.uuid4())+targetfilename
#download source and lookup
s3_client.download_file(sourcebucket, sourcekey, download_path)
s3_client.download_file(sourcebucket, lookupkey, download_path_lkp)
#if not os.path.exists(upload_path):
# open(upload_path, 'w').close()
targetfile = open(upload_path, 'w')
sourcefile = json.loads(open(download_path).read())
lookupfile = json.loads(open(download_path_lkp).read())
lookuplist = []
for row in lookupfile:
lookuplist.append(row["domain"])
targetfile.write('[')
firstrow = True
for row in sourcefile:
email = row["email"]
emaildomain = email[email.rfind('@')+1:]
if (emaildomain in lookuplist):
row["newdomain"]=email
else:
row["newdomain"]=emaildomain
if (firstrow==False):
targetfile.write(',\n')
else:
firstrow=False
json.dump(row, targetfile)
targetfile.write(']')
targetfile.close()
#upload to target
s3_client.upload_file(upload_path, targetbucket, targetkey)
答案 0 :(得分:2)
简单地说,S3不是用于此目的的正确服务。
如果不下载它,就无法查看存储在S3中的对象.¹
对象是S3中的原子实体 - S3没有理解它比对象小,例如对象内部的“记录”。
也无法在S3中向对象追加数据。您必须下载,修改并再次上传,如果多个进程并行尝试此操作,则至少有一个进程会静默丢失数据,因为无法锁定S3对象FOR UPDATE
(一个小的SQL术语,那里)。第二个进程读取原始对象,对其进行修改,然后在第二个进程读取对象后立即覆盖第一个进程保存的更改。
作为一个“在盒子外面思考”的人,我将首先断言 是一个有效的S3用例,作为一个简单的,敷衍的NoSQL数据库 - 毕竟它是,具有无限存储和按键快速查找的键/值存储...但适合此角色的应用程序是有限的。这不是它的设计目标。
在这种情况下,您可以通过不同的架构更好地服务...但是,如果您将lambda函数连接到VPC并为S3创建VPC端点或使用NAT实例(不是NAT网关,有带宽费用),你可以花0.04美元下载100,000次,所以根据你的规模,重复下载文件可能不是最糟糕的事情......但是你会浪费很多可计费的lambda毫秒反复解析同样的文件和扫描它,正如您所知,这只会随着您的应用程序的增长而变慢。看起来RDS,DynamoDB或SimpleDB可能更适合这里。
您还可以在“处理程序”范围之外的对象中将内容或至少特定查找结果缓存到内存中......对吗? (不是蟒蛇人,但看起来似乎有道理)。 Lambda将在某些时候重用相同的进程,具体取决于调用的工作量和频率。
是的,您可以在不下载整个对象的情况下进行字节范围读取,但这不适用于此,因为我们需要扫描而不是搜索。