如何在AWS Lambda函数中查找S3文件而不先下载它?

时间:2016-05-23 23:38:02

标签: python-2.7 amazon-web-services amazon-s3 aws-lambda

我是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)

1 个答案:

答案 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将在某些时候重用相同的进程,具体取决于调用的工作量和频率。

是的,您可以在不下载整个对象的情况下进行字节范围读取,但这不适用于此,因为我们需要扫描而不是搜索。