InfoQ 编辑部出品——2021年度技术盘点与展望 了解详情
写点什么

借助 Amazon S3 实现异步操作状态轮询的 Serverless 解决方法

作者:Cristian Gherghinescu

  • 2021 年 12 月 08 日
  • 本文字数:4530 字

    阅读完需:约 15 分钟

借助Amazon S3实现异步操作状态轮询的Serverless解决方法

异步 API 会有很多的优势,比如解耦、可扩展和弹性等。但是,正如俗话所言,“世上没有免费的午餐”,我们需要考虑在客户端和服务器端所增加的复杂性。


要获取异步操作的状态往往需要客户端定期轮询结果。这种操作会导致客户端和服务器端的资源浪费。


本文提供了一种将轮询部分重定向到 Amazon Simple Storage Service(S3)的方案。


S3 是一个由公有云提供商亚马逊云科技(Amazon Web Services)管理的高可用、可扩展和安全的对象存储服务。


我们将会展现一个使用 AWS Lambda 函数的 serverless 实现,但是如果你想使用 S3 的话,并不是强制要使用 AWS Lambda 函数。


举例来说,你还可以使用 Docker 容器。

Serverless 异步 API

在亚马逊云科技平台上,异步 API 的典型的 serverless 实现会涉及到 Amazon API Gateway、一些 lambda 函数、一个 SQS 队列以及我们本例中所用到的 NoSQL 键-值数据库:DynamoDB。在下图中,我们可以看到整体的架构:



为了简单起见,我们的 API 只有一个资源,通过 POST 到“/order”可以创建一个新的订单,通过 GET 到“/order/{id}”可以检索订单。我们假设创建订单会消耗一定的时间,所以请求是异步的。客户端调用该端点并得到一个订单的 id。借助这个 id,它们必须要轮询 GET 端点来检查该订单何时创建完成。当然,如果客户端有一个可以被调用的回调端点或者它们能够在订单创建完成之后,接收到通知的话,那就没有必要使用轮询了。


尽管每隔一秒钟或差不多的时间去调用一个端点是很容易的,但这是一个无效的过程,会浪费客户端和服务器端的资源。除此之外,有些客户端无法实现 webhook 端点,无法消费通知,或者没有足够的时间来实现这些机制。


消除服务器端资源浪费的一种方式就是将轮询委托给亚马逊云科技提供的托管服务。我们可以使用 Amazon Simple Storage Service(S3)来实现这一点。

使用 Amazon S3 实现轮询

Amazon S3 是亚马逊云科技云供应商最早提供的服务之一。它是一个对象存储服务,提供了高可扩展性、高可用性和高性能。它的结构在某种程度上模拟了一个文件系统,其中会使用桶来盛放对象,所谓的对象也就是文件以及描述该文件的元数据。


我们可以使用 S3 将异步操作的状态存储为一个 JSON 文件,API 的客户端会调用该服务,而不是轮询我们的 API。通过这种方式,客户端检查状态更新的所有流量会被重定向到 S3 API 上,而不是我们自己的 API 上。


为了避免向我们的 API 客户端传播证书或其他的认证机制,我们将会使用S3的预签名URL(presigned URL)特性。默认情况下,所有的桶和文件都是私有的。但是,在限定的时间内,我们可以使用预签名 URL 共享一些文件(不需要暴露安全凭证和权限)。


收到 POST 请求的 lambda 函数会生成包含操作状态的预签名 URL,并将其返回给客户端。这个 S3 的文件名也会作为一个属性添加到要发送至 SQS 的消息中,这样的话,负责进行处理的部分在需要更新状态的时候就可以引用它的值。



AWS SDK 提供了生成这些预签名 URL 的功能。在下面 Python 代码的样例中,我们会得到一个访问对象的 GET URL,对象的 key 是OBJECT_KEY且位于BUCKET_NAME S3 桶中,该 URL 会在十分钟内过期:


import boto3url = boto3.client('s3').generate_presigned_url(ClientMethod='get_object',Params={'Bucket': 'BUCKET_NAME', 'Key': 'OBJECT_KEY'},ExpiresIn=600)
复制代码


使用其他编程语言的样例,请参考文档


注意,这个功能也可以在 Docker 容器和自托管的应用中使用。如果你无法使用某种 AWS SDK(Java、.NET、Ruby、PHP、Node.js、Python 或 Go)的话,还可以采用AWS S3 REST APIAWS Command Line Interface。并不是必须要使用 serverless lambda 函数。


