如何將 Web 框架遷移到 Serverless

Serverless 一般翻譯爲「無服務架構」,是一種軟件系統設計架構思想和方法,並非一個開發框架或者工具。他的出現是爲了讓開發者更加關注業務的開發,而將繁雜的運維和部署交給雲廠商。Serverless 由 Faas 和 Baas 組成,Faas 爲開發者提供業務運算環境,而後與 Baas 提供的數據和存儲服務,進行交互,從而提供與傳統服務一致的體驗。可是因爲 Faas 是無狀態的,而且其運行環境是有讀寫限制的,最重要的是它是基於事件觸發的。所以若是傳統 Web 服務想遷移到 Serverless 上,是須要進行相關改造和特殊處理的,爲此遷移成本是必不可少的。本文將具體幫助你們剖析下,如何 Serverless 化傳統的 Web 服務。html

讀完本文將瞭解到:node

  1. 傳統 Web 服務特色
  2. Serverless 適用場景
  3. Web 框架如何遷移到 Serverless
  4. 使用 Serverless Components 快速部署 Web 框架

傳統 Web 服務特色

Web 服務定義:git

Web 服務是一種 面向服務的架構 (SOA) 的技術,經過標準的 Web 協議提供服務,目的是保證不一樣平臺的應用服務能夠互操做。

平常生活中,接觸最多的就是基於 HTTP 協議的服務,客戶端發起請求,服務端接受請求,進行計算處理,而後返回響應,簡單示意圖以下:github

web-service-flow

傳統 Web 服務部署流程:一般須要將項目代碼部署到服務器上,啓動服務進程,監聽服務器的相關端口,而後等待客戶端請求,從而響應返回處理結果。而這個服務進程是常駐的,就算沒有客戶端請求,也會佔用相應服務器資源。web

通常咱們的服務是由高流量和低流量場景交替組成的,可是爲了考慮高流量場景,咱們須要提供較高的服務器配置和多臺服務進行負載均衡。這就致使服務處在低流量場景時,會多出不少額外的閒置資源,可是購買的資源卻須要按照高流量場景進行付費,這是很是不划算的。express

若是咱們的服務能在高流量場景自動擴容,低流量場景自動縮容,而且只在進行計算處理響應時,才進行收費,而空閒時間不佔用任何資源,就不須要收費呢?npm

答案就是 Serverlessapi

Serverless 適用場景

上面已經提到了 Serverless 的兩個核心特色:按需使用和收費自動擴縮容。並且近幾年 Serverless 的應用也愈來愈普遍,可是它並非銀彈,任何技術都是有它的適合場景和不適合場景。咱們不能由於一項技術的火熱,而盲目的追捧。Serverless 是有它的侷限性的,通常 Serverless 適合以下幾種場景:promise

  1. 異步的併發,組件可獨立部署和擴展
  2. 應對突發或服務使用量不可預測
  3. 無狀態,計算耗時較短服務
  4. 請求延時不敏感服務
  5. 須要快速開發迭代的業務

若是你的服務不知足以上條件,筆者是不推薦遷移到 Serverless。瀏覽器

Web 框架如何遷移到 Serverless

若是你的服務是以上提到的任何話一個場景,那麼就能夠嘗試遷移到 Serverless 上。

常見的 Serverless HTTP 服務結構圖以下:

serverless-http-framework

那麼咱們如何將 Web 服務進行遷移呢?

咱們知道 Faas (雲函數)是基於事件觸發的,也就是雲函數被觸發運行時,接收到的是一個 JSON 結構體,它跟傳統 Web 請求時有區別的,這就是爲何須要額外的改造工做。而改造的工做就是圍繞如何將事件 JSON 結構體轉化成標準的 Web 請求

因此 Serverless 化 Web 服務的核心就是須要開發一個 適配層,來幫咱們將觸發事件轉化爲標準的 Web 請求。

整個處理流程圖以下:

serverless-http-flow

接下來將介紹如何爲 Express 框架開發一個適配層。

Serverless Express 適配層開發

實現原理

首先咱們先來看看一個標準的雲函數結構:

module.exports.handler = (event, context) => {
  // do some culculation
  return res;
};

在介紹如何開發一個 Express 的適配層前,咱們先來熟悉下 Express 框架。

一個簡單的 Node.js Web 服務以下:

const http = require("http");
const server = http.createServer(function (req, res) {
  res.end("helloword");
});
server.listen(3000);

