深入Serverless—让Lambda 和 API Gateway支持二进制数据

2019 年 11 月 15 日

深入Serverless—让Lambda 和 API Gateway支持二进制数据

1.概述


Serverless 即无服务器架构正在迅速举起,AWS Lambda 和 AWS API Gateway 作为 Serverless 架构主要的服务,正受到广泛关注,也有越来越多用户使用它们,享受其带来的便利。传统上来说,Lambda 和 API Gateway 主要用以实现 RESTful 接口,其响应输出结果是 JSON 数据,而实际业务场景还有需要输出二进制数据流的情况,比如输出图片内容。本文以触发式图片处理服务为例,深入挖掘 Lambda 和 API Gateway 的最新功能,让它们支持二进制数据,展示无服务器架构更全面的服务能力。


先看一个经典架构的案例——响应式主动图片处理服务。


Lambda 配合 S3 文件上传事件触发在后台进行图片处理,比如生成缩略图,然后再上传到 S3,这是 Lambda 用于事件触发的一个经典场景。


http://docs.aws.amazon.com/lambda/latest/dg/with-s3-example.html


在实际生产环境中这套架构还有一些局限,比如:


  • 后台运行的图片处理可能无法保证及时完成,用户上传完原图后需要立即查看缩略图时还没有生成。

  • 很多图片都是刚上传后使用频繁,一段时间以后就使用很少了,但是缩略图还不能删,因为也可能有少量使用,比如查看历史订单时。

  • 客户端设备类型繁多,一次性生成所有尺寸的缩略图,会消耗较多Lambda运算时间和 S3存储。

  • 如果增加了新的尺寸类型,旧图片要再生成新的缩略图就比较麻烦了。


我们使用用户触发的架构来实现实时图片处理服务,即当用户请求某个缩略图时实时生成该尺寸的缩略图,然后通过 CloudFront 缓存在 CDN 上。这其实还是事件触发执行 Lambda,只是由文件上传的事件主动触发,变成了用户访问的被动触发。但是只有原图存储在 S3,任何尺寸的缩图都不生成文件不存储到 S3。要实现此架构方案,核心技术点就是让 Lambda 和 API Gateway 可以响应输出二进制的图片数据流。


总体架构图如下:



主要技术点:


  • 涉及服务都是AWS完全托管的,自动扩容,无需运维,尤其是 Lambda,按运算时间付费,省去 EC2 部署的繁琐。

  • 原图存在 S3 上,只开放给 Lambda 的读取权限,禁止其它人访问原图,保护原图数据安全。

  • Lambda 实时生成缩略图,尽管Lambda目前还不支持直接输出二进制数据,我们可以设置让它输出base64编码后的文本,并且不再使用JSON结构。配合API Gateway可以把base64编码后的文本再转换回二进制数据,最终就可以实现输出二进制数据流了。

  • 用 API Gateway 实现 图片访问的URL。我们常见的API Gateway用来做RESTful 的API接口,接口的 URL形式通常是 /resource?parameter=value,其实还可以配置成不用GET参数,而把URL中的路径部分作参数映射成后端的参数。

  • 回源 API Gateway,缓存时间可以用户自定义,建议为24小时。直接支持 HTTPS,支持享用AWS全球边缘节点。

  • CloudFront 上还可使用 Route 53 配置域名,支持用户自己的域名。


相比前述的主动生成,被动触发生成有以下便利或优势:


  • 缩略图都不存储在S3上,节省存储空间和成本。

  • 方便给旧图增加新尺寸的缩略图。


2.部署与配置


本例中使用的 Region 是 Oregon(us-west-2),有关文件从以下链接下载:


https://s3.amazonaws.com/snowpeak-share/lambda/awslogo.png


2.1 使用 IAM 设置权限


打开控制台:


https://console.aws.amazon.com/iam/home?region=us-west-2


创建一个 Policy,名叫 CloudWatchLogsWrite,用于确保 Lambda 运行的日志可以写到 CloudWatch Logs。内容是