在返回预签名 URL 以便于进行轮询的 lambda 函数中,我们还可以在响应中包含一个预估的时间,即客户端在什么时候可以开始询问操作的状态。这个时间预估可以基于 SQS 队列中消息的大致数量、in-flight 状态的消息的大致数量(业已发送到客户端但尚未删除,或尚未达到消息的可见性过期时间),以及处理一个请求的平均时间。下面我们可以看到一个 Python 的例子,说明如何从 SQS 队列中获得这些数字:


import boto3response = boto3.client(‘sqs’).get_queue_attributes(     QueueUrl='QUEUE_URL',        AttributeNames=['ApproximateNumberOfMessages'|'ApproximateNumberOfMessagesNotVisible'])
复制代码


当使用 S3 来存储异步操作的状态时,较新的状态会被更频繁地查询,而旧的状态在一段时间后可能就完全不会再被读取了。因此,根据使用情况,你可以利用 S3 提供的不同存储类别。在写这篇文章的时候,AWS 提供的不同类别和成本如下所示(仅限于 Ireland 区域):


存储类别所设计的适用场景可用性(按照设计)可用区最小存储时间最小计费对象大小其他考虑因素每月每GB价格
S3 Standard经常访问的数据99.99%>= 30.023美元
S3 Standard-IA长期存在,不经常访问的数据99.99%>= 330天128 KB根据每GB的检索计费0.0125美元
S3 Intelligent-Tiering数据的访问模式是未知的、变化的或不可预测的99.99%>= 3对每个对象的监控和自动化会计费。没有检索的费用。0.023至0.00099美元(取决于所使用的层)
S3 One Zone-IA长期存活的、不经常访问、非关键性的数据99.5%130天128 KB根据每GB的检索计费。对可用区造成的丢失缺乏弹性0.01美元
S3 Glacier长期数据的存档,检索时间从几分钟到几个小时不等99.99%(还原对象之后)>= 390天40 KB根据每GB的检索计费。在访问之前,我们必须先还原归档的对象。0.004美元
S3 Glacier Deep Archive用于归档很少访问的数据,默认检索时间为12小时99.99%(还原对象之后)>= 3180天40 KB根据每GB的检索计费。在访问之前,我们必须先还原(restore)归档的对象。0.00099美元


资料来源


对象存储的管理是通过 S3 生命周期规则实现的。例如,我们可以声明一个规则,让文件在 S3 Standard 中存在十天,然后转移到 S3 Standard-IA,30 天后将其删除或者转移至 S3 Glacier Deep Archive 中。生命周期可以通过 Amazon S3 控制台、REST API、AWS SDK 和 AWS CLI 进行配置。关于这方面的更多信息,请参阅文档

安全方面的考虑因素

虽然在默认情况下,S3 中所有的文件和桶都是私有的,但是创建预签名 URL 会允许在限定的时间范围内访问这些文件。获取了预签名 URL 的所有人都能读取状态文件。因此,与 API 的通信应该只允许通过 HTTPS 来实现,状态文件中不要存储任何的敏感数据,并且这些文件的时间限制要设置地越短越好,当然,不能短于实际操作所要占用的时间。


另外一个额外的安全防护可以在 S3 侧执行,也就是只允许特定 IP 范围进行访问。这可以通过在桶上添加策略来实现,在文档页面我们可以看到相关的例子。


如果预签名 URL 的机制对你的使用场景来说不够安全的话,那么在这种情况下,你可以使用 AWS Security Token Service(AWS STS)创建临时的安全凭证,并将其提供给你的客户端,这种临时安全凭证可以控制对 S3 操作状态文件的访问。对于联合身份验证(identity federation),AWS STS 支持企业级联合身份验证(自定义身份代理或 SAML 2.0)和 Web 联合身份验证(使用 Google、Facebook、Amazon 或任意兼容 OpenID Connect 的身份识别供应商)。关于这方面的更多信息,请查阅他们的文档

收益分析

将轮询功能委托给 S3 能够让主服务只处理实际的业务逻辑请求,而不用持续地检查更新。这样的话,我们的 serverless 样例就会产生更少的函数调用,而且对 DynamoDB 的读取容量单元消耗也会更少。


尽管 AWS Lambda 函数的扩展速度非常快,并且可以处理大量的并发请求,但是你依然需要考虑并发的限制。根据区域的不同,初始的流量暴增限制是 500 到 3000,这一限制适用于账户中的所有函数。我们让轮询不去消耗并发量,这样就会为其他的函数留下更多的容量。关于 lambda 函数限制的完整列表,请查阅AWS的文档


