書名: Serverless從入門到進階:架構、原理與實踐作者名: 方坤丁 孫遠高本章字數: 1953字更新時間: 2021-06-24 11:19:09
3.6 運行時和自定義運行時
本節將通過介紹運行時和自定義運行時,對函數運行環境的架構進行說明,讓讀者進一步了解FaaS的執行原理和調度機制,實現自定義函數的運行環境。
3.6.1 運行時和自定義運行時的概念
FaaS平臺的每個函數的底層是基于輕量化虛擬機(MicroVM)實現的,而每個函數都需要通過運行時環境的承載來提供服務,因此可以將運行時看作每個函數的執行環境。函數、運行環境和輕量化虛擬機的層級關系如圖3-8所示。

圖3-8 函數、運行環境和輕量化虛擬機的層級關系
當前主流云廠商的函數平臺都提供常用運行環境的預置和封裝,并能夠持續支持和迭代。編程語言本身十分豐富,并且持續更新,對于平臺來說,維護所有語言和版本并不現實。此外,如果用戶希望針對預置運行環境安裝自定義的擴展,比如PHP環境中PostgreSQL數據庫的擴展Pdo_pgsql,則需要平臺進一步開放限制,實現支持自定義插件和擴展包的安裝。當前,各大云廠商已經通過開放公共容器鏡像等方式,支持函數掛載容器鏡像,從而實現自定義開發和依賴安裝。此外,用戶也可以通過自定義運行時來實現這一需求。接下來我們對運行時和自定義運行時的原理進行分析,實現自定義運行環境。
1. 運行時的生命周期
標準運行時的生命周期由兩部分組成:初始化階段和調用階段。在初始化階段,會準備運行函數需要的資源,然后拉取用戶代碼,對函數進行初始化,并對外暴露入口函數(handler)接口。在調用階段,通過事件觸發,對函數進行調用,并執行函數內部定義的邏輯進行響應,如圖3-9所示。

圖3-9 運行時的生命周期
2. 自定義運行時的生命周期
自定義運行時的生命周期如圖3-10所示,可以看到,不同的觸發器,如網關觸發器、定時觸發器,在觸發事件后,會先將事件分發給Runtime API,之后由用戶實現自定義運行時部分,和Runtime API通過HTTP/RPC等方式進行交互。用戶實現自定義運行時主要需要考慮以下幾個部分:首先需要自定義引導文件bootstrap,通過bootstrap進行初始化,和負責調度的引擎,即Runtime API進行交互,實現運行時的自定義,之后再執行函數的業務邏輯。

