天天看點

Serverless 掀起新的前端技術變革

作者:蔣航,阿裡雲前端工程師

前端開發模式的演進

首先回顧一下前端開發模式的演進,我覺得主要有四個階段。

基于模闆渲染的動态頁面

在早起的網際網路時代,我們的網頁很簡單,就是一些靜态或動态的頁面,主要目的是用來做資訊的展示和傳播。這個時候開發一個網頁也很容易,主要就是通過 JSP、PHP 等技術寫一些動态模闆,然後通過 Web Server 将模闆解析成一個個 HTML 檔案,浏覽器隻負責渲染這些 HTML 檔案。這個階段還沒有前後端的分工,通常是後端工程師順便寫了前端頁面。

基于 AJAX 的前後端分離

2005 年 AJAX 技術的正式提出,翻開了 Web 開發的新篇章。基于 AJAX,我們可以把 Web 分為前端和後端,前端負責界面和互動,後端負責業務邏輯的處理。前後端通過接口進行資料互動。我們也不再需要在各個後端語言裡面寫着難以維護的 HTML。網頁的複雜度也由後端的 Web Server 轉向了浏覽器端的 JavaScript。也正因如此,開始有了前端工程師這個職位。

基于 Node.js 的前端工程化

2009 年 Node.js 的出現,對于前端工程師來說,也是一個曆史性的時刻。随着 Node.js 一同出現的還有 CommonJS 規範和 npm 包管理機制。随後也出現了 Grunt、Gulp、Webpack 等一系列基于 Node.js 的前端開發建構工具。

在 2013 年前後,前端三大架構 React.js/Angular/Vue.js 相繼釋出第一個版本。我們可以從以往基于一個個頁面的開發,變為基于一個個元件進行開發。

開發完成後使用 webpack 等工具進行打包建構,并通過基于 Node.js 實作的指令行工具将建構結果釋出上線。前端開發開始變得規範化、标準化、工程化。

基于 Node.js 的全棧開發

Node.js 對前端的重要意義還有,以往隻能運作在浏覽器中的 JavaScript 也可以運作在伺服器上,前端工程師可以用自己最熟悉的語言來寫服務端的代碼。于是前端工程師開始使用 Node.js 做全棧開發,開始由前端工程師向全棧工程師的方向轉變。這是前端主動突破自己的邊界。

另一方面,前端在發展,後端也在發展。也差不多在 Node.js 誕生那個時代,後端普遍開始由巨石應用模式由微服務架構轉變。這也就導緻以往的前後端分工出現了分歧。随着微服務架構的興起,後端的接口漸漸變得原子性,微服務的接口也不再直接面向頁面,前端的調用變得複雜了。

于是 BFF(Backend For Frontend)架構應運而生,在微服務和前端中間,加了一個 BFF 層,由 BFF 對接口進行聚合、裁剪後,再輸出給前端。而 BFF 這層不是後端本質工作,且距離前端最近和前端關系最大,是以前端工程師自然而然選擇了 Node.js 來實作。這也是目前 Node.js 在服務端較為廣泛的應用。

小結

可以看到,每一次前端開發模式的變化,都因某個變革性的技術而起。先是 AJAX,而後是 Node.js。那麼下一個變革性的技術是什麼?不言而喻,就是 Serverless。

Serverless 服務中的前端解決方案

Serverless 簡介

根據 CNCF 的定義,Serverless 是指建構和運作不需要伺服器管理的應用程式的概念。

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。

Serverless 掀起新的前端技術變革

簡單來講,FaaS(Function as a Service) 就是一些運作函數的平台,比如阿裡雲的函數計算、AWS 的 Lambda 等。BaaS(Backend as a Service)則是一些後端雲服務,比如雲資料庫、對象存儲、消息隊列等。利用 BaaS,可以極大簡化我們的應用開發難度。

Serverless 則可以了解為運作在 FaaS 中,使用了 BaaS 的函數。

