AWS Lambda的各种优秀实践

译文
云计算 服务器运维
如今,无服务器已经成为了各种云应用的最常见部署模式。而在这个领域中,AWS Lambda可谓最为“骨灰级”的工具了。大多数开发人员都或多或少地有过,运用Lambda来快速构建并运行某个云端函数代码的经历。在本文中,我将分享:如何通过了解Lambda的工作原理,来合理并充分地使用Lambda。

【51CTO.com快译】概述

如今,无服务器已经成为了各种云应用的最常见部署模式。而在这个领域中,AWS Lambda可谓最为“骨灰级”的工具了。大多数开发人员都或多或少地有过,运用Lambda来快速构建并运行某个云端函数代码的经历。

然而,AWS在管理和处理可扩展性、高可用性(HA)、安全性、以及性能等方面,却不像AI机器人那样,通过自我学习和优化配置,来改进所有的云原生(cloud-native)指标。因此,开发人员需要在设计时,尤其注意并学习如何在成本和性能之间达到平衡。在本文中,我将分享:如何通过了解Lambda的工作原理,来合理并充分地使用Lambda。

高可用性

当我们在运行某个Lambda函数时,它实际上是默认运行在一个可以访问外网的VPC(Virtual Private Cloud)之上。不过,它却无法同时访问到其他任何私有的VPC。此处所谓“访问外网”是指:它只能访问S3和DynamoDB的AWS服务;而对于那些运行在其他VPC下的AWS资源(如:RDS,Elasticsearch等),则无法访问到。

如果某个函数运行在Lambda所管理的VPC上,那么Lambda将负责它在该VPC区域的多个AZ(Availability Zone)中的可用性。但在大多数企业应用场景中,我们的确需要同时访问到RDS和其他的VPC资源。因此,我们需要确保如下两个方面:

  • 通过在不同的AZ中选择多个子网,来设计Lambda、并实现高可用性。

AWS Lambda的各种优秀实践

  • 如果某个AZ发生故障,则其他AZ需要被分配足够的IP地址,来处理并发的Lambda请求。(注意,每个Lambda的执行都需要有一个私有的IP地址,来处理请求。)因此,我们需要在子网中分配足够多的IP地址,以达到HA。

并发性

虽说AWS Lambda会以自己的方式来实现可伸缩性,但是对于有限的资源而言,Lambda会遵循如下的并发执行限制:

  • 帐户级别 - 默认情况下,它会参照每个区域内的所有函数,将该值定为1000。
  • 函数级别 - 默认情况下,它会使用“Unreserved Account Concurrency limit”,但是这并不是一种很好的实现方式。为了避免耗尽所有帐户级别的并发数,它会限制其他的功能函数。因此,我们应该为每一个函数保留单独的并发数,以便在事件数量因为某种原因出现激增时,仅影响并隔离在该函数之中。

AWS Lambda的各种优秀实践

注意 - AWS始终保留一个具有至少100个并发执行量的无保留并发池(unreserved concurrency pool),以处理那些未做特殊设置的函数请求。也就是说,您最多只能分配900个。

如果我们是在一个专有的VPC上运行Lambda呢?

在这种情况下,我们需要根据函数的ENI(Elastic Network Interfaces,弹性网络接口)扩展性,去请求足够多的IP地址。您可以使用如下公式来估算ENI的近似容量:

  1. Concurrent executions * (Memory in GB / 3 GB) 

其中:

  • 发执行 - 是工作负载的预计并发数(用每秒调用次数*平均执行的时长,以秒为单位)。
  • 内存大小 - 是为Lambda函数配置的内存数量(以GB为单位)。

在设计Lambda的并发性时,我们还应该始终考虑,诸如DynamoDB、RDS等其他集成服务的限制。我们需要根据这些服务能够处理的***连接,来调整函数的并发限制。

节流

正如前文在“并发性”中提到的,一旦函数事件出现激增,并超过了并发数的限制,那么Lambda将无法再处理任何新的请求。如果我们不及时予以处理的话,业务系统就会受到影响。

  1. 如果Lambda的调用是同步模式的话,它会马上接收到429类型的错误代码。同时,如果节流被设定为函数级别或是帐户级别,那么它还能接收其他一些信息。因此,诸如API网关之类的调用服务,则需要处理此类重试问题。
  2. 如果Lambda的调用是异步模式的话,Lambda只会在丢弃事件之前尝试两次。因此,如果函数无法处理该事件,我们就应该使用SQS或SNS所定义的DLQ(Dead Letter Queue),来做稍后调试与处理。而如果我们忘记了定义DLQ,那些消息则会被直接丢弃掉。
  3. 如果Lambda调用是基于轮询模式的话,我们进一步细分两种情况:
  • 如果是流式(Kinesis),它将继续重试,直到超时(最多为7天)。
  • 如果是非流式(SQS),它将把消息放回到队列之中,并仅在Visibility时限到期后才开始重试,并持续执行下去,直到它能够成功地完成处理、或是超过保留期。

内存与成本之间的平衡