圖3-10 自定義運行時的生命周期
因此,與標準的內置運行時相比,自定義運行時的開放性主要體現在以下三個方面。
- 支持自定義開發語言。
- 開放了運行時的初始化階段。
- 支持通用的HTTP/RPC協議通信。
接下來我們詳細解讀自定義運行時每個階段所做的工作。首先,啟動運行時尋找代碼包中的bootstrap文件,此階段引導程序bootstrap做啟動之前的準備和數據加載等工作,同時也需要確保該文件的權限為chmod 755。
之后進入函數的初始化階段。該階段之前是通過FaaS平臺實現的,在自定義運行時中則開放給用戶。本階段可以做許多初始化的工作,例如上文提到的加載擴展、啟動自定義插件、創建連接池等。初始化完成后,可以通過/runtime/init/ready的API通知平臺初始化已經完成。
最后進入函數調用階段,通過/runtime/invocation/next API中長輪詢的方式獲取時間,并調用函數處理對應事件。處理完畢后,通過/runtime/invocation/response | error等API推送函數的處理結果或者異常報告。由此可見,在函數調用和初始化階段,都可以通過用戶自定義和平臺的運行時API進行交互,定義函數的生命周期。
通過自定義運行時的能力,FaaS函數平臺可以完美支持Deno、.NET、WASM、Swift等編程語言,同時也能滿足用戶對運行時自定義擴展和插件的需求,進一步擴展FaaS函數的邊界。
3.6.2 自定義運行時示例
接下來,我們通過一個Deno環境的函數示例,展示FaaS平臺自定義運行時的運行原理。Deno由Node.js的創始人Ryan Dahl在2017年創立。和Node.js一樣,Deno也是一個服務器運行時,它支持多種語言,可以直接運行JavaScript和TypeScript代碼。對比Node.js,Deno提供了安全的執行環境,并且能夠開箱即用地支持TypeScript等開發語言,在開發者中也越來越受歡迎。但當前各大云廠商的FaaS平臺默認僅支持Node.js運行時,因此在本節,我們通過自定義運行時創建Deno環境。
在創建自定義運行時Custom Runtime函數前,首先需要創建運行時引導文件bootstrap和函數處理文件。其中,bootstrap是運行時入庫引導程序文件。創建bootstrap文件,定義Deno的文件目錄并執行入口文件entry.js,如代碼清單3-2所示。
代碼清單3-2 Deno環境下創建bootstrap文件
# 設置Deno緩存目錄 export DENO_DIR=/tmp # 取消代理 unset http_proxy unset https_proxy # 執行入口文件 deno run --allow-net --allow-env entry.js
入口文件創建完畢后,接下來需要創建函數處理文件。函數處理文件主要包含函數邏輯的具體實現,其執行方式及參數可以通過運行時自定義實現。在本例中,創建entry.ts作為入口文件,在其中創建HTTP Server、監聽固定端口、進行函數的初始化等,如代碼清單3-3所示。
代碼清單3-3 Deno環境的函數處理文件
const SCF_RUNTIME_API: string | undefined = Deno.env.get('SCF_RUNTIME_API'); const SCF_RUNTIME_API_PORT: string | undefined = Deno.env.get( 'SCF_RUNTIME_API_PORT', ); const READY_URL = 'http://${SCF_RUNTIME_API}:${SCF_RUNTIME_API_PORT}/runtime/init/ready'; const EVENT_URL = 'http://${SCF_RUNTIME_API}:${SCF_RUNTIME_API_PORT}/runtime/invocation/next'; const RESPONSE_URL = 'http://${SCF_RUNTIME_API}:${SCF_RUNTIME_API_PORT}/runtime/invocation/response'; const ERROR_URL = 'http://${SCF_RUNTIME_API}:${SCF_RUNTIME_API_PORT}/runtime/invocation/error'; import { app } from './src/main.jsx'; const PORT = 9000; async function post(url = '', data = {}) { const response = await fetch(url, { method: 'POST', // 默認為GET, 也支持POST、PUT、DELETE等請求方式 body: JSON.stringify(data), }); return response.text(); } async function forwardEventToRequest(event: any) { // 獲取請求的事件 console.log( '++++++++ Req Url +++++++', 'http://localhost:${PORT}/${event.path}', ); const response = await fetch('http://localhost:${PORT}/${event.path}', { method: 'GET', }); const body = await response.text(); const apigwReturn = { statusCode: 200, body: body, headers: { 'Content-Type': 'text/html; charset=UTF-8', }, isBase64Encoded: false, }; return apigwReturn; } async function run() { // 事件循環 // 獲取事件 const eventObj: any = await fetch(EVENT_URL); const event = await eventObj.json(); await app.start({ port: PORT }); const apigwReturn = await forwardEventToRequest(event); await app.close(); if (!event) { const error = await post(ERROR_URL, { msg: 'error handling event' }); console.log('Error response: ${error}'); } else { console.log('Send Invoke Response: ${event}'); await post(RESPONSE_URL, apigwReturn); } } // 完成 POST 請求,初始化完畢 post(READY_URL, { msg: 'deno ready' }).then(() => { console.log('Initialize finish'); run(); });
示例完整代碼地址為https://github.com/serverless-plus/serverless-deno。
- C#程序設計實訓指導書
- Visual Basic程序設計教程
- PHP+MySQL網站開發項目式教程
- Python:Master the Art of Design Patterns
- Building Serverless Applications with Python
- Instant Nancy Web Development
- Scratch趣味編程:陪孩子像搭積木一樣學編程
- 深入實踐Kotlin元編程
- MINECRAFT編程:使用Python語言玩轉我的世界
- 微前端設計與實現
- Scrapy網絡爬蟲實戰
- Getting Started with Electronic Projects
- Web前端測試與集成:Jasmine/Selenium/Protractor/Jenkins的最佳實踐
- 高質量程序設計指南:C++/C語言
- Oracle API Management 12c Implementation