最近關於 Serverless 的討論愈來愈多。看似與前端關係不大的 Serverless,其實早已和前端有了頗深淵源,而且將掀起新的前端技術變革。本文主要就根據我的理解和總結,從前端開發模式的演進、基於 Serverless 的前端開發案例以及 Serverless 開發最佳實踐等方面,與你們探討 Serverless 中的前端開發模式。本人也有幸在 QCon2019 分享了這一主題。html
前端開發模式的演進
首先回顧一下前端開發模式的演進,我以爲主要有四個階段。前端
- 基於模板渲染的動態頁面
- 基於 AJAX 的先後端分離
- 基於 Node.js 的前端工程化
- 基於 Node.js 的全棧開發
基於模板渲染的動態頁面
在早起的互聯網時代,咱們的網頁很簡單,就是一些靜態或動態的頁面,主要目的是用來作信息的展現和傳播。這個時候開發一個網頁也很容易,主要就是經過 JSP、PHP 等技術寫一些動態模板,而後經過 Web Server 將模板解析成一個個 HTML 文件,瀏覽器只負責渲染這些 HTML 文件。這個階段尚未先後端的分工,一般是後端工程師順便寫了前端頁面。node
基於 AJAX 的先後端分離
2005 年 AJAX 技術的正式提出,翻開了 Web 開發的新篇章。基於 AJAX,咱們能夠把 Web 分爲前端和後端,前端負責界面和交互,後端負責業務邏輯的處理。先後端經過接口進行數據交互。咱們也再也不須要在各個後端語言裏面寫着難以維護的 HTML。網頁的複雜度也由後端的 Web Server 轉向了瀏覽器端的 JavaScript。也正因如此,開始有了前端工程師這個職位。mysql
基於 Node.js 的前端工程化
2009年 Node.js 的出現,對於前端工程師來講,也是一個歷史性的時刻。隨着 Node.js 一同出現的還有 CommonJS 規範和 npm 包管理機制。隨後也出現了 Grunt、Gulp、Webpack 等一系列基於 Node.js 的前端開發構建工具。webpack
在 2013 年先後,前端三大框架 React.js/Angular/Vue.js 相繼發佈第一個版本。咱們能夠從以往基於一個個頁面的開發,變爲基於一個個組件進行開發。開發完成後使用 webpack 等工具進行打包構建,並經過基於 Node.js 實現的命令行工具將構建結果發佈上線。前端開發開始變得規範化、標準化、工程化。git
基於 Node.js 的全棧開發
Node.js 對前端的重要意義還有,以往只能運行在瀏覽器中的 JavaScript 也能夠運行在服務器上,前端工程師能夠用本身最熟悉的語言來寫服務端的代碼。因而前端工程師開始使用 Node.js 作全棧開發,開始由前端工程師向全棧工程師的方向轉變。這是前端主動突破本身的邊界。github
另外一方面,前端在發展,後端也在發展。也差很少在 Node.js 誕生那個時代,後端廣泛開始由巨石應用模式由微服務架構轉變。這也就致使以往的先後端分工出現了分歧。隨着微服務架構的興起,後端的接口漸漸變得原子性,微服務的接口也再也不直接面向頁面,前端的調用變得複雜了。因而 BFF(Backend For Frontend)架構應運而生,在微服務和前端中間,加了一個 BFF 層,由 BFF 對接口進行聚合、裁剪後,再輸出給前端。而 BFF 這層不是後端本質工做,且距離前端最近和前端關係最大,因此前端工程師天然而然選擇了 Node.js 來實現。這也是當前 Node.js 在服務端較爲普遍的應用。web
下一代前端開發模式
能夠看到,每一次前端開發模式的變化,都因某個變革性的技術而起。先是 AJAX,然後是 Node.js。那麼下一個變革性的技術是什麼?不言而喻,就是 Serverless。sql
Serverless 服務中的前端解決方案
Serverless 簡介
根據 CNCF 的定義,Serverless 是指構建和運行不須要服務器管理的應用程序的概念。(serverless-overview)shell
Serverless computing refers to the concept of building and running applications that do not require server management. --- CNCF
其實 Serverless 早已和前端產生了聯繫,只是咱們可能沒有感知。好比 CDN,咱們把靜態資源發佈到 CDN 以後,就不須要關心 CDN 有多少個節點、節點如何分佈,也不須要關心它如何作負載均衡、如何實現網絡加速,因此 CDN 對前端來講是 Serverless。再好比對象存儲,和 CDN 同樣,咱們只須要將文件上傳到對象存儲,就能夠直接使用了,不須要關心它如何存取文件、如何進行權限控制,因此對象存儲對前端工程師來講是 Serverless。甚至一些第三方的 API 服務,也是 Serverless,由於咱們使用的時候,不須要去關心服務器。
固然,有了體感還不夠,咱們仍是須要一個更精確的定義。從技術角度來講,Serverless 就是 FaaS 和 BaaS 的結合。
Serverless = FaaS + BaaS。
![]()
簡單來說,FaaS(Function as a Service) 就是一些運行函數的平臺,好比阿里雲的函數計算、AWS 的 Lambda 等。
BaaS(Backend as a Service)則是一些後端雲服務,好比雲數據庫、對象存儲、消息隊列等。利用 BaaS,能夠極大簡化咱們的應用開發難度。
Serverless 則能夠理解爲運行在 FaaS 中的,使用了 BaaS 的函數。
Serverless 的主要特色有:
事件驅動
- 函數在 FaaS 平臺中,須要經過一系列的事件來驅動函數執行。
無狀態
- 由於每次函數執行,可能使用的都是不一樣的容器,沒法進行內存或數據共享。若是要共享數據,則只能經過第三方服務,好比 Redis 等。
無運維
- 使用 Serverless 咱們不須要關心服務器,不須要關心運維。這也是 Serverless 思想的核心。
低成本
- 使用 Serverless 成本很低,由於咱們只須要爲每次函數的運行付費。函數不運行,則不花錢,也不會浪費服務器資源
Serverless 服務中的前端解決方案架構圖
![]()
上圖是當前主要的一些 Serverless 服務,以及對應的前端解決方案。
從下往上,分別是基礎設施和開發工具。
基礎設施主要是一些雲計算廠商提供,包括雲計算平臺和各類 BaaS 服務,以及運行函數的 FaaS 平臺。
前端主要是 Serverless 的使用者,因此對前端來講,最重要的開發工具這一層,咱們須要依賴開發工具進行 Serverless 開發、調試和部署。
框架(Framework)
現在尚未一個統一的 Serverless 標準,不一樣雲計算平臺提供的 Serverless 服務極可能是不同的,這就致使咱們的代碼,沒法平滑遷移。Serverless 框架一個主要功能是簡化 Serverless 開發、部署流程,另外一主要功能則是屏蔽不一樣 Serverless 服務中的差別,讓咱們的函數可以在不改動或者只改動很小一部分的狀況下,在其餘 Serverless 服務中也能運行。
常見的 Serverless 框架有 Serverless Framework、ZEIT Now、Apex 等。不過這些基本都是國外公司作的,國內尚未這樣的平臺。
Web IDE
和 Serverless 緊密相關的 Web IDE 主要也是各個雲計算平臺的 Web IDE。利用 Web IDE,咱們能夠很方便地在雲端開發、調試函數,而且能夠直接部署到對應的 FaaS 平臺。這樣的好處是避免了在本地安裝各類開發工具、配置各類環境。常見的 Web IDE 有 AWS 的 Cloud九、阿里雲的函數計算 Web IDE、騰訊雲的 Cloud Studio。從體驗上來講,AWS Cloud9 最好。
命令行工具
固然,目前最主要的開發方式仍是在本地進行開發。因此在本地開發 Serverless 的命令行工具也必不可少。
命令行工具主要有兩類,一類是雲計算平臺提供的,如 AWS 的
aws
、 Azure 的az
、阿里雲的fun
;還有一類是 Serverless 框架提供的,如serverless
、now
。大部分工具如
serverless
、fun
等,都是用 Node.js 實現的。下面是幾個命令行工具的例子。
建立
# serverless $ serverless create --template aws-nodejs --path myService # fun $ fun init -n qcondemo helloworld-nodejs8 複製代碼
部署
# serverless $ serverless deploy # fun $ fun deploy 複製代碼
調試
# serverless $ serverless invoke [local] --function functionName # fun $ fun local invoke functionName 複製代碼
應用場景
在開發工具上面一層,則是 Serverless 的一些垂直應用場景。除了使用傳統的服務端開發,目前使用 Serverless 技術的還有小程序開發,將來可能還會設計物聯網領域(IoT)。
不一樣 Serverless 服務的對比
[圖片上傳失敗...(image-6ad507-1565936084100)]
上圖從支持語言、觸發器、價格等多個方面對不一樣 Serverless 服務進行了對比,能夠發現有差別,也有共性。
好比幾乎全部 Serverless 服務都支持 Node.js/Python/Java 等語言。
從支持的觸發器來看,幾乎全部服務也都支持 HTTP、對象存儲、定時任務、消息隊列等觸發器。固然,這些觸發器也與平臺本身的後端服務相關,好比阿里雲的對象存儲觸發器,是基於阿里雲的 OSS 產品的存取等事件觸發的;而 AWS 的對象存儲觸發器,則是基於 AWS 的 S3 的事件觸發的,兩個平臺並不通用。這也是當前 Serverless 面臨的一個問題,就是標準不統一。
從計費的角度來看,各個平臺的費用基本一致。在前面也提到,Serverless 的計費是按調用次數計費。對於各個 Serverless,每月都有 100 萬次的免費調用次數,以後差很少 ¥1.3/百萬次;以及 400,000 GB-s 的免費執行時間,以後 ¥0.0001108/GB-s。因此在應用體量較小的時候,使用 Serverless 是很是划算的。
基於 Serverless 的前端開發模式
在本章節,主要以幾個案例來講明基於 Serverless 的前端開發模式,以及它和以往的前端開發有什麼不同。
在開始具體的案例以前,先看一下傳統開發流程。
![]()
在傳統開發流程中,咱們須要前端工程師寫頁面,後端工程師寫接口。後端寫完接口以後,把接口部署了,再進行先後端聯調。聯調完畢後再測試、上線。上線以後,還須要運維工程師對系統進行維護。整個過程涉及多個不一樣角色,鏈路較長,溝通協調也是一個問題。
而基於 Serverless,後端變得很是簡單了,以往的後端應用被拆分爲一個個函數,只須要寫完函數並部署到 Serverless 服務便可,後續也不用關心任何服務器的運維操做。後端開發的門檻大幅度下降了。所以,只須要一個前端工程師就能夠完成全部的開發工做。
![]()
固然,前端工程師基於 Serverless 去寫後端,最好也須要具有必定的後端知識。涉及複雜的後端系統或者 Serverless 不適用的場景,仍是須要後端開發,後端變得更靠後了。
基於 Serverless 的 BFF
一方面,對不一樣的設備須要使用不一樣的 API,另外一方面,因爲微服務致使前端接口調用的複雜,因此前端工程師開始使用 BFF 的方式,對接口進行聚合裁剪,以獲得適用於前端的接口。
下面是一個通用的 BFF 架構。
[站外圖片上傳中...(image-42729a-1565936084100)] BFF @ SoundCloud
最底層的就是各類後端微服務,最上層就是各類前端應用。在微服務和應用以前,就是一般由前端工程師開發的 BFF。
這樣的架構解決了接口協調的問題,但也帶來了一些新的問題。
好比針對每一個設備開發一個 BFF 應用,也會面臨一些重複開發的問題。並且以往前端只須要開發頁面,關注於瀏覽器端的渲染便可,如今卻須要維護各類 BFF 應用。以往前端也不須要關心併發,如今併發壓力卻集中到了 BFF 上。總的來講運維成本很是高,一般前端並不擅長運維。
Serverless 則能夠幫咱們很好的解決這些問題。基於 Serverless,咱們可使用一個個函數來實各個接口的聚合裁剪。前端向 BFF 發起的請求,就至關因而 FaaS 的一個 HTTP 觸發器,觸發一個函數的執行,這個函數中來實現針對該請求的業務邏輯,好比調用多個微服務獲取數據,而後再將處理結果返回給前端。這樣運維的壓力,就由以往的 BFF Server 轉向了 FaaS 服務,前端不再用關心服務器了。
![]()
上圖則是基於 Serverless 的 BFF 架構。爲了更好的管理各類 API,咱們還能夠添加網關層,經過網關來管理全部 API(好比阿里雲的網關),好比對 API 進行分組、分環境。基於 API 網關,前端就不直接經過 HTTP 觸發器來執行函數,而是將請求發送至網關,再由網關去觸發具體的函數來執行。
基於 Serverless 的服務端渲染
基於當下最流行的三大前端框架(React.js/Anguler/Vue.js),如今的渲染方式大部分都是客戶端渲染。頁面初始化的時候,只加載一個簡單 HTML 以及對應的 JS 文件,再由 JS 來渲染出一個個頁面。這種方式最主要的問題就是白屏時間和 SEO。
爲了解決這個問題,前端又開始嘗試服務端渲染。本質思想其實和最先的模板渲染是同樣的。都是前端發起一個請求,後端 Server 解析出一個 HTML 文檔,而後再返回給瀏覽器。只不過以往是 JSP、PHP 等服務端語言的模板,如今是基於 React、Vue 等實現的同構應用,這也是現在的服務端渲染方案的優點。
但服務端渲染又爲前端帶來了一些額外的問題:運維成本,前端須要維護用於渲染的服務器。
Serverless 最大的優勢就是能夠幫咱們減小運維,那 Serverless 能不能用於服務端渲染呢?固然也是能夠的。
傳統的服務端渲染,每一個請求的 path 都對應着服務端的每一個路由,由該路由實現對應 path 的 HTML 文檔渲染。用於渲染的服務端程序,就是這些集成了這些路由的應用。
使用 Serverless 來作服務端渲染,就是將以往的每一個路由,都拆分爲一個個函數,再在 FaaS 上部署對應的函數。這樣用戶請求的 path,對應的就是每一個單獨的函數。經過這種方式,就將運維操做轉移到了 FaaS 平臺,前端作服務端渲染,就不用再關心服務端程序的運維部署了。
![]()
ZEIT 的 Next.js 就對基於 Serverless 的服務端渲染作了很好的實現。下面就是一個簡單的例子。
代碼結構以下:
. ├── next.config.js ├── now.json ├── package.json └── pages ├── about.js └── index.js 複製代碼
// next.config.js module.exports = { target: 'serverless' } 複製代碼
其中
pages/about.js
和pages/index.js
就是兩個頁面,在next.config.js
配置了使用 Zeit 提供的 Serverless 服務。而後使用
now
這個命令,就能夠將代碼以 Serverless 的方式部署。部署過程當中,pages/about.js
和pages/index.js
就分別轉換爲兩個函數,負責渲染對應的頁面。![]()
基於 Serverless 的小程序開發
目前國內使用 Serverless 較多的場景可能就是小程開發了。具體的實現就是小程序雲開發,支付寶小程序和微信小程序都提供了雲開發功能。
在傳統的小程序開發中,咱們須要前端工程師進行小程序端的開發;後端工程師進行服務端的開發。小程序的後端開發和其餘的後端應用開發,本質是是同樣的,須要關心應用的負載均衡、備份冗災、監控報警等一些列部署運維操做。若是開發團隊人不多,可能還須要前端工程師去實現服務端。
但基於雲開發,就只須要讓開發者關注於業務的實現,由一個前端工程師就可以完成整個應用的先後端開發。由於雲開發將後端封裝爲了 BaaS 服務,並提供了對應的 SDK 給開發者,開發者能夠像調用函數同樣使用各類後端服務。應用的運維也轉移到了提供雲開發的服務商。
![]()
下面分別是使用支付寶雲開發(Basement)的一些例子,函數就是定義在 FaaS 服務中的函數。
操做數據庫
// `basement` 是一個全局變量 // 操做數據庫 basement.db.collection('users') .insertOne({ name: 'node', age: 18, }) .then(() => { resolve({ success: true }); }) .catch(err => { reject({ success: false }); }); 複製代碼
上傳圖片
// 上傳圖片 basement.file .uploadFile(options) .then((image) => { this.setData({ iconUrl: image.fileUrl, }); }) .catch(console.error); 複製代碼
調用函數
// 調用函數 basement.function .invoke('getUserInfo') .then((res) => { this.setData({ user: res.result }); }) .catch(console.error} 複製代碼
通用 Serverless 架構
基於上述幾個 Serverless 開發的例子,就能夠總結出一個通用的 Serverless 架構。
![]()
其中最底層就是實現複雜業務的後端微服務(Backend)。而後 FaaS 層經過一系列函數實現業務邏輯,併爲前端直接提供服務。對於前端開發者來講,前端能夠經過編寫函數的方式來實現服務端的邏輯。對於後端開發者來講,後端變得更靠後了。若是業務比較較淡,FaaS 層可以實現,甚至也不須要微服務這一層了。
同時無論是在後端、FaaS 仍是前端,咱們均可以去調用雲計算平臺提供的 BaaS 服務,大大下降開發難度、減小開發成本。小程序雲開發,就是直接在前端調用 BaaS 服務的例子。
Serverless 開發最佳實踐
基於 Serverless 開發模式和傳統開發模式最大的不一樣,就是傳統開發中,咱們是基於應用的開發。開發完成後,咱們須要對應用進行單元測試和集成測試。而基於 Serverless,開發的是一個個函數,那麼咱們應該如何對 Serverless 函數進行測試?Serverless 函數的測試和普通的單元測試又有什麼區別?
還有一個很重要的點是,基於 Serverless 開發的應用性能如何?應該怎麼去提升 Serverless 應用的性能?
本章主要就介紹一下,基於 Serverless 的函數的測試和函數的性能兩個方面的最佳實踐。
函數的測試
雖然使用 Serverless 咱們能夠簡單地進行業務的開發,但它的特性也給咱們的測試帶來了一些挑戰。主要有如下幾個方面。
Serverless 函數是分佈式的,咱們不知道也無需知道函數是部署或運行在哪臺機器上,因此咱們須要對每一個函數進行單元測試。Serverless 應用是由一組函數組成的,函數內部可能依賴了一些別的後端服務(BaaS),因此咱們也須要對 Serverless 應用進行集成測試。
運行函數的 FaaS 和 BaaS 在本地也難以模擬。除此以外,不一樣平臺提供的 FaaS 環境可能不一致,不平臺提供的 BaaS 服務的 SDK 或接口也可能不一致,這不只給咱們的測試帶來了一些問題,也增長了應用遷移成本。
函數的執行是由事件驅動的,驅動函數執行的事件,在本地也難以模擬。
那麼如何解決這些問題呢?
根據 Mike Cohn 提出的測試金字塔,單元測試的成本最低,效率最高;UI 測試(集成)測試的成本最高,效率最低,因此咱們要儘量多的進行單元測試,從而減小集成測試。這對 Serverless 的函數測試一樣適用。
圖片來源: martinfowler.com/bliki/TestP… ![]()
爲了能更簡單對函數進行單元測試,咱們須要作的就是將業務邏輯和函數依賴的 FaaS(如函數計算) 和 BaaS (如雲數據庫)分離。當 FaaS 和 BaaS 分離出去以後,咱們就能夠像編寫傳統的單元測試同樣,對函數的業務邏輯進行測試。而後再編寫集成測試,驗證函數和其餘服務的集成是否正常工做。
一個糟糕的例子
下面是一個使用 Node.js 實現的函數的例子。該函數作的事情就是,首先將用戶信息存儲到數據庫中,而後給用戶發送郵件。
const db = require('db').connect(); const mailer = require('mailer'); module.exports.saveUser = (event, context, callback) => { const user = { email: event.email, created_at: Date.now() } db.saveUser(user, function (err) { if (err) { callback(err); } else { mailer.sendWelcomeEmail(event.email); callback(); } }); }; 複製代碼
這個例子主要存在兩個問題:
- 業務邏輯和 FaaS 耦合在一塊兒。主要就是業務邏輯都在
saveUser
這個函數裏,而saveUser
參數的event
和conent
對象,是 FaaS 平臺提供的。- 業務邏輯和 BaaS 耦合在一塊兒。具體來講,就是函數內使用了
db
和mailer
這兩個後端服務,測試函數必須依賴於db
和mailer
。編寫可測試的函數
基於將業務邏輯和函數依賴的 FaaS 和 BaaS 分離的原則,對上面的代碼進行重構。
class Users { constructor(db, mailer) { this.db = db; this.mailer = mailer; } save(email, callback) { const user = { email: email, created_at: Date.now() } this.db.saveUser(user, function (err) { if (err) { callback(err); } else { this.mailer.sendWelcomeEmail(email); callback(); } }); } } module.exports = Users; 複製代碼
const db = require('db').connect(); const mailer = require('mailer'); const Users = require('users'); let users = new Users(db, mailer); module.exports.saveUser = (event, context, callback) => { users.save(event.email, callback); }; 複製代碼
在重構後的代碼中,咱們將業務邏輯全都放在了
Users
這個類裏面,Users
不依賴任何外部服務。測試的時候,咱們也能夠不傳入真實的db
或mailer
,而是傳入模擬的服務。下面是一個模擬
mailer
的例子。// 模擬 mailer const mailer = { sendWelcomeEmail: (email) => { console.log(`Send email to ${email} success!`); }, }; 複製代碼
這樣只要對
Users
進行充分的單元測試,就能確保業務代碼如期運行。而後再傳入真實的
db
和mailer
,進行簡單的集成測試,就能知道整個函數是否可以正常工做。重構後的代碼還有一個好處是方便函數的遷移。當咱們想要把函數從一個平臺遷移到另外一個平臺的時候,只須要根據不一樣平臺提供的參數,修改一下
Users
的調用方式就能夠了,而不用再去修改業務邏輯。小結
綜上所述,對函數進行測試,就須要牢記金字塔原則,並遵循如下原則:
- 將業務邏輯和函數依賴的 FaaS 和 BaaS 分離
- 對業務邏輯進行充分的單元測試
- 將函數進行集成測試驗證代碼是否正常工做
函數的性能
使用 Serverless 進行開發,還有一個你們都關心的問題就是函數的性能怎麼樣。
對於傳統的應用,咱們的程序啓動起來以後,就常駐在內存中;而 Serverless 函數則不是這樣。
當驅動函數執行的事件到來的時候,首先須要下載代碼,而後啓動一個容器,在容器裏面再啓動一個運行環境,最後纔是執行代碼。前幾步統稱爲冷啓動(Cold Start)。傳統的應用沒有冷啓動的過程。
下面是函數生命週期的示意圖:
圖片來源: www.youtube.com/watch?v=oQF… ![]()
冷啓動時間的長短,就是函數性能的關鍵因素。優化函數的性能,也就須要從函數生命週期的各個階段去優化。
不一樣編程語言對冷啓動時間的影響
在此以前,已經有不少人測試過不一樣編程語言對冷啓動時間的影響,好比:
- Compare coldstart time with different languages, memory and code sizes -by Yan Cui
- Cold start / Warm start with AWS Lambda - by Erwan Alliaume
- Serverless: Cold Start War - by Mikhail Shilkov
圖片來源: Cold start / Warm start with AWS Lambda ![]()
從這些測試中可以獲得一些統一的結論:
- 增長函數的內存能夠減小冷啓動時間
- C#、Java 等編程語言的能啓動時間大約是 Node.js、Python 的 100 倍
基於上述結論,若是想要 Java 的冷啓動時間達到 Node.js 那麼小,能夠爲 Java 分配更大的內存。但更大的內存意味着更多的成本。
函數冷啓動的時機
剛開始接觸 Serverless 的開發者可能有一個誤區,就是每次函數執行,都須要冷啓動。其實並非這樣。
當第一次請求(驅動函數執行的事件)來臨,成功啓動運行環境並執行函數以後,運行環境會保留一段時間,以便用於下一次函數執行。這樣就能減小冷啓動的次數,從而縮短函數運行時間。當請求達到一個運行環境的限制時,FaaS 平臺會自動擴展下一個運行環境。
![]()
以 AWS Lambda 爲例,在執行函數以後,Lambda 會保持執行上下文一段時間,預期用於另外一次 Lambda 函數調用。其效果是,服務在 Lambda 函數完成後凍結執行上下文,若是再次調用 Lambda 函數時 AWS Lambda 選擇重用上下文,則解凍上下文供重用。
下面以兩個小測試來講明上述內容。
我使用阿里雲的函數計算實現了一個 Serverless 函數,並經過 HTTP 事件來驅動。而後使用不一樣併發數向函數發起 100 個請求。
首先是一個併發的狀況:
![]()
能夠看到第一個請求時間爲 302ms,其餘請求時間基本都在 50ms 左右。基本就能肯定,第一個請求對應的函數是冷啓動,剩餘 99 個請求,都是熱啓動,直接重複利用了第一個請求的運行環境。
接下來是併發數爲 10 的狀況:
![]()
能夠發現,前 10 個請求,耗時基本在 200ms-300ms,其他請求耗時在 50ms 左右。因而能夠得出結論,前 10 個併發請求都是冷啓動,同時啓動了 10 個運行環境;後面 90 個請求都是熱啓動。
這也就印證了以前的結論,函數不是每次都冷啓動,而是會在必定時間內複用以前的運行環境。
執行上下文重用
上面的結論對咱們提升函數性能有什麼幫助呢?固然是有的。既然運行環境可以保留,那就意味着咱們能對運行環境中的執行上下文進行重複利用。
來看一個例子:
const mysql = require('mysql'); module.exports.saveUser = (event, context, callback) => { // 初始化數據庫鏈接 const connection = mysql.createConnection({ /* ... */ }); connection.connect(); connection.query('...'); }; 複製代碼
上面例子實現的功能就是在
saveUser
函數中初始化一個數據庫鏈接。這樣的問題就是,每次函數執行的時候,都會從新初始化數據庫鏈接,而鏈接數據庫又是一個比較耗時的操做。顯然這樣對函數的性能是沒有好處的。既然在短期內,函數的執行上下文能夠重複利用,那麼咱們就能夠將數據庫鏈接放在函數以外:
const mysql = require('mysql'); // 初始化數據庫鏈接 const connection = mysql.createConnection({ /* ... */ }); connection.connect(); module.exports.saveUser = (event, context, callback) => { connection.query('...'); }; 複製代碼
這樣就只有第一次運行環境啓動的時候,纔會初始化數據庫鏈接。後續請求來臨、執行函數的時候,就能夠直接利用執行上下文中的
connection
,從而提後續高函數的性能。大部分狀況下,經過犧牲一個請求的性能,換取大部分請求的性能,是徹底能夠夠接受的。
給函數預熱
既然函數的運行環境會保留一段時間,那麼咱們也能夠經過主動調用函數的方式,隔一段時間就冷啓動一個運行環境,這樣就能使得其餘正常的請求都是熱啓動,從而避免冷啓動時間對函數性能的影響。
這是目前比較有效的方式,但也須要有一些注意的地方:
- 不要過於頻繁調用函數,至少頻率要大於 5 分鐘
- 直接調用函數,而不是經過網關等間接調用
- 建立專門處理這種預熱調用的函數,而不是正常業務函數
這種方案只是目前行之有效且比較黑科技的方案,可使用,但若是你的業務容許「犧牲第一個請求的性能換取大部分性能」,那也徹底沒必要使用該方案,
小結
整體而言,優化函數的性能就是優化冷啓動時間。上述方案都是開發者方面的優化,固然還一方面主要是 FaaS 平臺的性能優化。
總結一下上述方案,主要是如下幾點:
- 選用 Node.js / Python 等冷啓動時間短的編程語言
- 爲函數分配合適的運行內存
- 執行上下文重用
- 爲函數預熱
總結
做爲前端工程師,咱們一直在探討前端的邊界是什麼。如今的前端開發早已不是以往的前端開發,前端不只能夠作網頁,還能夠作小程序,作 APP,作桌面程序,甚至作服務端。而前端之因此在不斷拓展本身的邊界、不斷探索更多的領域,則是但願本身可以產生更大的價值。最好是用咱們熟悉的工具、熟悉的方式來創造價值。
而 Serverless 架構的誕生,則能夠最大程度幫助前端工程師實現本身的理想。使用 Serverless,咱們不須要再過多關注服務端的運維,不須要關心咱們不熟悉的領域,咱們只須要專一於業務的開發、專一於產品的實現。咱們須要關心的事情變少了,但咱們能作的事情更多了。
Serverless 也必將對前端的開發模式產生巨大的變革,前端工程師的職能也將再度迴歸到應用工程師的職能。
若是要用一句話來總結 Serverless,那就是 Less is More。
原文轉自nodejh@(Serverless 掀起新的前端技術變革) 文章寫的很是好!