{


"Version":


"2012-10-17",


"Statement": [


{


"Effect":


"Allow",


"Action": [


"logs:CreateLogGroup",


"logs:CreateLogStream",


"logs:PutLogEvents",


"logs:DescribeLogStreams"


],


"Resource": [


"arn:aws:logs:*:*:*"


]


}


]


}


创建 一个 Role,名叫 LambdaReadS3,用于 Lambda 访问 S3。Attach Poilcy 选 AmazonS3ReadOnlyAccess 和刚刚创建的 CloudWatchLogsWrite。


记下它的 ARN,比如 arn:aws:iam::111122223333:role/LambdaReadS3


2.2 使用 S3 配置原图存储


打开控制台


https://console.aws.amazon.com/s3/home?region=us-west-2


创建 Bucket,Bucket Name 需要填写全局唯一的,比如 img201703,Region 选 US Standard。通常图片的原图禁止直接访问,这里我们设置权限,仅允许 Lambda 访问。


Permissions 下点 Add bucket policy,使用 AWS Policy Generator :


Select Type of Policy 选 S3 bucket policy,


Principal 填写前述创建的 LambdaReadS3 的 ARN arn:aws:iam::111122223333:role/LambdaReadS3


Actions 下拉选中 GetObjext,


Amazon Resource Name (ARN) 填写刚刚创建的 bucket 的 ARN,比如arn:aws:s3:::image201703/*


然后点 Add Statement,最后再点 Generate Policy,生成类似


{


"Id": "Policy1411122223333",


"Version":


"2012-10-17",


"Statement": [


{


"Sid": "Stmt1411122223333",


"Action": [


"s3:GetObject"


],


"Effect":


"Allow",


"Resource":


"arn:aws:s3:::img201703/*",


"Principal": {


"AWS": [


"arn:aws:iam::111122223333:role/LambdaReadS3"


]


}


}


]


}


复制粘贴到 Bucket Policy Editor 里 save 即可。


验证 S3 bucket 配置效果。把前下载图片文件 awslogo.png 下载到自己电脑,然后把它上传到这个 bucket 里,测试一下。直接访问链接不能下载,需要右键菜单点“Download”才能下载,说明权限配置已经成功。


2.3 创建 Lambda 函数


AWS Lambda 管理控制台:


https://us-west-2.console.aws.amazon.com/lambda/home?region=us-west-2#/


点击 Create a Lambda function 按钮


Select runtime 菜单选 Node.js 4.3,然后点 Blank Function。


Configure triggers 页,点 Next。


Configure function 页,在 Name 栏输入 ImageMagick,Description 栏输入


Uses ImageMagick to perform simple image processing operations, such as resizing.


Lambda function code 里填写以下代码:


'use strict';


var AWS = require('aws-sdk');


const im = require('imagemagick');


const fs = require('fs');


const defaultFilePath = 'awslogo_w300.png';


// 样式名配置,把宽高尺寸组合定义成样式名。


const config = {


'w500':{'width':500,


'height':300},


'w300':{'width':300,


'height':150},


'w50':{'width':50,


'height':40}


};


// 默认样式名


const defaultStyle = 'w50';


// 完成处理后把临时文件删除的方法。


const postProcessResource = (resource, fn) => {


let ret = null;


if (resource) {


if (fn) {


ret = fn(resource);


}


try {


fs.unlinkSync(resource);


} catch (err) {


// Ignore


}


}


return ret;


};


// 生成缩略图的主方法


const resize = (filePathResize, style, data, callback) => {


// Lambda 本地写文件,必须是 /tmp/ 下


var filePathResize =


'/tmp/'+filePathResize;


// 直接用 Buffer 操作图片转换,源文件不写到本地磁盘,但是转换成的文件要写盘,所以最后再用 postProcessResource 把临时文件删了。


var resizeReq = {


srcData: data.Body,


dstPath: filePathResize,


width: style.width,


height: style.height


};


try {


im.resize(resizeReq,


(err) => {


if (err) {


throw err;


} else {


console.log('Resize ok: '+ filePathResize);


// 注意这里不使用JSON结构,直接输出图片内容数据,并且要进行base64转码。


callback(null,


postProcessResource(filePathResize, (file) => new


Buffer(fs.readFileSync(file)).toString('base64')));


}


});


} catch (err) {


console.log('Resize


operation failed:', err);


callback(err);


}


};


exports.handler = (event, context, callback) => {


var s3 = new AWS.S3();


//改成刚刚创建的 bucket 名字,如 img201703


var bucketName = 'image201702';


// 从文件 URI 中截取出 S3 上的 key 和尺寸信息。


// 稳妥起见,尺寸信息应该规定成样式名字,而不是直接把宽高参数化,因为后者会被人滥用。


// 使用样式还有个好处,样式名字如果写错,可以有个默认的样式。


var filepath = (undefined ===


event.filepath ? defaultFilePath:event.filepath);


var tmp =


filepath.split('.');


var fileExt = tmp[1];


tmp = tmp[0].split('_');


var fileName = tmp[0];


var style = tmp.pop();


console.log(style);


var validStyle = false;


for (var i in config)


{


if (style == i)


{


validStyle = true;


break;


}


}


style = validStyle ? style :


defaultStyle;


console.log(style);


var fileKey =


fileName+'.'+fileExt;


var params = {Bucket:


bucketName, Key: fileKey};


// 从 S3 下载文件,成功后再回调缩图


s3.getObject(params,


function(err, data) {


if (err)


{


console.log(err,


err.stack);


}


else


{


resize(filepath,


config[style], data, callback);


}


});


};


注意一定要把


var bucketName = ‘image201702’;


改成刚刚创建的 bucket 名字,如


var bucketName = ‘img201703’;


这个 Lambda 函数就可以运行了。


Lambda function handler and role 部分的 Role 选择 Choose an existing role,然后 Existing role 选择之前创建的 LambdaReadS3。


Advanced settings:Memory (MB)* 选 512,Timeout 选 30 sec。


其它保持默认,点 Next;最后一页确认一下,点 Create Function。


提示创建成功。


点击 Test 按钮,测试一下。第一次测试时,会弹出测试使用的参数值,这些参数其实我们都不用,也不用管它,点击 Save and test 按钮测试即可。以后再测试就不会弹出了。


显示“Execution result: succeeded”表示测试成功了,右边的 Logs 链接可以点击,前往 CloudWatch Logs,查看详细日志。右下方的 Log output 是当前测试执行的输出。



可以看到这里 Execution result 下面显示的结果是一个长字符串,已经不是我们以往普通 Lambda 函数返回的 JSON 结构了。想做进一步验证的,可以把这个长字符串 base64 解码,会看到一个尺寸变小的图片,那样可以进一步验证我们运行成功。


2.4 配置 API Gateway


管理控制台


https://us-west-2.console.aws.amazon.com/apigateway/home?region=us-west-2#


2.4.1 配置 API


点击 Create API


API name 填写 ImageMagick。


Description 填写 Endpoint for Lambda using ImageMagick to perform simple image processing operations, such as resizing.


这时左侧导航链接会显示成 APIs > ImageMagick > Resources。点击 Actions 下拉菜单,选择 Create Resource。


Resource Name* 填写 filepath


Resource Path* 填写 {filepath},注意要包括大括号。然后点击 Create Resource 按钮。


这时刚刚创建的{filepath}应该是选中状态,再点击 Actions 下拉菜单,选择 Create Method,在当时出现的方法菜单里选择 GET,然后点后面的对号符确定。


然后在/{filepath} – GET – Setup 页,Integration type 保持 Lambda Function 不变,Lambda Region 选 us-west-2,在 Lambda Function 格输入 ImageMagick,下拉的备选菜单中点中 ImageMagick,点击 Save。弹出赋权限提示,点击“OK”。


这时会显示出完整的“/{filepath} – GET – Method Execution”配置页。


点击右上角“Integration Request”链接,进入配置页,点击“Body Mapping Templates”左边的三角形展开之。


Request body passthrough 选择 When there are no templates defined (recommended)。


点击最下面“add mapping template”链接,“Content-Type”格,注意即使这里已经有提示文字 application/json,还是要自己输入 application/json,然后点击右边的对勾链接,下面会弹出模板编辑输入框,输入


{“filepath”: “$input.params(‘filepath’)”}


完成的效果如下图所示:



最后点击“Save”按钮。点击左上角 “Method Execution”链接返回。


点击左下角“Method Response”链接,HTTP Status 下点击第一行 200 左边的三角形展开之,“Response Headers for 200” 下点击 add header 链接,Name 格输入 Content-Type,点击右边的对勾链接保存。Response Body for 200 下已有一行 application/json,点击其右边的笔图标编辑,把值改成 image/png,点击右边的对勾链接保存。点击左上角 “Method Execution”链接返回。


点击右下角“Integration Response”链接,点击第一行 “- 200 Yes”左边的三角形展开之,“Content handling”选择 Convert to binary(if needed),然后点击“Save”按钮。这项配置是把 Lambda 返回的 base64 编码的图片数据转换成二进制的图片数据,是此架构的另一个技术重点。


Header Mappings 下已有一行 Content-Type,点击其右边的笔图标编辑,在“Mapping value”格输入’image/png’,注意要带上单引号,点击右边的对勾链接保存。


点击“Body Mapping Templates”左边三角形展开之,点击“application/json”右边的减号符,把它删除掉。点击左上角 “Method Execution”链接返回。


点击最左边的竖条 Test 链接,来到“/{filepath} – GET – Method Test”页,“{filepath}”格输入 awslogo_w300.png,点击 Test 按钮。右侧显示类似下面的结果


Request: /awslogo_w300.png


Status: 200


Latency: 247 ms


Response Body 是乱码是正常的,因为我们的返回内容就是图片文件本身。可以查看右下角 Logs 部分显示的详细执行情况,显示类似以下的日志表示执行成功。


Thu Mar 09 03:40:11 UTC 2017 : Method response body


after transformations: [Binary Data]


Thu Mar 09 03:40:11 UTC 2017 : Method response


headers: {X-Amzn-Trace-Id=Root=1-12345678-1234567890abcdefghijlkln,


Content-Type=image/png}


Thu Mar 09 03:40:11 UTC 2017 : Successfully completed


execution


Thu Mar 09 03:40:11 UTC 2017 : Method completed with


status: 200


2.4.2 部署 API


点击 Actions 按钮,在下拉菜单中点选 Deploy API,Deployment stage 选择[New Stage],Stage name 输入 test,注意这里都是小写。


Stage description 输入 test stage


Deployment description 输入 initial deploy to test.


点击 Deploy 按钮。然后会跳转到 Test Stage Editor 页。


复制 Invoke URL: 后面的链接,比如


https://1234567890.execute-api.us-west-2.amazonaws.com/test


然后在后面接上 awslogo_w300.png,组成形如以下的链接


https://1234567890.execute-api.us-west-2.amazonaws.com/test/awslogo_w300.png


输入浏览器地址栏里访问,可以得到一张图片,表示 API Gateway 已经配置成功。


2.5 配置 CloudFront 分发


我们在 API Gateway 前再加上 CloudFront,通过 CDN 缓存生成好的图片,就可以实现不需要把缩略图额外存储,而又不用每次都为了图片处理进行计算。这里使用了 CDN 和其它使用 CDN 的思路一样,如果更新图片,不建议调用清除 CloudFront 的 API,而是从应用程序生成新的图片标识字符串,从而生成新的 URL 让 CloudFront 成为无缓存状态从而回源重新计算。


由于 API Gateway 仅支持 HTTPS 访问,而 CloudFront 同时支持 HTTP 和 HTTPS,所以我们可以配置成 CloudFront 前端同时支持 HTTP 和 HTTPS,但是实测发现 CloudFront 前端使用 HTTP 而回源使用 HTTPS 时性能不如前端和回源同为 HTTPS。所以这里我们也采用同时 HTTPS 的方式。


我们打开 CloudFront 的管理控制台


https://console.aws.amazon.com/cloudfront/home?region=us-west-2#


点击 Create Distribution 按钮,在 Web 下点击 Get Started。


Origin Domain Name,输入上述部署出来的 API Gateway 的域名,比如 1234567890.execute-api.us-west-2.amazonaws.com


Origin Path,输入上述 API Gateway 的 Stage 名,如 /test


Origin Protocol Policy 选择 HTTPS Only


Object Caching 点选 Customize,然后 Maximum TTL 输入 86400


Alternate Domain Names (CNAMEs) 栏本例使用自己的域名,比如 img.myexample.com。SSL Certificate 选择 Custom SSL Certificate (example.com),并从下面的证书菜单中选择一个已经通过 ACM 管理的证书。


注意,如果填写了自己的域名,那么下面的 SSL Certificate 就不建议使用默认的 Default CloudFront Certificate (*.cloudfront.net),因为很多浏览器和客户端会发现证书的域名和图片 CDN 的域名不一致会报警告。


其它项保持默认,点击 Create Distribution 按钮,然后回到 CloudFront Distributions 列表,这里刚刚创建的记录 Status 会显示为 In Progress,我们点击 ID 的链接,前进到详情页,可以看到 Domain Name 显示一个 CloudFront 分发的 URL,比如 cloudfronttest.cloudfront.net。大约 10 多分钟后,等待 Distribution Status 变成 Deployed,我们可以用上述域名来测试一下。注意测试用的 URL 不要包含 API Gateway 的 Stage 名,比如


https://1234567890.execute-api.us-west-2.amazonaws.com/test/awslogo_w300.png


那么 CloudFront 的 URL 应该是


https://cloudfronttest.cloudfront.net/awslogo_w300.png


尽管我们已经配置了自己的域名,但是这时自已的域名还未生效。我们还需要到 Route 53 去添加域名解析。


2.6 Route 53


最后我们使用 Route 53 实现自定义域名关联 CloudFront 分发。访问 Route 53 管制台


https://console.aws.amazon.com/route53/home?region=us-west-2


在 Hosted Zone 下点击一个域名,在域名列表页,点击上方 Create Record Set 按钮,页面右侧会弹出创建记录集的面板。


Name 栏输入 img。


Type 保持默认 A – IP4 Address 不变。


Alias 点选 Yes,在 Alias Target 输入前述创建的 CloudFront 分发的 URL cloudfronttest.cloudfront.net。


点击右下角 Create 按钮完成创建。


3. 效果验证


现在我们回到 CloudFront 控制台,等到我们的 Distribution 的 Status 变成 Deployed,先用 CloudFront 自身的域名访问一下。


https://cloudfronttest.cloudfront.net/awslogo_w300.png


顺利的话,会看到咱们的范例图片。再以自定义域名访问一下。


http://img.myexample.com/awslogo_w300.png


还是输出这张图片,那么到此就全部部署成功了。现在可以在 S3 的 bucket 里上传更多其它图片,比如 abc.png,然后访问时使用的 URL 就是


http://img.myexample.com/abc_w300.png


用浏览器打开调试工具,可以看到响应头里已经有


X-Cache: Hit from cloudfront


表示已经经过 CloudFront 缓存。


4. 监控


这个架构方案使用的服务都可以通过 CloudTrail 记录管理行为,使用 CloudWatch 记录用户访问情况。


4.1 Lambda 监控


在 Lambda 控制台点击我们的 ImageMagick 函数,然后点击选项卡最末一个 Monitoring,可以看到常用指标的简易图表。



点击任何一个图表,都可以前进到 CloudWatch 相关指标的指标详细页。然后我们还可以为各个指标配置相关的 CloudWatch Alarm,用以监控和报警。


点击 View logs in CloudWatch 链接,可以前往 CloudWatch Log,这里记录了这个 Lambda 函数每次执行的详细信息,包括我们的函数中自已输出的调试信息,方便我们排查问题。


4.2 API Gateway 监控


在 API Gateway 控制台找到我们的 API ImageMagick,点击它下面的 Dashboard。



如果部署了多个 Stage,注意左上角 Stage 菜单要选择相应的 Stage。同样下面展示的是常用图表,点击每个图表也可以前往 CloudWatch 显示指标监控详情。


4.3 CloudFront 日志


我们刚刚配置 CloudFront 时没有启用日志。如果需要日志,可以来到 CloudFront 控制台,点击我们刚刚创建的分发,在 General 选项页点击 Edit 按钮。在 Edit Distribution 页找到 Logging 项,选择 On,然后再填写 Bucket for Logs 和 Log Prefix,这样 CloudFront 的访问日志就会以文件形式存储在相应的 S3 的 bucket 里了。


5. 小结


我们这样一个例子使用了 Lambda 和 API Gateway 的一些高级功能,并串联了一系列 AWS 全托管的服务,演示了一个无服务器架构的典型场景。虽然实现的功能比较简单,但是 Lambda 函数可以继续扩展,提供更丰富功能,比如截图、增加水印、定制文本等,几乎满足任何的业务需求。相比传统的的计算能力部署,不论是用 EC2 还是 ECS 容器,都要自己管理扩容,而使用 Lambda 无需管理扩容,只管运行代码。能够让我们从繁琐的重复工作中解脱,而把业务集中到业务开发上,这正是无服务器架构的真正理念和优势。


作者介绍:



薛峰


AWS 解决方案架构师,AWS 的云计算方案架构的咨询和设计,同时致力于 AWS 云服务在国内和全球的应用和推广,在大规模并发应用架构、移动应用以及无服务器架构等方面有丰富的实践经验。在加入 AWS 之前曾长期从事互联网应用开发,先后在新浪、唯品会等公司担任架构师、技术总监等职位。对跨平台多终端的互联网应用架构和方案有深入的研究。


本文转载自 AWS 技术博客。


原文链接:


https://amazonaws-china.com/cn/blogs/china/deep-dive-in-serverless-lambda-and-api-gateway-support-binary-data/


2019 年 11 月 15 日 08:00116

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

评论

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

总结04-互联网架构演化

梦子说

课程作业

Week 04 作业

鱼_XueTr

架构

架构师训练营第四周学习总结

AceXu

学习

互联网系统架构——总结(架构师训练营week4)

小叶

极客大学架构师训练营

架构师训练营第四周总结

allen

第四周学习总结

qqq

第四周总结

carol

深入解析典型的大型互联网应用系统

拈香(曾德政)

互联网 架构师 极客大学架构师训练营 互联网架构 互联网应用技术方案

分布式系统架构学习总结(第四周)

吴建中

极客大学架构师训练营

系统架构 - 第四周

X﹏X

架构学习第四周作业

乐天

架构师训练营 - 总结4

进击的炮灰

面向对象学习

一叶知秋

第四周作业 - 命题作业

molly

极客大学架构师训练营

架构师训练营 第四周 命题作业

RZC

架构师训练营第四周课后总结

Cloud.

第四周 - 学习总结

molly

极客大学架构师训练营

【架构师训练营 - week4 -1】作业

早睡早起

互联网系统架构总结

紫极

大型互联网应用的发展和未来

拈香(曾德政)

互联网 极客大学架构师训练营 互联网架构 互联网架构的演进

第四周作业

qqq

架构师训练营-作业4

紫极

架构师第四课总结

Dennis

大规模复杂系统如何架构(一)?

阿飞

架构 极客大学架构师训练营

架构师课作业 - 第四周

Tulane

架构师 0 期 | 大型互联网系统使用了哪些技术?

刁架构

极客大学架构师训练营

Week4-作业

龙7

第四周学习总结

子豪sirius

【架构师训练营 - week4 -2】总结

早睡早起

04周作业——互联网系统架构

dao

极客大学架构师训练营 作业

架构师训练营第四周总结

陌生人

深入Serverless—让Lambda 和 API Gateway支持二进制数据-InfoQ