這是第 76 篇不摻水的原創,想獲取更多原創好文,請搜索公衆號關注咱們吧~ 本文首發於政採雲前端博客: 結合阿里雲 FC 談談我對 FaaS 的理解
進入主題以前,先從背景簡述下最近前端界的熱點詞彙 Serverless,其實,Serverless 這個概念在其餘領域已經提出來好久了。javascript
Serverless 直譯爲無服務器,表明一種無服務器架構(也被稱爲「無服務器計算」),並不表示不須要物理服務器,而是指不須要關注和管理服務器,直接使用服務便可,其實就是一種服務外包或者說服務託管,這些服務由第三方供應商提供。css
具體來講,Serverless 就是指服務端邏輯由開發者實現,運行在無狀態的計算容器中,由事件驅動,徹底被第三方管理,而業務層面的狀態則記錄在數據庫或存儲資源中。html
目前國內一些大型公司如阿里、騰訊、滴滴都已經將 Serverless 逐步在業務中落地(案例分享:2020.06.19 ServerlessDays · China Online)。前端
Serverless = FaaS+BaaS ,是目前界內比較公認的定義:vue
FaaS(Function as a Service):函數即服務java
BaaS(Backend as Service):後端即服務node
筆者認爲,單看 FaaS 或者 BaaS,都是一種 Serverless ,只是通常咱們完整的應用須要二者結合才能實現。nginx
好,下面正式介紹 FaaS。git
做爲一個前端,咱們平日裏很難接觸到服務器、運維方面的操做。假設如今給你一個任務,讓你本身開發一個有先後端交互的應用,並從 0 到 1 進行部署,是否是以爲光靠本身根本搞不定,這個任務有點難。github
傳統應用的部署,咱們須要作不少工做:準備服務器、配置環境、購買域名、配置 nginx、……。應用發佈以後,咱們還要考慮運維的問題,線上監控,擴縮容,容災等等等等。
如今,咱們運用 FaaS 去開發部署的話,以上都不用考慮,只須要專一於業務邏輯開發便可,由於其它一切都託管給 FaaS 平臺幫咱們處理了。
FaaS 是無服務器計算的一種形式。經過 FaaS,能夠快速構建任何類型的應用和服務,它具備開發敏捷特性、自動彈性伸縮能力、免運維和完善的監控設施。所以:
所以,相比傳統開發模式,FaaS 大大提升了開發和交付效率,是將來雲服務發展的大趨勢。自 2014 年始,在 AWS Lambda 以後,Google、IBM、Microsoft、阿里、騰訊、華爲等國內外雲廠商相繼推出雲函數計算平臺 FaaS。
函數計算開發方式有不少種,好比:Fun 工具、函數計算 FC 平臺、Serverless VScode、雲開發平臺。本文藉助阿里雲函數計算平臺,經過其提供的模版快速建立、部署一個 Web 應用,向你們更清楚地展現 FaaS 是什麼。
本文直接基於模版建立,用戶也能夠選擇本身上傳應用代碼直接部署 Web 應用。
咱們選擇有先後端交互、數據增刪改查等行爲的 Todo List 應用,它是一個先後端一體化(先後端代碼共屬一個項目中開發、調試、部署,高效且節省資源) FaaS 應用。
一個應用能夠拆分爲多個服務。從資源使用維度出發,一個服務能夠由多個函數組成。先建立服務,再建立函數。
能夠看到 TodoList 應用部署成功後建立好的服務,咱們能夠對該服務進行配置、刪除、查看指標等操做,還能夠對其下的函數進行配置、刪除。
函數是系統調度和運行的單位。函數必須從屬於服務,同一個服務下能夠有多個函數,同一個服務下的全部函數共享相同的設置,例如服務受權、日誌配置,但彼此相互獨立,互不影響。
FaaS 對多種語言都有良好的支持性,好比阿里雲支持 Node.js、Python、PHP、Java 等等,開發者可使用本身熟悉的語言,根據平臺提供的函數接口形式編寫代碼。這也意味着,團隊協做時,你們能夠利用多種語言混合開發複雜應用。
點擊代碼執行,能夠看到這裏有一個在線編輯器,裏面就是模板生成的代碼,能夠在此處進行運行、調試。該應用前端頁面用 React 實現,後端服務基於 Node 的 Express 框架。
template.yml
是咱們的函數信息配置文件,告訴雲廠商咱們的代碼入口函數、觸發器類型等操做。
函數入口爲src/index.handler
,即src/index.js
服務端代碼文件中的 handler 方法,該文件代碼以下:
const { Server } = require('@webserverless/fc-express') const express = require('express'); const fs = require('fs'); const path = require('path'); const bodyParser = require('body-parser'); // initial todo list let todos = [ { id: '123', text: 'Go shopping', isCompleted: false, }, { id: '213', text: 'Clean room', isCompleted: true, }, ]; const staticBasePath = path.join('public', 'build'); const app = express(); // index.html app.all("/", (req, resp) => { resp.setHeader('Content-Type', 'text/html'); resp.send(fs.readFileSync('./public/build/index.html', 'utf8')); }); // 靜態資源文件:js、css、圖片 // static js resources app.all('/*.js', (req, resp) => { const filePath = path.join(staticBasePath, req.path); resp.setHeader('Content-Type', 'text/javascript'); resp.send(fs.readFileSync(filePath, 'utf8')); }); // static css resources app.all('/*.css', (req, resp) => { const filePath = path.join(staticBasePath, req.path); resp.setHeader('Content-Type', 'text/css'); resp.send(fs.readFileSync(filePath, 'utf8')); }); // static svg resources app.all('/*.svg', (req, resp) => { const filePath = path.join(staticBasePath, req.path); resp.setHeader('Content-Type', 'image/svg+xml'); resp.send(fs.readFileSync(filePath, 'utf8')); }); // static png resources app.all('/*.png', (req, resp) => { const filePath = path.join(staticBasePath, req.path); resp.setHeader('Content-Type', 'image/png'); resp.send(fs.readFileSync(filePath, 'utf8')); }); app.all('/manifest.json', (req, resp) => { const filePath = path.join(staticBasePath, req.path); resp.setHeader('Content-Type', 'application/json'); resp.send(fs.readFileSync(filePath, 'utf8')); }); // 增刪改查對應的api接口 // list api app.get('/api/listTodos', (req, resp) => { resp.send(JSON.stringify(todos)); }); // create api app.get('/api/createTodo', (req, resp) => { const { todo: todoStr } = req.query; const todo = JSON.parse(todoStr); todos.push({ id: todo.id, text: todo.text, isCompleted: todo.isCompleted, }); resp.send(JSON.stringify(todos)); }); // update api app.get('/api/updateTodo', (req, resp) => { const { todo: todoStr } = req.query; const targetTodo = JSON.parse(todoStr); const todo = todos.find((todo) => todo.id === targetTodo.id); if (todo) { todo.isCompleted = targetTodo.isCompleted; todo.text = targetTodo.text; } resp.send(JSON.stringify(todos)); }); // remove api app.get('/api/removeTodo', (req, resp) => { const { id } = req.query // TODO: Implement methods to filter todos, filtering out item with the same id // todos = todos.filter(); const todosIndex = todos.findIndex((todo) => todo.id === id); if (todosIndex !== -1) { todos.splice(todosIndex, 1); } resp.send(JSON.stringify(todos)); }); const server = new Server(app); // 向外暴露了 http觸發器入口 // http trigger entry module.exports.handler = function(req, res, context) { server.httpProxy(req, res, context); };
能夠看到,咱們不須要本身起服務,FaaS 平臺會爲咱們管理。這個文件,有兩個重要的部分:
// http trigger entry module.exports.handler = function(req, res, context) { server.httpProxy(req, res, context); };
前面說過,FaaS 是一種事件驅動的計算模型,即函數的執行是由事件驅動的,沒有事件觸發,函數就不運行。與傳統開發模式不一樣,函數不須要本身啓動一個服務去監聽數據,而是經過綁定一個(或者多個)觸發器。
觸發器就是觸發函數執行的方式,咱們須要爲函數建立指定的觸發器。
FaaS 應用的觸發器有多種(不一樣雲廠商的觸發器會有所區別),但基本都支持 HTTP、對象存儲、定時任務、消息隊列等觸發器,其中 HTTP 觸發器是最多見的。
以阿里雲函數計算爲例,介紹幾個表明類型:
名稱 | 描述 |
---|---|
HTTP 觸發器 | 1.HTTP 觸發器經過發送 HTTP 請求觸發函數執行,主要適用於快速構建 Web 服務等場 2.HTTP 觸發器支持 HEAD、POST、PUT、GET 和 DELETE 方式觸發函數 3.能夠經過綁定自定義域名爲 HTTP 函數映射不一樣的 HTTP 訪問路徑 4.開發人員能夠快速使用 HTTP 觸發器搭建 Web service和 API |
OSS 觸發器(對象存儲) | 1.OSS 事件能觸發相關函數執行,實現對 OSS 中的數據進行自定義處理 |
日誌服務觸發器 | 1.當日志服務定時獲取更新的數據時,經過日誌服務觸發器,觸發函數消費增量的日誌數據,並完成對數據的自定義加工 |
定時觸發器 | 1.在指定的時間點自動觸發函數執行 |
API 網關觸發器 | 1.API 網關做爲事件源,當有請求到達後端服務設置爲函數計算的 API 網關時,API 網關會觸發函數的執行。函數計算會將執行結果返回給 API 網關 2.與 HTTP 觸發器相似,可應用於搭建 Web 應用。相較於 HTTP 觸發器,您可使用 API 網關進行 IP 白名單或黑名單設置等高級操做 |
開發者在調試時,若是不配置觸發器,也可使用控制檯、命令行工具 或者 SDK 等方式直接調用函數執行。
咱們點開 TodoList 的觸發器,能夠看到建立的 HTTP 觸發器,WEB 用戶經過 HTTP 請求便可觸發函數的執行。
注意這裏的提示語:打開連接,會下載一個 html 附件,這時咱們打開,由於找不到靜態資源文件,應用不能正常運行。
能夠點擊自定義域名去用平臺爲咱們建立的臨時域名打開:
能夠看到,頁面和功能都正常了。咱們查看、新增、更新、刪除,在控制檯裏能夠看到發起的請求和返回結果
該應用 HTTP 觸發器的入口函數形式以下:
// http trigger entry module.exports.handler = function(req, res, context) { server.httpProxy(req, res, context); };
配置 HTTP 觸發器的函數能夠經過 HTTP 請求被觸發執行。此時函數能夠看作一個 Web Server,對 HTTP 請求進行處理,並將處理結果返回給調用端。
訪問 html 頁面、請求靜態資源文件,以及請求接口,都是經過 http 請求去觸發相應函數的執行。
能夠在這裏進行調試:
目前市面上已經有一些較爲成熟的開源 FaaS 框架,好比 OpenFaaS、funktion、Kubeless、Fission等等,本文向你們介紹阿里雲今年正式發佈的Midway FaaS框架,它是用於構建 Node.js 雲函數的 Serverless 框架,它提供了函數的本地開發、調用、測試整個鏈路。它能夠開發新的 Serveless 應用,也提供方案將傳統應用遷移至各雲廠商的雲函數。阿里內部已經使用一年多了。
咱們能夠運用腳手架@midwayjs/faas-cli
提供的能力在本地快速建立、調試、mock、部署一個 FaaS 應用。Serverless 有一個很大的弊端,就是和雲服務商平臺強綁定,可是Midway Serverles 是防平臺鎖定的,它能一套代碼可以運行在不一樣的平臺和運行時之上,Midaway faas的部署能夠跨雲廠商,默認部署到阿里雲FC,咱們也能夠修改部署到其它平臺,如騰訊雲SCF、AWS Lambda。
Midway FaaS 體系也與雲工做臺進行告終合,使用了和本地一樣的能力,這裏選擇登陸阿里雲開發平臺,用示例庫模版再次建立一個 TodoList 應用進行演示,只不過這個是用 Midway FaaS 構建的。
代碼目錄結構能夠簡單抽取爲:
|-- src | |-- apis //函數代碼 | |-- config //針對不一樣環境建立配置文件 | |-- configuration.ts //擴展能力配置 | |-- index.ts // 函數代碼入口文件,裏面包括多個函數 | |-- components // 前端組件 | |-- index.tsx // 前端頁面入口文件(該應用前端基於React,如果Vue,則是App.vue) |-- f.yml // 函數信息配置文件
f.yml配置文件
service: serverless-hello-world // 服務提供商 provider: name: aliyun runtime: nodejs10 //運行時環境及版本信息 // 函數具體信息(包括函數入口、觸發器等等) functions: render: handler: render.handler events: - apigw: path: /* list: handler: todo.list events: - apigw: path: /api/list update: handler: todo.update events: - apigw: path: /api/update remove: handler: todo.remove events: - apigw: path: /api/remove add: handler: todo.add events: - apigw: path: /api/add // 構建的配置信息 package: include: - build //打包目錄 artifact: code.zip //打包名稱
函數代碼中一個函數對應一個 api 接口:
安裝依賴後,咱們npm run dev
,阿里云爲咱們建立了一個臨時域名用於調試:
能夠看到請求的這些資源和接口數據:
一鍵部署,很是方便。
傳統應用咱們的服務是一直佔用資源的,而 FaaS 在資源空閒時不收費,按需付費,能夠大大節省開支。
收費標準:
由於每個月都有免費額度,因此在我的平常使用時基本不須要付費。
(PS:但仍是要特別注意,部署的應用不用時必定要及時下線,不然可能會收取費用,還有開通的服務、功能也必定要仔細留意一下收費標準,好比可能想解決冷啓動的問題,會開通預留實例功能,若是咱們不用的話,必定要注意手動釋放,不然即便它沒有執行任何請求,也會從啓動開始不斷收費,費用可不小)
再說說 FaaS 目前備受關注的一個問題——冷啓動。
FaaS 中的函數首次調用、更新函數或長時間未調用時從新調用函數時,平臺會初始化一個函數實例,這個過程就是冷啓動,平均耗時在幾百毫秒。
FaaS 由於冷啓動,不能當即調用函數,調用延遲會給應用性能帶來影響,針對冷啓動的延遲問題,各大雲服務商很是關注,正在想辦法不斷優化。
與冷啓動相呼應的是熱啓動,熱啓動指函數調用時不用從新建立新的函數實例,而是直接複用以前的函數實例。由於 FaaS 函數若在一段時間內沒有被事件觸發運行,雲服務商就會回收運行函數的容器資源,銷燬函數實例,因此,在未被回收的這段時間內再次調用函數就是熱啓動;銷燬後,從新建立就是冷啓動。
冷啓動具體作了哪些操做呢?以阿里云爲例,大體包括了調度實例、下載解壓代碼、啓動容器、啓動運行時,這一過程結束後,函數纔開始執行。因此冷啓動的啓動消耗時間受到不少因素的影響:
有專門研究對比,不一樣語言的冷啓動時間不一樣
這個過程在冷啓動過程當中相對比較耗時,可能幾十毫秒,也可能幾秒,看代碼體積大小
這個過程的耗時取決於雲服務商
各大雲廠商都已經有了一些優化方案的最佳實踐,須要開發者和雲廠商共同努力:
減小代碼體積:
下降冷啓動頻率
FaaS 還有一個侷限性,就是平臺會限制函數的執行時間,超出時間後執行代碼的進程會被強行銷燬,因此 FaaS 不適合長時間運行的應用。例如 AWS Lambda 函數不容許運行超過 15 分鐘(之前只有 5 分鐘),若是超過就會中斷。使用時,應該根據本身的預期執行時間來設置超時值,防止函數的運行時間超出預期,而且建議調用函數的 client 端的 timeout 要稍稍大於函數設置的 timeout,這樣才能防止執行環境不會意外退出。
相信你們讀到這裏,應該差很少能夠明白 FaaS 的工做流程了,咱們總結一下:
Serverless 如今這麼熱,它對前端到底有什麼影響呢?
整個實踐下來發現,FaaS 幫咱們前端擴展了能力邊界,做爲前端,咱們本身一我的也能快速完成先後端開發以及部署工做,徹底不用關心服務器以及運維方面咱們不擅長的問題。前端也有機會參與服務端業務邏輯開發,更深刻業務,創造更大的價值。
本文結合阿里雲 FC、Midway FaaS 框架快速建立 FaaS 應用的實踐,向你們展現了什麼是 FaaS,FaaS 的工做流程,優缺點,展示了 FaaS 顛覆傳統開發模式的魅力。如今 FaaS 的應用場景很是普遍,Web 應用及小程序等移動應用、AI 及機器學習、物聯網、實時數據處理等等。Serverless 時代,生產效率大大提升,每一個人都有更多機會創造無限可能,讓咱們一塊兒爲將來加油!
Serverless Handbook——無服務架構實踐手冊
Serverless Architectures(譯文)—(Martin Fowler)
政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 40 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。
若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com