其他浪费的资源是 DynamoDB 的读取请求单元。每个读取单元代表了一次强一致性的读取请求,或者两个最终一致的读取请求,因为每个条目最多只能有 4KB。另外,如果你的表配置成了 provisioned 模式的话,这意味着你会声明读取容量单元的数量,这样的话,有些请求可能会被限流。DynamoDB 还有一种 On-Demand 模式,在这种模式下,容量会随着流量进行调整。令人遗憾的是,轮询只会产生带来副作用的业务流量。


成本的收益会在请求达到 100 万的时候开始显现。对于几十万级别的请求来讲,差异并不大。我们下面会看到一个成本计算的样例。


我们以 10 万个请求为例,并假设每个请求平均会有 10 个轮询请求,因此共有 100 万个轮询请求。如下的计算是使用AWS Pricing Calculator针对 Ireland AWS 区域进行的计算。


API Gateway REST API 的成本计算很简单:1,000,000 个请求 x 0.0000035000 美元 = 3.50 美元


对于 lambda 函数,我们假设平均执行时间是 500 毫秒,并分配 256MB 的内存:


  • 1,000,000 请求 x 500 毫秒 x 0.001(毫秒到秒的转换系数)= 500,000.00 的计算总量(秒)

  • 0.25 GB x 500,000.00 秒 = 125,000.00 的计算总量(GB-s)

  • 125,000.00 GB-s x 0.0000166667 美元 = 2.08 美元(每月计算费用)

  • 1,000,000 请求 x 0.0000002 美元 = 0.20 美元(每月计算费用)


lambda 的总成本:2.08 美元 + 0.20 美元 = 2.28 美元


对于 DynamoDB,我们估算的平均条目大小是 10KB,我们将会使用最终一致的读取。


  • 平均条目大小为 10 KB / 4 KB = 每个条目需要 2.50 个读取请求

  • 四舍五入(2.500000000) = 每个条目需要 3 个读取请求

  • 1,000,000 个读取 x 1 个最终一致的分区 x 0.5 个最终一致的读数请求单元 x 每个条目所需的读取请求单元数为 3 = 1,500,000.00 为实现最终一致性读取所需的读取请求单元


从 Dynamo 进行读取的总成本:总的读取请求单元 1,500,000.00 x 0.000000283 美元=0.42 美元的读取请求成本


轮询请求的总成本将会是:3.50(API Gateway) + 2.28(Lambda) + 0.42(从 DynamoDB 的读取) = 6.2 美元


这个成本略微有些高估了,因为 lambda 函数的响应时间可能会少于 500 毫秒,为它们提供 128MB 的内存可能就足够了。


对于 S3,我们预估使用每月 1GB(100,000 x 10 KB)的 Standard 存储:


  • 1 GB x 0.0230000000 美元 = 0.02 美元

  • 100,000 个对 S3 存储的 PUT 请求 x 每个请求 0.000005 美元 = 0.50 美元

  • 每月 1,000,000 个 GET 请求 x 每个请求 0.0000004 美元 = 0.40 美元

  • 0.023 美元 + 0.40 美元 + 0.50 美元 = 0.92 美元(总的 S3 Standard 存储,数据请求和 S3 查找的成本)


S3 数据传输,outbound 的互联网流量,1 GB 的 tiered 价格:


  • 1 GB x 每 GB 的 0 美元 = 0.00 美元

  • 0 GB x 每 GB 的 0.09 美元 = 0.00 美元


S3 总成本:0.92 美元 + 0.00 美元 = 0.92 美元


请注意,为了尽可能让对比更接近实际情况,这些计算只包含了实际请求相关的成本。因此,所有其他的额外成本没有包含进去,比如 DynamoDB 的存储成本。


成本差异不是很大。但是,我们将它列在了这里,这样你可以大致了解如何进行计算。

缺点

将轮询转移到 S3 有这么多的好处,但它也给整个解决方案增加了额外的复杂性。我们需要涉及另一个服务,即 S3,并为每个操作创建一个预签名的 URL。如果状态文件包含任何敏感信息的话,这个解决方案可能会增加更高的风险,因为任何得到预签名 URL 的人都可以访问这些信息。如果有来自许多客户端的大量调用,并且他们会在很短的间隔内进行轮询时,本文所提到的大部分的收益将会兑现。在只有少量调用的情况下,主 API 也可以处理轮询流量,而不需要使用 S3。

总结

这篇文章展示了如何使用 AWS S3 来处理来自异步 API 的轮询流量。如果你无法实现通知策略,并且客户端需要轮询来获取操作结果的话,那么 S3 可以是一个很好的候选方案,它能够将轮询的调用从主 API 中迁移出来。我们需要为每个操作生成一个 S3 预签名的 URL,并将其返回给客户端,以便于客户端调用它,这样的话,计算资源就能处理应用程序的主业务逻辑,而不必通过 API 调用检查操作的状态。