Express 就是基於 Node.js 的 Web 框架,而 Express 核心就是 經過中間件的方式,生成一個回調函數,而後提供給 http.createServer() 方法使用。

Express 核心架構圖以下:

express-framework

由此可知,咱們能夠將 Express 框架生成的回調函數,做爲 http.createServer() 的參數,來建立可控的 HTTP Server,而後將雲函數的 event 對象轉化成一個 request 對象,經過 http.request() 方法發起 HTTP 請求,獲取請求響應,返回給用戶,就能夠實現咱們想要的結果。

Node.js Server 的監聽方式選擇

對於 Node.js 的 HTTP Server,能夠經過調用 server.listen() 方法來啓動服務,listen() 方法支持多種參數類型,主要有兩種監聽方式 從一個TCP端口啓動監聽從一個UNIX Socket套接字啓動監聽

  • server.listen(port[, hostname][, backlog][, callback]):從一個TCP端口啓動監聽
  • server.listen(path, [callback]):從一個UNIX Domain Socket啓動監聽

服務器建立後,咱們能夠像下面這樣啓動服務器:

// 從'127.0.0.1'和3000端口開始接收鏈接
server.listen(3000, '127.0.0.1', () => {});
// 從 UNIX 套接字所在路徑 path 上監聽鏈接
server.listen('path/to/socket', () => {})

不管是 TCP Socket 仍是 Unix Domain Socket,每一個 Socket 都是惟一的。TCP Socket 經過 IP和端口 描述,而 Unix Domain Socket 經過 文件路徑 描述。

TCP 屬於傳輸層的協議,使用 TCP Socket 進行通信時,須要通過傳輸層 TCP/IP 協議的解析。

Unix Domain Socket 可用於不一樣進程間的通信和傳遞,使用 Unix Domain Socket 進行通信時不須要通過傳輸層,也不須要使用 TCP/IP 協議。因此,理論上講 Unix Domain Socket 具備更好的傳輸效率。

所以這裏在設計啓動服務時,採用了 Unix Domain Socket 方式,以便減小函數執行時間,節約成本。

關於 Node.js 如何實現 IPC 通訊,這裏就不詳細介紹的,感興趣的小夥伴能夠深刻研究下,這裏有個簡單的示例, nodejs-ipc

代碼實現

原理大概介紹清楚了,咱們的核心實現代碼須要如下三步:

  1. 經過 Node.js HTTP Server 監聽 Unix Domain Socket,啓動服務
function createServer(requestListener, serverListenCallback) {
  const server = http.createServer(requestListener);

  server._socketPathSuffix = getRandomString();
  server.on("listening", () => {
    server._isListening = true;
    if (serverListenCallback) serverListenCallback();
  });
  server
    .on("close", () => {
      server._isListening = false;
    })
    .on("error", (error) => {
      // ...
    });
  server.listen(`/tmp/server-${server._socketPathSuffix}.sock`)
  return server;
}
  1. 將 Serverless Event 對象轉化爲 Http 請求
function forwardRequestToNodeServer(server, event, context, resolver) {
  try {
    const requestOptions = mapApiGatewayEventToHttpRequest(
      event,
      context,
      getSocketPath(server._socketPathSuffix),
    );
    // make http request to node server
    const req = http.request(requestOptions, (response) =>
      forwardResponseToApiGateway(server, response, resolver),
    );
    if (event.body) {
      const body = getEventBody(event);
      req.write(body);
    }

    req
      .on('error', (error) =>
        // ...
      )
      .end();
  } catch (error) {
    // ...
    return server;
  }
}
  1. 將 HTTP 響應轉化爲 API 網關標準數據結構
function forwardResponseToApiGateway(server, response, resolver) {
  response
    .on("data", (chunk) => buf.push(chunk))
    .on("end", () => {
      // ...
      resolver.succeed({
        statusCode,
        body,
        headers,
        isBase64Encoded,
      });
    });
}

最後函數的 handler 將異步請求返回就能夠了。

藉助 tencent-serverless-http 庫實現

若是不想手寫這些適配層代碼,能夠直接使用 tencent-serverless-http 模塊。

它使用起來很簡單,建立咱們的 Express 應用入口文件 sls.js

const express = require("express");
const app = express();

// Routes
app.get(`/`, (req, res) => {
  res.send({
    msg: `Hello Express`,
  });
});

module.exports = app;

而後建立雲函數 sl_handler.js 文件:

const { createServer, proxy } = require("tencent-serverless-http");
const app = require("./app");

exports.handler = async (event, context) => {
  const server = createServer(app);
  const result = await proxy(server, event, context, "PROMISE").promise;
};

接下來,將業務代碼和依賴模塊一塊兒打包部署到雲函數就能夠了(記得指定 執行方法sl_handler.handler )。

其餘 Node.js 框架

除了 Express 框架,其餘的 Node.js 框架也基本相似,只須要按照要求,exports 一個 HTTP Server 的回調函數就能夠。

好比 Koa,咱們拿到初始化的 Koa 應用後,只須要將 app.callback() 做爲 createServer() 方法的參數就能夠了,以下:

const { createServer, proxy } = require("tencent-serverless-http");
const app = require("./app");

exports.handler = async (event, context) => {
  // 這裏和 Express 略有區別
  const server = createServer(app.callback());
  const result = await proxy(server, event, context, "PROMISE").promise;
};

其餘語言框架

對於非 Node.js 框架,好比 PythonFlask 框架,原理都是同樣的,核心只須要作到 將 Serverless Event 對象轉化爲 Http 請求,就能夠了。因爲筆者對其餘語言不太熟悉,這裏就不作深刻介紹了,感興趣的小夥伴,能夠到 Github 社區搜索下,已經有不少對應的解決方案了,或者本身嘗試手擼也是能夠的。

使用 Serverless Components 快速部署 Web 框架

讀到這裏,相信你已經清楚,如何將本身的 Node.js 框架遷移到 Serverless 了。可是在這以前,咱們都是手動處理的,並且每次都須要本身建立 handler.js 文件,仍是不夠方便。

爲此開源社區提供了一套優秀的解決方案 Serverless Component,經過組件,咱們進行簡單的 yaml 文件配置後,就能夠方便的將咱們的框架代碼部署到雲端。

好比上面提到的 Express 框架,就有對應的組件,咱們只須要在項目根目錄下建立 serverless.yml 配置文件:

component: express
name: expressDemo

inputs:
  src: ./
  region: ap-guangzhou
  runtime: Nodejs10.15
  apigatewayConf:
    protocols:
      - https
    environment: release

而後全局安裝 serverless 命令 npm install serverless -g 以後,執行部署命令便可:

$ serverless deploy

耐心等待幾秒,咱們的 Express 應用就成功部署到雲端了。更多詳細信息,請參考 Express 官方文檔

注意:本文 Serverless 服務均基於 騰訊雲 部署。

Serverless Express 組件不只能幫咱們快速部署 Express 應用,並且它還提供了 實時日誌雲端調試 的能力。

只須要在項目目錄下執行 serverless dev 命令,serverless 命令行工具就會自動監聽項目業務代碼的更改,而且實時部署,同時咱們能夠經過打開 Chrome Devtools 來調試 Express 應用。

關於雲端調試, 騰訊雲 Serverless Framework 正式發佈公告 中有詳細的介紹,而且有視頻演示。

並且除了 Express 組件,還支持: Koa.js,Egg.js,Next.js,Nuxt.js.....

發現更多組件

最後

固然 Serverless 化 Web 服務並無本文介紹的那麼簡單,好比文件讀寫,服務日誌存儲,Cookie/Session 存儲等......實際開發中,咱們還會面臨各類未知的坑,可是比起困難,Serverless 帶給咱們的收益是值得去嘗試的。固然傳統 Web 服務真的適合遷移到 Serverless 架構上,也是值得咱們去思考的問題,畢竟現有的 Web 框架都是面向傳統 Web 服務開發實現的 (推薦閱讀 利與弊-傳統框架要不要部署在 Serverless 架構上)。可是筆者相信,很快就會出現一個專門爲 Serverless 而生的 Web 框架,能夠幫助咱們更好地基於 Serverless 開發應用 ~

One More Thing

3 秒你能作什麼?喝一口水,看一封郵件,仍是 —— 部署一個完整的 Serverless 應用?

複製連接至 PC 瀏覽器訪問: https://serverless.cloud.tenc...

3 秒極速部署,當即體驗史上最快的 Serverless HTTP 實戰開發!

傳送門:

歡迎訪問:Serverless 中文網,您能夠在 最佳實踐 裏體驗更多關於 Serverless 應用的開發!


推薦閱讀: 《Serverless 架構:從原理、設計到項目實戰》
相關文章
相關標籤/搜索