在Lambda里,内存和CPU息息相关,也就是说,如果您增加了内存,那么CPU分配也应该有所增加。因此,如果需要减少Lambda的执行时间,那么我们就应当增加内存和CPU。但是,如果您进行过详细的实验就会发现:在一定的限制情况下,单凭增加内存只会增加购置成本,而并不会大幅减少执行的时间。

目前市面上很少有开源的工具,能够帮助我们找到***的资源配置。我个人倾向使用CloudWatch的各种日志,来监控内存的使用情况和执行时间,进而调整相应的配置。对内存进行参数微调,便可对AWS的整体成本产生较大的影响,我籍此来找到***平衡点。

性能 - 冷启动与热启动

当我们***次调用Lambda时,它会从S3那里下载代码和所有依赖项,以创建容器,并在执行代码之前先启动对应的应用程序。整个过程的耗时(代码的执行除外)被称为冷启动时间。而一旦容器被启动并运行起来后,Lambda就已经为后续的调用完成了初始化,它只需要执行应用程序的逻辑便可。因此,这段时长就被称为热启动时间。

AWS Lambda的各种优秀实践

那么问题来了,我们应该缩短冷启动时间、还是热启动时间呢?原则上说,作为完整执行时长的一部分,冷启动占据了大部分时间,因此需要想办法予以减少。但是,在实践中,我们却可以通过优质的代码,来减少热启动的时间。

下面,让我们讨论如何才能提高Lambda的整体性能:

  1. 选择诸如Nodejs和Python之类的解释性语言,而不是Java或C ++,来减少冷启动时间。
  2. 如果出于某种原因不得不选用Java的话,请使用Spring Cloud Functions,而不是Spring Boot Web框架。
  3. 由于我们设置ENI的耗时较长、并且会增加冷启动时间,因此除非您需要带有专有IP地址的VPC资源,否则请使用默认的网络环境。我个人判读:随着新版AWS Lambda的即将发布,此方面应该有所改进。
  4. 删除所有与运行该函数无关的依赖项。仅保留那些必需的。
  5. 使用各种全局/静态变量、以及Singleton对象,这些变量能够在容器发生故障之前,一直保持活动状态。因此,任何后续调用都不必再重新初始化这些变量与对象了。
  6. 请使用全局定义的数据库连接,以便它们能够被重用到后续的调用中。
  7. 如果您选用的是Java,那么请使用诸如Dagger和Guice之类的简单IoC依赖注入,而不是Spring框架。
  8. 同样,如果您选用了Java,那么请将依赖项.jar文件与函数的代码相分离,以便对解包程序加速。
  9. 如果您选用的是Nodejs,请控制Function js文件的体积小于600字符,并使用V8的运行环境(runtime)。V8优化器能够内联主体小于600字符(包括各种注释)的函数。
  10. 同样,如果您选用了Nodejs,则可以使用代码的minification和/或uglification,来减小包的大小,进而大幅减少下载包的耗时。在某些情况下,我曾经看到有将包的体积从10MB减少到1MB的案例。
  • Minification – 会删除掉所有的空格、换行符、以及注释。
  • Uglification – 会对所有变量进行混淆和简化。

