S3触发Lambda删除对象失败,手动测试正常求助
我有一个由S3 Put事件触发的Lambda函数,文件上传至存储桶后会检查对象键的扩展名,未通过则执行删除。发现S3自动触发时,文件有时仍留存且带删除标记;但手动用测试按钮运行(含上传事件数据)每次都能成功删除。已添加15秒延迟仍无效,附上相关代码及日志,求解决方案。
相关代码
主Lambda代码片段
# 提取存储桶名称、对象键和版本ID bucket_name = record['s3']['bucket']['name'] object_key = record['s3']['object']['key'] version_id = record['s3']['object']['versionId'] # 删除所有版本前短暂暂停 time.sleep(15) print(f"文件 {object_key} 扩展名无效,将从存储桶 {bucket_name} 删除。") # 先删除对象——可能会添加删除标记 response = s3_client.delete_object(Bucket=bucket_name, Key=object_key, VersionId=version_id) print(f"已从 {bucket_name} 删除版本 {version_id} 的 {object_key}。") print("实际响应如下:") print(response) # 删除所有版本前短暂暂停 time.sleep(3) # 调用函数删除所有版本(因为存储桶开启了版本控制) delete_all_versions_new(bucket_name, object_key)
delete_all_versions_new函数代码
def delete_all_versions_new(bucket_name, object_key): """从开启版本控制的S3存储桶中删除对象的所有版本。""" s3_client = boto3.client('s3') try: # 列出对象的所有版本 versions = s3_client.list_object_versions(Bucket=bucket_name, Prefix=object_key) if 'Versions' in versions: for version in versions['Versions']: s3_client.delete_object(Bucket=bucket_name, Key=object_key, VersionId=version['VersionId']) print(f"已从 {bucket_name} 删除 {object_key} 的版本 {version['VersionId']}。") if 'DeleteMarkers' in versions: for marker in versions['DeleteMarkers']: s3_client.delete_object(Bucket=bucket_name, Key=object_key, VersionId=marker['VersionId']) print(f"已从 {bucket_name} 删除 {object_key} 的删除标记 {marker['VersionId']}。") except Exception as e: print(f"从 {bucket_name} 删除 {object_key} 的所有版本失败:{e}")
日志输出
文件 RSTMT/ASP_SUBMITTER/Q2+2023/DUA-Blank.pdf 扩展名无效,将从存储桶删除(暂停后)。
已从存储桶删除版本 wp3CRB_gJwsCPGpDJQ9Bjvyus5N3d6V2 的 KEY/ASP_SUBMITTER/Q2+2023/DUA-Blank.pdf。
实际响应如下:
{'ResponseMetadata': {'RequestId': 'S0Y16H5YZCNCHJW6', 'HostId': 'xxxx', 'HTTPStatusCode': 204, 'HTTPHeaders': {'x-amz-id-2': '', 'x-amz-request-id': '', 'date': 'Fri, 09 May 2025 02:54:26 GMT', 'x-amz-version-id': '', 'server': 'AmazonS3'}, 'RetryAttempts': 0}, 'VersionId': 'wp3CRB_gJwsCPGpDJQ9Bjvyus5N3d6V2'}
END RequestId: ...
解决方案
1. 替换time.sleep(),改用S3一致性等待
S3触发Lambda时存在最终一致性问题——Lambda启动后,对象的版本元数据可能还未完全同步。time.sleep()不可靠,改用S3 waiter等待对象版本可用:
# 替换time.sleep(15),等待目标版本存在 s3_client.get_waiter('object_exists').wait( Bucket=bucket_name, Key=object_key, VersionId=version_id )
2. 修复delete_all_versions_new的前缀匹配问题
当前用Prefix=object_key会误删所有前缀匹配的对象(比如a/b会匹配a/b/c),需精确匹配对象键,同时处理分页结果:
def delete_all_versions_new(bucket_name, object_key): s3_client = boto3.client('s3') try: paginator = s3_client.get_paginator('list_object_versions') page_iterator = paginator.paginate(Bucket=bucket_name, Prefix=object_key) for page in page_iterator: # 精确匹配对象键,避免误删 if 'Versions' in page: for version in page['Versions']: if version['Key'] == object_key: s3_client.delete_object(Bucket=bucket_name, Key=object_key, VersionId=version['VersionId']) print(f"已从 {bucket_name} 删除 {object_key} 的版本 {version['VersionId']}。") if 'DeleteMarkers' in page: for marker in page['DeleteMarkers']: if marker['Key'] == object_key: s3_client.delete_object(Bucket=bucket_name, Key=object_key, VersionId=marker['VersionId']) print(f"已从 {bucket_name} 删除 {object_key} 的删除标记 {marker['VersionId']}。") except Exception as e: print(f"从 {bucket_name} 删除 {object_key} 的所有版本失败:{e}")
3. 简化删除逻辑
无需先删除对象再清理版本,直接删除触发事件的版本,再调用修复后的全版本删除函数:
print(f"文件 {object_key} 扩展名无效,将从存储桶 {bucket_name} 删除。") # 直接删除触发事件的目标版本 s3_client.delete_object(Bucket=bucket_name, Key=object_key, VersionId=version_id) print(f"已从 {bucket_name} 删除版本 {version_id} 的 {object_key}。") # 清理剩余版本和删除标记 delete_all_versions_new(bucket_name, object_key)
4. 检查Lambda权限
确保Lambda角色拥有s3:DeleteObject、s3:ListObjectVersions权限,且策略覆盖存储桶的所有版本操作。
内容的提问来源于stack exchange,提问作者Brian Edwards

