本地正常的SFTP转S3 Lambda代码线上无文件传输问题排查
问题:Node.js Lambda执行SFTP到S3文件传输时,异步代码未完成就提前终止
我有一个用于将文件从SFTP传输到S3存储桶的小型Node.js Lambda函数。本地触发代码时,可完整运行并重复完成文件传输;但上传至Lambda并通过测试事件触发后,代码执行通常无文件传输,也未收到任何错误信息。由于看不到s3.upload()内的console.log日志,推测可能是Lambda提前终止的问题。请问如何确保所有async/await代码在Lambda终止前执行完毕?(该代码曾成功运行过一次,但后续无法复现此结果!)
附原代码
const Client = require("ssh2-sftp-client"); const sftpConfig = require("./config/sftp"); const AWS = require("aws-sdk"); const s3 = new AWS.S3(); const sftpDir = "/mysftp/source/directory"; const bucketName = 'myTargetS3Bucket'; function timestampLessThanADayOld(timestampMilliseconds) { const currentTimestampMilliseconds = Date.now(); const timeDifferenceMilliseconds = currentTimestampMilliseconds - timestampMilliseconds; return timeDifferenceMilliseconds < 1 * 24 * 60 * 60 * 1000; } const transferFile = async (file, sftp, bucketName) => { try { const sftpFilePath = `${sftpDir}/${file.name}`; console.log(`Attempting to transfer ${file.name}`); const fileData = await sftp.get(sftpFilePath); const s3Key = `Uploads/${file.name}`; const s3Params = { Bucket: bucketName, Key: s3Key, Body: fileData, }; await s3.upload(s3Params, (err, data) => { console.log("data: ",data); if (err) { console.error("Error: ", err); } else { console.log(`Upload succeeded: ${data.Location}`); } }); } catch (error) { console.error("Error reading file: ", error); } }; const copyNewFiles = async () => { console.log("Transfer initiated"); const sftp = new Client(); try { await sftp.connect(sftpConfig); const files = await sftp.list(sftpDir); for (let i = 0; i < files.length; i++) { if (timestampLessThanADayOld(files[i].modifyTime)) { await transferFile(files[i], sftp, bucketName); } } } catch (error) { console.error("Error connecting to SFTP:", error); } finally { sftp.end(); } console.log("Transfer complete"); }; module.exports.handler = async (event, context) => { await copyNewFiles(); } // if executing locally, I add a `copyNewFiles();` here to trigger it, // and run it using `node sftpTransfer.js`
CloudWatch日志输出
2024-01-02T19:10:49.464+00:00 INIT_START Runtime Version: nodejs:14.v42 Runtime Version ARN: arn:aws:lambda:eu-north-1::runtime:xxxxxx 2024-01-02T19:10:50.234+00:00 START RequestId: 9eb64b6a-xxxxxx Version: $LATEST 2024-01-02T19:10:50.238+00:00 2024-01-02T19:10:50.238Z 9eb64b6a-xxxxxx INFO Transfer initiated 2024-01-02T19:10:53.319+00:00 2024-01-02T19:10:53.319Z 9eb64b6a-xxxxxx INFO Attempting to transfer MyFile.txt 2024-01-02T19:10:54.175+00:00 2024-01-02T19:10:54.175Z 9eb64b6a-xxxxxx INFO Transfer complete 2024-01-02T19:10:54.180+00:00 END RequestId: 9eb64b6a-xxxxxx 2024-01-02T19:10:54.180+00:00 REPORT RequestId: 9eb64b6a-xxxxxx Duration: 3944.60 ms Billed Duration: 3945 ms Memory Size: 1024 MB Max Memory Used: 89 MB Init Duration: 769.30 ms
解决方案
核心问题是**s3.upload()混用了回调函数和await,导致await并未真正等待上传操作完成**。AWS SDK的方法如果传入回调参数,就不会返回Promise,此时await相当于没有等待异步操作结束,Lambda会认为所有异步任务已完成,提前终止进程,导致上传操作被中断。
具体修改步骤:
- 将
s3.upload()改为Promise形式,使用.promise()方法获取Promise对象,这样await才能正确等待上传完成。 - 把原来回调里的日志和错误处理逻辑,移到外层的
try/catch中统一处理。
修改后的关键代码(transferFile函数):
const transferFile = async (file, sftp, bucketName) => { try { const sftpFilePath = `${sftpDir}/${file.name}`; console.log(`Attempting to transfer ${file.name}`); const fileData = await sftp.get(sftpFilePath); const s3Key = `Uploads/${file.name}`; const s3Params = { Bucket: bucketName, Key: s3Key, Body: fileData, }; // 改用promise形式,让await等待上传完成 const data = await s3.upload(s3Params).promise(); console.log(`Upload succeeded: ${data.Location}`); } catch (error) { // 统一捕获SFTP读取和S3上传的错误 console.error(`Error transferring ${file.name}: `, error); } };
其他注意事项:
- 确保Lambda的超时时间设置足够长,避免大文件上传时被强制终止(当前日志显示耗时约4秒,可根据文件大小适当调整)。
- 检查Lambda执行角色是否具备S3上传的权限,避免因权限问题导致上传失败但无日志(不过原代码曾成功运行,权限大概率没问题)。
修改后重新上传Lambda测试,CloudWatch日志应该会出现Upload succeeded: xxx的日志,文件也能正常传输到S3。
内容的提问来源于stack exchange,提问作者Dycey
相关产品推荐
相关产品推荐

