R Shiny应用调用AWS Lambda实现异步计算的方案咨询
架构可行性判断
你这套设计整体是可行的,刚好匹配Serverless方案解决长计算占Shiny服务CPU的痛点,1分钟的任务时长完全在Lambda默认15分钟的执行上限内,按实际执行毫秒计费的模式,比你给Shiny实例升配、扩容扛计算量的成本低一大截,给Shiny配S3输入桶仅写权限的思路也符合最小权限的安全要求。
但现有设计有几个必须补的漏洞,不然上线容易出问题:
- 输入输出文件名必须绑定用户会话级的唯一ID,绝对不能用固定文件名或者简单用户名命名,不然高并发下很容易出现A用户拿到B用户计算结果的串数据问题
- 别只靠Lambda执行完删原始输入文件,一定要给S3输入桶配生命周期规则,比如存满24小时的文件自动删除,避免Lambda执行崩溃、超时的时候残留垃圾文件占存储
- 现有流程缺异常兜底:Lambda跑失败了不能让用户傻等,得把错误信息也存到S3对应路径,方便前端给用户提示
- 绝对不能给用户开S3桶的遍历、全局读权限,不然所有用户的计算结果都能被随便扒走
替代方案参考
没有绝对最优的方案,看你的实际场景选就行:
- 如果你的计算逻辑依赖的R包特别重,带一堆系统级依赖,打包完超过Lambda 10G的镜像上限,就别硬磕Lambda,换AWS Fargate跑Serverless容器任务就行,触发逻辑和S3触发Lambda完全一致,没有包体积限制,也是按实际运行的资源量计费,成本只比Lambda高一点,还是远低于扩容Shiny服务
- 如果单份输入数据小于6MB(Lambda异步调用的payload上限),完全可以省掉S3存输入的环节,用户点按钮后Shiny直接异步调用Lambda接口传参,少一层S3中转的链路,复杂度低很多,出问题也好排查
- 要是你日均计算请求量连10次都不到,其实直接用
promises加个单独的小规格计算节点专门跑异步任务就行,没必要搭整套S3+Lambda的链路,维护成本更低 - 别考虑自建R计算集群这种方案,闲置资源成本太高,对你这种单次1分钟的计算任务来说性价比极低
落地验证参考方向
不用找杂七杂八的教程,核心看这几块官方公开的文档内容就能跑通:
- Lambda自定义R运行时的部署规则:现在已经有现成的R运行时支持,重点看怎么把你本地的计算逻辑、依赖包打包成Lambda可识别的部署包,怎么给Lambda配置对应S3桶的读写权限角色
- S3事件通知配置规则:重点看怎么配置文件后缀过滤,只让指定后缀(比如
_input.Rds)的新文件触发Lambda,避免结果文件回写的时候误触发重复计算 - S3预签名URL生成逻辑:后续给用户返回结果的时候用,不用给用户开S3永久读权限,生成15分钟有效的专属临时链接就行,避免数据泄露
- Shiny非阻塞会话的相关接口:重点看怎么写不阻塞当前用户操作、不卡其他用户会话的后台任务逻辑
Shiny端结果检测与通知实现
别搞太复杂的消息推送组件,用Shiny自带的接口就能实现,而且稳定性够高,核心逻辑如下:
- 用户点击计算按钮时,先生成全局唯一的任务ID,直接拼当前用户的会话token、时间戳、4位随机数就行,比如用
task_id <- paste0(session$token, "_", as.integer(Sys.time()), "_", sample(1000:9999, 1)),把输入数据用这个ID命名存到S3输入路径,同时在当前会话内存里把任务状态标记为计算中 - 用
invalidateLater()做非阻塞轮询,间隔设3-5秒就行,别设1秒那种高频轮询,纯浪费S3接口调用费,用户也感知不到延迟。每次轮询不要列整个S3桶的文件,直接调用S3的对象元数据查询接口,查固定路径下的两个文件:- 如果查到
[task_id]_result.Rds存在,就把任务状态改成完成,调用showNotification()弹成功通知,同时生成对应结果文件的预签名URL,给用户展示下载按钮或者直接加载结果渲染到页面,终止轮询 - 如果查到
[task_id]_error.txt存在,说明Lambda执行失败,直接弹错误提示,把文件里存的错误原因简单展示给用户,终止轮询 - 如果两个文件都没查到,就等下一个轮询周期再查,同时可以给用户展示个动态等待提示,比如显示“已等待X秒,计算通常1分钟左右完成”,轮询超过2分钟就弹超时提示,让用户稍后重试
- 如果查到
- 轮询逻辑一定要绑定当前用户的session,用户关闭页面的时候自动终止轮询,别白占接口资源
注意:给Shiny服务配S3权限的时候,只给对应结果路径下的对象查询、读权限就行,绝对不要给桶的列表权限,避免越权访问。
内容的提问来源于stack exchange,提问作者NellieG
相关产品推荐
相关产品推荐