Serverless 的主要特點有:

  • 事件驅動:函數在 FaaS 平台中,需要通過一系列的事件來驅動函數執行。
  • 無狀态:因為每次函數執行,可能使用的都是不同的容器,無法進行記憶體或資料共享。如果要共享資料,則隻能通過第三方服務,比如 Redis 等。
  • 無運維:使用 Serverless 我們不需要關心伺服器,不需要關心運維。這也是 Serverless 思想的核心。
  • 低成本:使用 Serverless 成本很低,因為我們隻需要為每次函數的運作付費。函數不運作,則不花錢,也不會浪費伺服器資源

Serverless 服務中的前端解決方案架構圖

Serverless 掀起新的前端技術變革

上圖是目前主要的一些 Serverless 服務,以及對應的前端解決方案。

從下往上,分别是基礎設施和開發工具。

基礎設施主要是一些雲計算廠商提供,包括雲計算平台和各種 BaaS 服務,以及運作函數的 FaaS 平台。

前端主要是 Serverless 的使用者,是以對前端來說,最重要的開發工具這一層,我們需要依賴開發工具進行 Serverless 開發、調試和部署。

架構

常見的 Serverless 架構有 Serverless Framework、ZEIT Now、Apex 等。不過這些基本都是國外公司做的,國内還沒有這樣的平台。

而且如今還沒有一個統一的 Serverless 标準,不同雲計算平台提供的 Serverless 服務很可能是不一樣的,這就導緻我們的代碼,無法平滑遷移。Serverless 架構一個主要功能是簡化 Serverless 開發、部署流程,另一主要功能則是屏蔽不同 Serverless 服務中的差異,讓我們的函數能夠在不改動或者隻改動很小一部分的情況下,在其他 Serverless 服務中也能運作。

Web IDE

和 Serverless 緊密相關的 Web IDE 主要也是各個雲計算平台的 Web IDE。利用 Web IDE,我們可以很友善地在雲端開發、調試函數,并且可以直接部署到對應的 FaaS 平台。這樣的好處是避免了在本地安裝各種開發工具、配置各種環境。常見的 Web IDE 有 AWS 的 Cloud9、阿裡雲的函數計算 Web IDE、騰訊雲的 Cloud Studio。從體驗上來說,AWS Cloud9 最好。

指令行工具

當然,目前最主要的開發方式還是在本地進行開發。是以在本地開發 Serverless 的指令行工具也必不可少。

指令行工具主要有兩類:

  1. 由雲計算平台提供的,如 AWS 的 aws、 Azure 的 az、阿裡雲的 fun;
  2. 由 Serverless 架構提供的,如 serverless、now。

大部分工具如 serverless、fun 等,都是用 Node.js 實作的。

下面是幾個指令行工具的例子。

  • 建立

複制代碼

col 1col 2# serverless$ serverless create --template aws-nodejs --path myService# fun$ fun init -n qcondemo helloworld-nodejs8           
  • 部署
col 1col 2# serverless$ serverless deploy# fun$ fun deploy           
  • 調試
col 1col 2# serverless$ serverless invoke [local] --function functionName# fun$ fun local invoke functionName           

應用場景

在開發工具上面一層,則是 Serverless 的一些垂直應用場景。除了使用傳統的服務端開發,目前使用 Serverless 技術的還有小程式開發,未來可能還會設計物聯網領域(IoT)。

不同 Serverless 服務的對比

Serverless 掀起新的前端技術變革

上圖從支援語言、觸發器、價格等多個方面對不同 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 去寫後端,最好也需要具備一定的後端知識。涉及複雜的後端系統或者 Serverless 不适用的場景,還是需要後端開發,後端變得更靠後了。

基于 Serverless 的 BFF

一方面,對不同的裝置需要使用不同的 API,另一方面,由于微服務導緻前端接口調用的複雜,是以前端工程師開始使用 BFF 的方式,對接口進行聚合裁剪,以得到适用于前端的接口。

下面是一個通用的 BFF 架構。

Serverless 掀起新的前端技術變革

圖檔來源:

https://www.thoughtworks.com/insights/blog/bff-soundcloud

最底層的就是各種後端微服務,最上層就是各種前端應用。在微服務和應用之前,就是通常由前端工程師開發的 BFF。這樣的架構解決了接口協調的問題,但也帶來了一些新的問題。比如針對每個裝置開發一個 BFF 應用,也會面臨一些重複開發的問題。而且以往前端隻需要開發頁面,關注于浏覽器端的渲染即可,現在卻需要維護各種 BFF 應用。以往前端也不需要關心并發,現在并發壓力卻集中到了 BFF 上。總的來說運維成本非常高,通常前端并不擅長運維。