示例,原代码: 

  1. var organizationname = “xyz” 
  2. var bigArray = [1,2,3,4,5,6] 
  3. //write some code 
  4. for(var index = 0; index < 6; index++){ 
  5.   console.log(bigArray[index]); 

Minification之后:

  1. var organizationname = “xyz”, bigArray = [1,2,3,4,5,6] for(var index = 0; index < 6; index++) console.log(bigArray[index]); 

Uglification之后:

  1. for(var o=”myname”,a=[1,2,3,4,5,6],e=0;e<6;e++)console.log(a[e])  

网上有不少的文章都提到:Lambda的执行环境已经具有了适用于Nodejs和Python的AWS SDK。因此,我们不必在依赖项中添加它们。此特性虽然有利于提高性能,但是潜藏着一个问题:该SDK库将定期使用***的修补程序来进行升级,为了不影响Lambda的各种行为,您***采用自己的依赖项管理方式。

安全性

  1. 为每个函数分配一个IAM角色。即使有多个函数需要相同的IAM策略,单个IAM角色也应该只映射一个函数。当特定的函数安全策略需要加固时,这将有助于保持最小权限的策略。
  2. 由于Lambda会在共享的VPC上运行,因此将AWS的凭据保留在代码中并不可取。
  • 在大多数情况下,IAM的执行角色已足以通过使用AWS SDK,去连接到AWS的各种服务。
  • 如果函数需要调用跨帐户的服务,则可能会使用到不同的凭据。因此我们需要在AWS的Security Token Service(请参见https://docs.aws.amazon.com/STS/latest/APIReference/Welcome.html)中使用Assume Role API,并检索各种临时凭据。
  • 如果函数需要存储长期凭据(如DB凭据、访问密钥),请使用带有加密助手或AWS System Manager的环境变量。

可测性

由于AWS Lambda让用户的代码运行在云端,那么我们该如何在本地进行测试呢?

虽然Lambda并不提供任何直接测试的URL,但是我们可以根据要启动的事件源系统来开展测试。

  • 我们可以使用AWS SAM(请参见https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html)进行Lambda函数的本地测试。它为CLI提供了类似本地Lambda的执行环境。我们可以获得API Gateway的localhost URL,它会在本地调用Lambda函数。
  • 我们可以使用localstack(请参见https://github.com/localstack/localstack)开源项目,来创建具有大量AWS资源/服务的本地环境。它可以与其他AWS服务一起运行Lambda。而且由于它能够以API的形式提供所有的服务,并能够在后端作为Docker容器运行,因此您也可以将AWS SAM与localstack相集成。
  • 将业务逻辑放到Lambda Handler之外。Handler函数应当仅用于检索各项输入,然后将它们传递给其他函数/方法。这些函数/方法会将它们解析成为与我们的应用程序相关的变量,然后进一步使用。该过程不但实现了将业务逻辑与处理程序相分离,而且可以在我们创建的对象和函数的上下文中进行测试。

AWS Lambda的各种优秀实践

蓝/绿部署

通过Lambda附带的Versioning和Alias功能,我们可以发布一个函数的多个版本。同时,我们可以在单独的容器中并行地调用每个版本。默认情况下,版本特征是由$LATEST来表示的。在开发的过程中,我们可以使用这些版本,来创建诸如dev/UAT等多个环境。但是,由于我们在每一次上传新的代码时,版本都会递增,而客户则会被指向***的版本。因此,我们***不要直接将Versioning用于生产环境,而可以用到Alias。

Alias可以指向函数的某个特定版本。因此,如果您的代码发生了更改,并且发布了更新的版本时,事件源仍将指向原来相同的Alias。我们只需管理好Alias何时需要被指向新的版本便可。这便实现了蓝/绿部署。我们可以使用一些样本事件来测试新的版本,确认其工作正常之后,再通过修改Alias的指向,来切换访问的流量。与此同时,如果发现出现任何问题,我们还可以迅速回滚到原始的版本上。

AWS Lambda的各种优秀实践

监控

个人以为:CloudWatch能够很好地与Lambda配合使用,并为用户提供Lambda执行的各种详细信息。Lambda能够自动跟踪请求数、每个请求的执行时间、导致错误的请求数、以及发布相关的CloudWatch指标。同时,您也可以利用这些指标,来自定义各种CloudWatch的警报功能。

另外,我们还可以使用X-Ray来识别Lambda执行中的各种潜在瓶颈。它对于我们试图可视化那些耗费在函数执行上的时间,是非常实用的。而且,X-Ray还有助于跟踪那些与整个流程相连接的所有下游系统。

其他建议

  • 请勿使用AWS Lambda Console开发那些直接被用在生产环境中的代码。
  1. 由于代码版本控制并非自动生效的,因此如果您误点了Save按钮,那么生产环境中的工作代码就会被***覆盖掉。
  2. 它并没有与GitHub、或其他代码存储库相集成。
  3. 它无法被导入AWS SDK之外的模块中。因此,如果您需要某种特定的库,则必须从一开始就在本地开发自己的函数、创建.zip文件、然后将其上传到AWS Lambda中。
  • 请使用AWS SAM或无服务器框架来进行开发。
  • 就Lambda部署的CI/CD计划而言,它其实与其他可交付式的计划并无不同。
  • 可使用各种环境变量(Environment Variables)和参数存储(Parameter Store),来将代码与配置相分离。

总结

在本文中,我们讨论了在设计和部署Lambda时,各种值得参考和使用的***实践。我们可以根据实际应用的编码语言和用例,来不断改进业务系统的性能。当然,我们也可以在其他的云平台,以及Kubernetes的无服务器平台中借鉴这些***实践。希望您能够将这些实践总结运用到自己成熟的生产环境与应用之中。

原文标题:AWS Lambda Best Practices,作者:Rajesh Bhojwani

【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】

责任编辑:未丽燕 来源: 51CTO.com
相关推荐

2018-05-17 22:55:48

AWS Lambda服务器代码

2014-11-14 10:16:18

亚马逊

2014-11-14 10:08:07

AWSAWS Lambda

2021-11-29 13:36:34

云计算AWS云平台网络安全

2016-07-01 16:13:13

AWSLambda

2023-05-04 17:20:54

AWS ECSAWS Lambda云计算

2023-07-30 15:00:21

2016-10-27 13:46:23

AWSLambdaServerless

2015-11-16 17:48:00

2020-10-13 08:03:47

ServerlessFramework

2014-06-27 13:32:07

GartnerAWS安全亚马逊AWS

2019-09-17 09:44:45

DockerHTMLPython

2021-04-15 08:08:48

微前端Web开发

2019-11-27 10:55:36

云迁移云计算云平台

2019-04-10 09:00:23

AWSOracle数据库

2016-11-25 23:04:00

AdMaster架构实践

2014-11-18 09:43:29

AWS Lambda事件驱动计算服务

2023-07-04 15:56:08

DevOps开发测试

2020-11-25 10:26:24

云计算云安全数据

2021-01-20 10:53:41

云计算云存储云迁移
点赞
收藏

51CTO技术栈公众号