AWS Lambda的各種優(yōu)秀實(shí)踐
譯文【51CTO.com快譯】概述
如今,無服務(wù)器已經(jīng)成為了各種云應(yīng)用的最常見部署模式。而在這個(gè)領(lǐng)域中,AWS Lambda可謂最為“骨灰級(jí)”的工具了。大多數(shù)開發(fā)人員都或多或少地有過,運(yùn)用Lambda來快速構(gòu)建并運(yùn)行某個(gè)云端函數(shù)代碼的經(jīng)歷。
然而,AWS在管理和處理可擴(kuò)展性、高可用性(HA)、安全性、以及性能等方面,卻不像AI機(jī)器人那樣,通過自我學(xué)習(xí)和優(yōu)化配置,來改進(jìn)所有的云原生(cloud-native)指標(biāo)。因此,開發(fā)人員需要在設(shè)計(jì)時(shí),尤其注意并學(xué)習(xí)如何在成本和性能之間達(dá)到平衡。在本文中,我將分享:如何通過了解Lambda的工作原理,來合理并充分地使用Lambda。
高可用性
當(dāng)我們?cè)谶\(yùn)行某個(gè)Lambda函數(shù)時(shí),它實(shí)際上是默認(rèn)運(yùn)行在一個(gè)可以訪問外網(wǎng)的VPC(Virtual Private Cloud)之上。不過,它卻無法同時(shí)訪問到其他任何私有的VPC。此處所謂“訪問外網(wǎng)”是指:它只能訪問S3和DynamoDB的AWS服務(wù);而對(duì)于那些運(yùn)行在其他VPC下的AWS資源(如:RDS,Elasticsearch等),則無法訪問到。
如果某個(gè)函數(shù)運(yùn)行在Lambda所管理的VPC上,那么Lambda將負(fù)責(zé)它在該VPC區(qū)域的多個(gè)AZ(Availability Zone)中的可用性。但在大多數(shù)企業(yè)應(yīng)用場(chǎng)景中,我們的確需要同時(shí)訪問到RDS和其他的VPC資源。因此,我們需要確保如下兩個(gè)方面:
- 通過在不同的AZ中選擇多個(gè)子網(wǎng),來設(shè)計(jì)Lambda、并實(shí)現(xiàn)高可用性。
- 如果某個(gè)AZ發(fā)生故障,則其他AZ需要被分配足夠的IP地址,來處理并發(fā)的Lambda請(qǐng)求。(注意,每個(gè)Lambda的執(zhí)行都需要有一個(gè)私有的IP地址,來處理請(qǐng)求。)因此,我們需要在子網(wǎng)中分配足夠多的IP地址,以達(dá)到HA。
并發(fā)性
雖說AWS Lambda會(huì)以自己的方式來實(shí)現(xiàn)可伸縮性,但是對(duì)于有限的資源而言,Lambda會(huì)遵循如下的并發(fā)執(zhí)行限制:
- 帳戶級(jí)別 - 默認(rèn)情況下,它會(huì)參照每個(gè)區(qū)域內(nèi)的所有函數(shù),將該值定為1000。
- 函數(shù)級(jí)別 - 默認(rèn)情況下,它會(huì)使用“Unreserved Account Concurrency limit”,但是這并不是一種很好的實(shí)現(xiàn)方式。為了避免耗盡所有帳戶級(jí)別的并發(fā)數(shù),它會(huì)限制其他的功能函數(shù)。因此,我們應(yīng)該為每一個(gè)函數(shù)保留單獨(dú)的并發(fā)數(shù),以便在事件數(shù)量因?yàn)槟撤N原因出現(xiàn)激增時(shí),僅影響并隔離在該函數(shù)之中。
注意 - AWS始終保留一個(gè)具有至少100個(gè)并發(fā)執(zhí)行量的無保留并發(fā)池(unreserved concurrency pool),以處理那些未做特殊設(shè)置的函數(shù)請(qǐng)求。也就是說,您最多只能分配900個(gè)。
如果我們是在一個(gè)專有的VPC上運(yùn)行Lambda呢?
在這種情況下,我們需要根據(jù)函數(shù)的ENI(Elastic Network Interfaces,彈性網(wǎng)絡(luò)接口)擴(kuò)展性,去請(qǐng)求足夠多的IP地址。您可以使用如下公式來估算ENI的近似容量:
- Concurrent executions * (Memory in GB / 3 GB)
其中:
- 發(fā)執(zhí)行 - 是工作負(fù)載的預(yù)計(jì)并發(fā)數(shù)(用每秒調(diào)用次數(shù)*平均執(zhí)行的時(shí)長,以秒為單位)。
- 內(nèi)存大小 - 是為Lambda函數(shù)配置的內(nèi)存數(shù)量(以GB為單位)。
在設(shè)計(jì)Lambda的并發(fā)性時(shí),我們還應(yīng)該始終考慮,諸如DynamoDB、RDS等其他集成服務(wù)的限制。我們需要根據(jù)這些服務(wù)能夠處理的***連接,來調(diào)整函數(shù)的并發(fā)限制。
節(jié)流
正如前文在“并發(fā)性”中提到的,一旦函數(shù)事件出現(xiàn)激增,并超過了并發(fā)數(shù)的限制,那么Lambda將無法再處理任何新的請(qǐng)求。如果我們不及時(shí)予以處理的話,業(yè)務(wù)系統(tǒng)就會(huì)受到影響。
- 如果Lambda的調(diào)用是同步模式的話,它會(huì)馬上接收到429類型的錯(cuò)誤代碼。同時(shí),如果節(jié)流被設(shè)定為函數(shù)級(jí)別或是帳戶級(jí)別,那么它還能接收其他一些信息。因此,諸如API網(wǎng)關(guān)之類的調(diào)用服務(wù),則需要處理此類重試問題。
- 如果Lambda的調(diào)用是異步模式的話,Lambda只會(huì)在丟棄事件之前嘗試兩次。因此,如果函數(shù)無法處理該事件,我們就應(yīng)該使用SQS或SNS所定義的DLQ(Dead Letter Queue),來做稍后調(diào)試與處理。而如果我們忘記了定義DLQ,那些消息則會(huì)被直接丟棄掉。
- 如果Lambda調(diào)用是基于輪詢模式的話,我們進(jìn)一步細(xì)分兩種情況:
- 如果是流式(Kinesis),它將繼續(xù)重試,直到超時(shí)(最多為7天)。
- 如果是非流式(SQS),它將把消息放回到隊(duì)列之中,并僅在Visibility時(shí)限到期后才開始重試,并持續(xù)執(zhí)行下去,直到它能夠成功地完成處理、或是超過保留期。
內(nèi)存與成本之間的平衡
在Lambda里,內(nèi)存和CPU息息相關(guān),也就是說,如果您增加了內(nèi)存,那么CPU分配也應(yīng)該有所增加。因此,如果需要減少Lambda的執(zhí)行時(shí)間,那么我們就應(yīng)當(dāng)增加內(nèi)存和CPU。但是,如果您進(jìn)行過詳細(xì)的實(shí)驗(yàn)就會(huì)發(fā)現(xiàn):在一定的限制情況下,單憑增加內(nèi)存只會(huì)增加購置成本,而并不會(huì)大幅減少執(zhí)行的時(shí)間。
目前市面上很少有開源的工具,能夠幫助我們找到***的資源配置。我個(gè)人傾向使用CloudWatch的各種日志,來監(jiān)控內(nèi)存的使用情況和執(zhí)行時(shí)間,進(jìn)而調(diào)整相應(yīng)的配置。對(duì)內(nèi)存進(jìn)行參數(shù)微調(diào),便可對(duì)AWS的整體成本產(chǎn)生較大的影響,我籍此來找到***平衡點(diǎn)。
性能 - 冷啟動(dòng)與熱啟動(dòng)
當(dāng)我們***次調(diào)用Lambda時(shí),它會(huì)從S3那里下載代碼和所有依賴項(xiàng),以創(chuàng)建容器,并在執(zhí)行代碼之前先啟動(dòng)對(duì)應(yīng)的應(yīng)用程序。整個(gè)過程的耗時(shí)(代碼的執(zhí)行除外)被稱為冷啟動(dòng)時(shí)間。而一旦容器被啟動(dòng)并運(yùn)行起來后,Lambda就已經(jīng)為后續(xù)的調(diào)用完成了初始化,它只需要執(zhí)行應(yīng)用程序的邏輯便可。因此,這段時(shí)長就被稱為熱啟動(dòng)時(shí)間。
那么問題來了,我們應(yīng)該縮短冷啟動(dòng)時(shí)間、還是熱啟動(dòng)時(shí)間呢?原則上說,作為完整執(zhí)行時(shí)長的一部分,冷啟動(dòng)占據(jù)了大部分時(shí)間,因此需要想辦法予以減少。但是,在實(shí)踐中,我們卻可以通過優(yōu)質(zhì)的代碼,來減少熱啟動(dòng)的時(shí)間。
下面,讓我們討論如何才能提高Lambda的整體性能:
- 選擇諸如Nodejs和Python之類的解釋性語言,而不是Java或C ++,來減少冷啟動(dòng)時(shí)間。
- 如果出于某種原因不得不選用Java的話,請(qǐng)使用Spring Cloud Functions,而不是Spring Boot Web框架。
- 由于我們?cè)O(shè)置ENI的耗時(shí)較長、并且會(huì)增加冷啟動(dòng)時(shí)間,因此除非您需要帶有專有IP地址的VPC資源,否則請(qǐng)使用默認(rèn)的網(wǎng)絡(luò)環(huán)境。我個(gè)人判讀:隨著新版AWS Lambda的即將發(fā)布,此方面應(yīng)該有所改進(jìn)。
- 刪除所有與運(yùn)行該函數(shù)無關(guān)的依賴項(xiàng)。僅保留那些必需的。
- 使用各種全局/靜態(tài)變量、以及Singleton對(duì)象,這些變量能夠在容器發(fā)生故障之前,一直保持活動(dòng)狀態(tài)。因此,任何后續(xù)調(diào)用都不必再重新初始化這些變量與對(duì)象了。
- 請(qǐng)使用全局定義的數(shù)據(jù)庫連接,以便它們能夠被重用到后續(xù)的調(diào)用中。
- 如果您選用的是Java,那么請(qǐng)使用諸如Dagger和Guice之類的簡(jiǎn)單IoC依賴注入,而不是Spring框架。
- 同樣,如果您選用了Java,那么請(qǐng)將依賴項(xiàng).jar文件與函數(shù)的代碼相分離,以便對(duì)解包程序加速。
- 如果您選用的是Nodejs,請(qǐng)控制Function js文件的體積小于600字符,并使用V8的運(yùn)行環(huán)境(runtime)。V8優(yōu)化器能夠內(nèi)聯(lián)主體小于600字符(包括各種注釋)的函數(shù)。
- 同樣,如果您選用了Nodejs,則可以使用代碼的minification和/或uglification,來減小包的大小,進(jìn)而大幅減少下載包的耗時(shí)。在某些情況下,我曾經(jīng)看到有將包的體積從10MB減少到1MB的案例。
- Minification – 會(huì)刪除掉所有的空格、換行符、以及注釋。
- Uglification – 會(huì)對(duì)所有變量進(jìn)行混淆和簡(jiǎn)化。
示例,原代碼:
- var organizationname = “xyz”
- var bigArray = [1,2,3,4,5,6]
- //write some code
- for(var index = 0; index < 6; index++){
- console.log(bigArray[index]);
- }
Minification之后:
- var organizationname = “xyz”, bigArray = [1,2,3,4,5,6] for(var index = 0; index < 6; index++) console.log(bigArray[index]);
Uglification之后:
- for(var o=”myname”,a=[1,2,3,4,5,6],e=0;e<6;e++)console.log(a[e])
網(wǎng)上有不少的文章都提到:Lambda的執(zhí)行環(huán)境已經(jīng)具有了適用于Nodejs和Python的AWS SDK。因此,我們不必在依賴項(xiàng)中添加它們。此特性雖然有利于提高性能,但是潛藏著一個(gè)問題:該SDK庫將定期使用***的修補(bǔ)程序來進(jìn)行升級(jí),為了不影響Lambda的各種行為,您***采用自己的依賴項(xiàng)管理方式。
安全性
- 為每個(gè)函數(shù)分配一個(gè)IAM角色。即使有多個(gè)函數(shù)需要相同的IAM策略,單個(gè)IAM角色也應(yīng)該只映射一個(gè)函數(shù)。當(dāng)特定的函數(shù)安全策略需要加固時(shí),這將有助于保持最小權(quán)限的策略。
- 由于Lambda會(huì)在共享的VPC上運(yùn)行,因此將AWS的憑據(jù)保留在代碼中并不可取。
- 在大多數(shù)情況下,IAM的執(zhí)行角色已足以通過使用AWS SDK,去連接到AWS的各種服務(wù)。
- 如果函數(shù)需要調(diào)用跨帳戶的服務(wù),則可能會(huì)使用到不同的憑據(jù)。因此我們需要在AWS的Security Token Service(請(qǐng)參見https://docs.aws.amazon.com/STS/latest/APIReference/Welcome.html)中使用Assume Role API,并檢索各種臨時(shí)憑據(jù)。
- 如果函數(shù)需要存儲(chǔ)長期憑據(jù)(如DB憑據(jù)、訪問密鑰),請(qǐng)使用帶有加密助手或AWS System Manager的環(huán)境變量。
可測(cè)性
由于AWS Lambda讓用戶的代碼運(yùn)行在云端,那么我們?cè)撊绾卧诒镜剡M(jìn)行測(cè)試呢?
雖然Lambda并不提供任何直接測(cè)試的URL,但是我們可以根據(jù)要啟動(dòng)的事件源系統(tǒng)來開展測(cè)試。
- 我們可以使用AWS SAM(請(qǐng)參見https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html)進(jìn)行Lambda函數(shù)的本地測(cè)試。它為CLI提供了類似本地Lambda的執(zhí)行環(huán)境。我們可以獲得API Gateway的localhost URL,它會(huì)在本地調(diào)用Lambda函數(shù)。
- 我們可以使用localstack(請(qǐng)參見https://github.com/localstack/localstack)開源項(xiàng)目,來創(chuàng)建具有大量AWS資源/服務(wù)的本地環(huán)境。它可以與其他AWS服務(wù)一起運(yùn)行Lambda。而且由于它能夠以API的形式提供所有的服務(wù),并能夠在后端作為Docker容器運(yùn)行,因此您也可以將AWS SAM與localstack相集成。
- 將業(yè)務(wù)邏輯放到Lambda Handler之外。Handler函數(shù)應(yīng)當(dāng)僅用于檢索各項(xiàng)輸入,然后將它們傳遞給其他函數(shù)/方法。這些函數(shù)/方法會(huì)將它們解析成為與我們的應(yīng)用程序相關(guān)的變量,然后進(jìn)一步使用。該過程不但實(shí)現(xiàn)了將業(yè)務(wù)邏輯與處理程序相分離,而且可以在我們創(chuàng)建的對(duì)象和函數(shù)的上下文中進(jìn)行測(cè)試。
藍(lán)/綠部署
通過Lambda附帶的Versioning和Alias功能,我們可以發(fā)布一個(gè)函數(shù)的多個(gè)版本。同時(shí),我們可以在單獨(dú)的容器中并行地調(diào)用每個(gè)版本。默認(rèn)情況下,版本特征是由$LATEST來表示的。在開發(fā)的過程中,我們可以使用這些版本,來創(chuàng)建諸如dev/UAT等多個(gè)環(huán)境。但是,由于我們?cè)诿恳淮紊蟼餍碌拇a時(shí),版本都會(huì)遞增,而客戶則會(huì)被指向***的版本。因此,我們***不要直接將Versioning用于生產(chǎn)環(huán)境,而可以用到Alias。
Alias可以指向函數(shù)的某個(gè)特定版本。因此,如果您的代碼發(fā)生了更改,并且發(fā)布了更新的版本時(shí),事件源仍將指向原來相同的Alias。我們只需管理好Alias何時(shí)需要被指向新的版本便可。這便實(shí)現(xiàn)了藍(lán)/綠部署。我們可以使用一些樣本事件來測(cè)試新的版本,確認(rèn)其工作正常之后,再通過修改Alias的指向,來切換訪問的流量。與此同時(shí),如果發(fā)現(xiàn)出現(xiàn)任何問題,我們還可以迅速回滾到原始的版本上。
監(jiān)控
個(gè)人以為:CloudWatch能夠很好地與Lambda配合使用,并為用戶提供Lambda執(zhí)行的各種詳細(xì)信息。Lambda能夠自動(dòng)跟蹤請(qǐng)求數(shù)、每個(gè)請(qǐng)求的執(zhí)行時(shí)間、導(dǎo)致錯(cuò)誤的請(qǐng)求數(shù)、以及發(fā)布相關(guān)的CloudWatch指標(biāo)。同時(shí),您也可以利用這些指標(biāo),來自定義各種CloudWatch的警報(bào)功能。
另外,我們還可以使用X-Ray來識(shí)別Lambda執(zhí)行中的各種潛在瓶頸。它對(duì)于我們?cè)噲D可視化那些耗費(fèi)在函數(shù)執(zhí)行上的時(shí)間,是非常實(shí)用的。而且,X-Ray還有助于跟蹤那些與整個(gè)流程相連接的所有下游系統(tǒng)。
其他建議
- 請(qǐng)勿使用AWS Lambda Console開發(fā)那些直接被用在生產(chǎn)環(huán)境中的代碼。
- 由于代碼版本控制并非自動(dòng)生效的,因此如果您誤點(diǎn)了Save按鈕,那么生產(chǎn)環(huán)境中的工作代碼就會(huì)被***覆蓋掉。
- 它并沒有與GitHub、或其他代碼存儲(chǔ)庫相集成。
- 它無法被導(dǎo)入AWS SDK之外的模塊中。因此,如果您需要某種特定的庫,則必須從一開始就在本地開發(fā)自己的函數(shù)、創(chuàng)建.zip文件、然后將其上傳到AWS Lambda中。
- 請(qǐng)使用AWS SAM或無服務(wù)器框架來進(jìn)行開發(fā)。
- 就Lambda部署的CI/CD計(jì)劃而言,它其實(shí)與其他可交付式的計(jì)劃并無不同。
- 可使用各種環(huán)境變量(Environment Variables)和參數(shù)存儲(chǔ)(Parameter Store),來將代碼與配置相分離。
總結(jié)
在本文中,我們討論了在設(shè)計(jì)和部署Lambda時(shí),各種值得參考和使用的***實(shí)踐。我們可以根據(jù)實(shí)際應(yīng)用的編碼語言和用例,來不斷改進(jìn)業(yè)務(wù)系統(tǒng)的性能。當(dāng)然,我們也可以在其他的云平臺(tái),以及Kubernetes的無服務(wù)器平臺(tái)中借鑒這些***實(shí)踐。希望您能夠?qū)⑦@些實(shí)踐總結(jié)運(yùn)用到自己成熟的生產(chǎn)環(huán)境與應(yīng)用之中。
原文標(biāo)題:AWS Lambda Best Practices,作者:Rajesh Bhojwani
【51CTO譯稿,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文譯者和出處為51CTO.com】