文章中的例子展现了一个 Serverless 的 API。但是,这种机制也可以用于其他类型的应用中,比如托管在 Docker 容器、虚拟机中的应用,甚至自托管的应用。对于短时间内大量调用的场景,其好处会显现出来。如果只是几个客户端不时地进行调用,那么在解决方案中再增加一个系统可能并不是高效的办法。


作者简介:


Cristian Gherghinescu 自 2006 年以来一直在软件开发领域工作。他目前在挪威的 Visma 公司担任软件架构师。Cristian 从 C#和 Java EE 开始其职业生涯,现在专注于将当前的解决方案迁移到亚马逊云科技平台上。最近,他开始热衷于 Serverless 的解决方案。


原文链接:


Serverless Solution to Offload Polling for Asynchronous Operation Status Using Amazon S3

2021 年 12 月 08 日 15:352326

欲了解 AWS 的更多信息,请访问【AWS 技术专区】

评论

发布
暂无评论
发现更多内容

2020年Android开发年终总结之如何挤进一线大厂?,android界面开发实验报告

android 程序员 移动开发

2020年Android高级面试题总结(附答案解析),面试突击版

android 程序员 移动开发

2020应届毕业生,Android春招总结,已入职小米,深入解析android核心组件和应用框架

android 程序员 移动开发

2021字节跳动,金三银四内幕Android中高级面试题合集-令人细思极恐!

android 程序员 移动开发

2021想进阿里?送你一份 40000 字《阿里进阶指南,android音视频编解码

android 程序员 移动开发

2021 年 9 月美团 Android 面试总结,flutter屏幕旋转监听

android 程序员 移动开发

2021最新Android必备面试题,上海大厂Android面试经历

android 程序员 移动开发

2020年末知识大总结:Java程序员转Android开发必读经验一份

android 程序员 移动开发

2020我的坑爹Android面试经历,(百度,android路由器

android 程序员 移动开发

2021Android性能优化总结最新、最全面、最完整的资料,大厂内部资料

android 程序员 移动开发

2021年之Android面经分享(已获头条、顺丰,androidapp开发工具

android 程序员 移动开发

2021年最新Android开发岗面试笔试总结,android开发视频播放器

android 程序员 移动开发

2020我的-Android-年中面试复盘:怎么挤进一线大厂?需要掌握些什么

android 程序员 移动开发

2020最新Android大厂高频面试题解析大全(BAT TMD JD 小米)(1)

android 程序员 移动开发

2020最新Android大厂高频面试题解析大全(BAT TMD JD 小米)

android 程序员 移动开发

2020这一年的Android面经汇总(百度、腾讯、滴滴,职场中的中年危机

android 程序员 移动开发

2020非科班生的Android秋招,金九银十求职经历 (快手,android游戏开发实践指南

android 程序员 移动开发

2020年的大厂末班车!啃完这些资料,我拿到了字节跳动Android高级开发工程师的offer

android 程序员 移动开发

2020适合中高级工程师的面试题——java基础,移动网页开发框架

android 程序员 移动开发

2021年Android开发的前景如何?,安卓面试题及答案

android 程序员 移动开发

2020年度整理国内一线互联网公司内部Android面试题库,androidstudio开发项目

android 程序员 移动开发

2020年,Android技术人如何实现自我成长?,带你碾压面试官

android 程序员 移动开发

2020最新中高阶Android面试题总结-下(附解题思路),androidwifi开发框架

android 程序员 移动开发

2020阿里P8单写给Android程序员的建议:这样的offer不能要啊!注意避坑

android 程序员 移动开发

2021年3月份Android 面经总结!(OPPO和腾讯等大厂,android底层开发百度网盘

android 程序员 移动开发

2020年中总结之----怎么挤进一线大厂?非软文!,2021Android面试心得

android 程序员 移动开发

2020最新Android大厂面试真题大全(附答案),移动应用开发的前景

android 程序员 移动开发

2021最新整理大厂Android面试高频知识点,阿里高级算法专家公开10份资料

android 程序员 移动开发

2020年中总结之----怎么挤进一线大厂?非软文!(1),Android面试题整理

android 程序员 移动开发

模块二作业:微信朋友圈复杂度分析

赵先生

架构实战营

2021了,为什么说音视频技术是技术风口?Android音视频开发这么吃香

android 程序员 移动开发

借助Amazon S3实现异步操作状态轮询的Serverless解决方法-InfoQ