Serverless 則可以幫我們很好的解決這些問題。基于 Serverless,我們可以使用一個個函數來實各個接口的聚合裁剪。前端向 BFF 發起的請求,就相當于是 FaaS 的一個 HTTP 觸發器,觸發一個函數的執行,這個函數中來實作針對該請求的業務邏輯,比如調用多個微服務擷取資料,然後再将處理結果傳回給前端。這樣運維的壓力,就由以往的 BFF Server 轉向了 FaaS 服務,前端再也不用關心伺服器了。

Serverless 掀起新的前端技術變革

上圖則是基于 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 平台,前端做服務端渲染,就不用再關心服務端程式的運維部署了。

Serverless 掀起新的前端技術變革

ZEIT] 的 Next.js 就對基于 Serverless 的服務端渲染做了很好的實作。下面就是一個簡單的例子。

代碼結構如下:

col 1col 2├── next.config.js├── now.json├── package.json└── pages    ├── about.js    └── index.js// next.config.jsmodule.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 給開發者,開發者可以像調用函數一樣使用各種後端服務。應用的運維也轉移到了提供雲開發的服務商。

Serverless 掀起新的前端技術變革

下面分别是使用 [支付寶雲開發 Basement 的一些例子,函數就是定義在 FaaS 服務中的函數。

  • 操作資料庫
col 1col 2// basement 是一個全局變量// 操作資料庫basement.db.collection('users')    .insertOne({        name: 'node',        age: 18,    })    .then(() => {        resolve({ success: true });    })    .catch(err => {        reject({ success: false });    });           
  • 上傳圖檔
col 1col 2// 上傳圖檔basement.file    .uploadFile(options)    .then((image) => {        this.setData({            iconUrl: image.fileUrl,        });    })    .catch(console.error);           
  • 調用函數
col 1col 2// 調用函數basement.function    .invoke('getUserInfo')    .then((res) => {         this.setData({             user: res.result        });    })    .catch(console.error}           

通用 Serverless 架構

基于上述幾個 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 的函數測試同樣适用。

Serverless 掀起新的前端技術變革

圖檔來源:

https://martinfowler.com/bliki/TestPyramid.html

為了能更簡單對函數進行單元測試,我們需要做的就是将業務邏輯和函數依賴的 FaaS(如函數計算) 和 BaaS (如雲資料庫)分離。當 FaaS 和 BaaS 分離出去之後,我們就可以像編寫傳統的單元測試一樣,對函數的業務邏輯進行測試。然後再編寫內建測試,驗證函數和其他服務的內建是否正常工作。

一個糟糕的例子

下面是一個使用 Node.js 實作的函數的例子。該函數做的事情就是,首先将使用者資訊存儲到資料庫中,然後給使用者發送郵件。

col 1col 2const 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 分離的原則,對上面的代碼進行重構。

col 1col 2class 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 的例子。

col 1col 2// 模拟 mailerconst mailer = {  sendWelcomeEmail: (email) => {    console.log(`Send email to ${email} success!`);  },};           

這樣隻要對 Users 進行充分的單元測試,就能確定業務代碼如期運作。然後再傳入真實的 db 和 mailer,進行簡單的內建測試,就能知道整個函數是否能夠正常工作。

重構後的代碼還有一個好處是友善函數的遷移。當我們想要把函數從一個平台遷移到另一個平台的時候,隻需要根據不同平台提供的參數,修改一下 Users 的調用方式就可以了,而不用再去修改業務邏輯。

綜上所述,對函數進行測試,就需要牢記金字塔原則,并遵循以下原則:

  • 将業務邏輯和函數依賴的 FaaS 和 BaaS 分離
  • 對業務邏輯進行充分的單元測試
  • 将函數進行內建測試驗證代碼是否正常工作。

函數的性能

使用 Serverless 進行開發,還有一個大家都關心的問題就是函數的性能怎麼樣。

對于傳統的應用,我們的程式啟動起來之後,就常駐在記憶體中;而 Serverless 函數則不是這樣。當驅動函數執行的事件到來的時候,首先需要下載下傳代碼,然後啟動一個容器,在容器裡面再啟動一個運作環境,最後才是執行代碼。前幾步統稱為冷啟動(Cold Start)。傳統的應用沒有冷啟動的過程。

下面是函數生命周期的示意圖:

Serverless 掀起新的前端技術變革
https://www.youtube.com/watch?v=oQFORsso2go&feature=youtu.be&t=8m5s

冷啟動時間的長短,就是函數性能的關鍵因素。優化函數的性能,也就需要從函數生命周期的各個階段去優化。

不同程式設計語言對冷啟動時間的影響

在此之前,已經有很多人測試過不同程式設計語言對冷啟動時間的影響,比如:

  • 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
Serverless 掀起新的前端技術變革

圖檔來源: Cold start / Warm start with AWS Lambda

從這些測試中能夠得到一些統一的結論:

  • 增加函數的記憶體
  • 可以減少冷啟動時間
  • C#、Java 等程式設計語言的能啟動時間大約是 Node.js、Python 的 100 倍

基于上述結論,如果想要 Java 的冷啟動時間達到 Node.js 那麼小,可以為 Java 配置設定更大的記憶體。但更大的記憶體意味着更多的成本。

函數冷啟動的時機

剛開始接觸 Serverless 的開發者可能有一個誤區,就是每次函數執行,都需要冷啟動。其實并不是這樣。

當第一次請求(驅動函數執行的事件)來臨,成功啟動運作環境并執行函數之後,運作環境會保留一段時間,以便用于下一次函數執行。這樣就能減少冷啟動的次數,進而縮短函數運作時間。當請求達到一個運作環境的限制時,FaaS 平台會自動擴充下一個運作環境。

Serverless 掀起新的前端技術變革
以 AWS Lambda 為例,在執行函數之後,Lambda 會保持執行上下文一段時間,預期用于另一次 Lambda 函數調用。其效果是,服務在 Lambda 函數完成後當機執行上下文,如果再次調用 Lambda 函數時 AWS Lambda 選擇重用上下文,則解凍上下文供重用。

下面以兩個小測試來說明上述内容。

我使用阿裡雲的函數計算實作了一個 Serverless 函數,并通過 HTTP 事件來驅動。然後使用不同并發數向函數發起 100 個請求。

首先是一個并發的情況:

Serverless 掀起新的前端技術變革

可以看到第一個請求時間為 302ms,其他請求時間基本都在 50ms 左右。基本就能确定,第一個請求對應的函數是冷啟動,剩餘 99 個請求,都是熱啟動,直接重複利用了第一個請求的運作環境。

接下來是并發數為 10 的情況:

Serverless 掀起新的前端技術變革

可以發現,前 10 個請求,耗時基本在 200ms-300ms,其餘請求耗時在 50ms 左右。于是可以得出結論,前 10 個并發請求都是冷啟動,同時啟動了 10 個運作環境;後面 90 個請求都是熱啟動。

這也就印證了之前的結論,函數不是每次都冷啟動,而是會在一定時間内複用之前的運作環境。

執行上下文重用

上面的結論對我們提高函數性能有什麼幫助呢?當然是有的。既然運作環境能夠保留,那就意味着我們能對運作環境中的執行上下文進行重複利用。

來看一個例子:

col 1col 2const mysql = require('mysql'); module.exports.saveUser = (event, context, callback) => {     // 初始化資料庫連接配接    const connection = mysql.createConnection({ */* ... */* });    connection.connect();     connection.query('...'); };           

上面例子實作的功能就是在 saveUser 函數中初始化一個資料庫連接配接。這樣的問題就是,每次函數執行的時候,都會重新初始化資料庫連接配接,而連接配接資料庫又是一個比較耗時的操作。顯然這樣對函數的性能是沒有好處的。

既然在短時間内,函數的執行上下文可以重複利用,那麼我們就可以将資料庫連接配接放在函數之外:

col 1col 2const 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。

作者首發于個人知乎專欄:

https://zhuanlan.zhihu.com/p/65914436

繼續閱讀