Serverless 多函數開發示例

01.

什麼是 Serverless?

Serverless 的定義和理解在不同的角度和場景會有不同的解讀,AWS 將 Serverless(在 AWS 雲上)定義爲 “是一種用於描述服務、實踐和策略的方式,使您能夠構建更敏捷的應用程序,從而能夠更快地創新和響應變化” 的一種服務。而紅帽認爲 Serverless 是 “可使開發人員專注構建和運行應用,而無需管理服務器” 的一種開發模型,並進一步將 Serverless 的產品分爲兩類:BaaS(後端即服務,讓開發人員訪問各種各樣的第三方服務和應用))與 FaaS(功能即服務,開發人員編寫邏輯,部署到完全由平臺管理的容器中,然後按需執行)兩種形態。而 Serverless Framework 則認爲 Serverless 是 “一場由開發人員和企業推動, 讓單個開發人員可以完成高流量的應用開發,同時只將精力集中在產生價值的方面” 的運動,

不管哪個方面,哪種角度,Serverless 都具有以下共同特點:

  1. 快速開發,快速部署;

  2. 按量付費,降低成本;

  3. 自動擴容,無需維護;

而目前都是基於各個雲廠商的 FaaS 服務來實現,如:騰訊雲的 SCF,AWS 的 Lambda,Azure 雲的 Azure Funcitons 等。

Serverless 解決什麼問題?

隨着計算能力的加強,系統複雜度的增加,用戶規模的增長,軟件問題(如下, 也稱爲軟件危機)也會發生指數型的增長。

而 Serverless 則可以通過以下方式提出了對於軟件危機問題的解決方案:

同時在現在普遍倡導敏捷工作方式的現代工作環境中,Serverless 也爲快速驗證想法、迭代功能提供了開發方式的最佳實踐,同時而不需要擔心代碼改動會影響系統的其他功能,也無需考慮部署前的服務器配置以及部署後的維護工作。

02.

Serverless Framework

Serverless Framework 是業界非常受歡迎的無服務器應用框架,通過與衆多一流雲供應商如騰訊雲,AWS 等的緊密合作,爲廣大開發者提供無需關心底層基礎設施,即可編寫和部署代碼的無服務開發體驗。

Serverless Framework 同時提供資源管理、自動伸縮、統計分析等能力,讓廣大開發者可以節省運維成本,真正做到 “按量付費” 的同時,也無需花費精力處理日誌收集、異常統計等任務。

Serverless Framework 通過 CLI 工具與騰訊雲緊密合作,爲中國用戶提供了基於組件(Serverless Components) 的完整解決方案。覆蓋了無服務應用編碼、測試、部署等全生命週期,同時切合中國用戶的使用場景和習慣。

爲什麼選用 Serverless Framework

通過 Serverless Framework 的短短几行配置文件和 CLI 工具,開發者就可以額外獲得:

03.

多函數開發示例

本示例使用 Serverless Framework 的多函數組件(multi-scf)和 PostgreSQL 組件(postgresql),實現以下 3 個 API 接口。

並使用 Serverless Framework 提供的 invoke 和 logs 功能進行調試以及查看生產環境實時日誌。

本示例相關代碼可以在 Git 倉庫中獲取。

步驟 1:安裝 Serverless Framework

執行以下命令安裝 Serverless Framework

$ npm install serverless -g

如果之前您已經安裝過 Serverless Framework,可以通過下列命令升級到最新版:

$ npm update serverless -g

此命令會安裝最新的 Serverless Framework 到你的計算機,安裝成功後可以通過 serverless 或者 sls 開始使用 Serverless Framework

步驟 2:初始化多函數項目

$ npm update serverless -g

此命令會使用應用模板 multi-scf-nodejs 初始化名爲 my-multi-scf-demo 的應用目錄。初始化成功後該目錄結構爲:

.
├── README.md
├── index.js
└── serverless.yml

這裏的文件用途如下:

步驟 3:鏈接數據庫

因爲 Serverless 是無狀態的(運行後就會銷燬), 所以這裏需要鏈接數據庫用來持久化 todo 信息。添加數據庫需要先借助 VPC 網絡連接。

1. 添加 VPC

創建子目錄 vpc 並在子目錄中添加新的 serverless.yml 文件如下:

component: vpc # [必選]要使用組件,更多組件請查看 https://github.com/serverless-components
name: sls-demo-msn-vpc # [必選]組件實例名稱
inputs:
  region: ap-guangzhou # 實例所屬地區
  zone: ap-guangzhou-2 # 實例所屬地區區域
  vpcName: ${name} # 實例名稱,這裏複用字段 name 作爲名稱。
  subnetName: sls-demo-msn-subnet # 子網的名稱

更多 VPC 的配置內容,查看 VPC 私有網絡 獲取更多詳情信息。

在子組件的配置文件中,app 名稱會自動繼承父目錄的 serverless.yml 中的配置。同時同一個應用的 app 名稱需要保持一致。

2. 添加數據庫

創建子目錄 db 並在子目錄中添加新的 serverless.yml 文件如下:

component: postgresql #(必填) 引用 component 的名稱,當前用到的是 postgresql 組件
name: sls-demo-msn-DB # (必填) 該 postgresql 組件創建的實例名稱
inputs:
  region: ap-guangzhou # 實例所屬地區
  zone: ap-guangzhou-2 # 實例所屬地區區域
  dBInstanceName: ${name}-${stage} # 數據庫實例名稱唯一,且同一個數據庫只能存在同一個vpc內。
  extranetAccess: true # 是否開啓實例外網訪問
  vpcConfig: # vpc網絡配置
    vpcId: ${output:${stage}:${app}:sls-demo-msn-vpc.vpcId} # 私有網絡Id
    subnetId: ${output:${stage}:${app}:sls-demo-msn-vpc.subnetId} # 子網Id

在數據庫配置中添加數據庫到 vpc 網絡,這裏使用輸出變量 (output) 來動態獲取 vpc 的 id 信息。

更多變量的配置內容,查看 Serverless 變量 獲取更多詳情信息。

更多 PostgreSQL 的配置內容,查看 PostgreSQL 數據庫    獲取更多詳情信息。

在組件部署完成後,可以在組件目錄內,使用 sls info 查看組件的輸出變量,或者可以到騰訊雲的應用控制檯查看相關信息。

3. 初始化應用目錄

  1. 創建子目錄 src 並將創建生成的 index.js (重命名爲todos.js) 和 serverless.yml 移動到目錄中。

  2. src目錄中執行npm init初始化 Node.js 項目。

  3. src目錄中執行npm i pg --save安裝數據庫鏈接依賴包pg

  4. 在項目根目錄添加根配置文件serverless.yml,文件如下:

app: sls-demo-msn-todo-3e5a2134 # 應用唯一識別標識,同賬號下需要保持唯一。
stage: dev # 應用部署環境名稱,這裏使用環境變量 STAGE 的值。

根目錄的配置文件信息會被子組件繼承,不需要在子組件中重複定義。(僅限於 app 與 stage)。

最終完成的項目目錄結構如下:

.
├── README.md
├── db # 數據庫
│   └── serverless.yml # 數據庫配置文件
├── serverless.yml
├── src # 多函數應用
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json # Node.js依賴文件
│   ├── serverless.yml # 多函數應用配置文件
│   └── todos.js # todo 應用主文件
└── vpc # vpc
    └── serverless.yml # vpc配置文件

4. 修改多函數應用配置

在多函數目錄src內修改配置文件如下:

component: multi-scf
name: sls-demo-msn
inputs:
  src:
    src: ./
    exclude:
      - .env
      - "node_modules/**" # 部署時忽略node_modules目錄中所有文件,以加快部署速度
  environments: # 應用環境變量信息
    - key: PG_CONNECT_STRING
      value: ${output:${stage}:${app}:sls-demo-msn-DB.private.connectionString}
  region: ap-guangzhou
  runtime: Nodejs12.16
  memorySize: 128
  vpc: # vpc網絡配置
    vpcId: ${output:${stage}:${app}:sls-demo-msn-vpc.vpcId} # 私有網絡Id
    subnetId: ${output:${stage}:${app}:sls-demo-msn-vpc.subnetId} # 子網Id
  installDependency: true # 是否在線安裝依賴
  timeout: 6 # 默認超時時間(秒)
  functions: # 多函數定義
    allTodo: # 函數別名
      handler: todos.all # 處理函數
      memorySize: 256 # 自定義次函數的內存空間
    addTodo:
      handler: todos.add
      timeout: 9 # 自定義此函數的超時時間(秒)
    completeTodo:
      handler: todos.comp
      timeout: 9
  triggers: # 觸發器配置
    - type: apigw
      parameters:
        name: todosAPIGW
        protocols:
          - https
          - http
        apis: # API配置
          - path: /todos/ # 路由路徑
            method: GET # 路由方法
            function: allTodo # 路由處理函數別名
          - path: /todos/
            method: POST
            function: addTodo
          - path: /todos/{id}/actions/complete
            method: POST
            function: completeTodo
            param: # 動態路由參數配置
              - name: id
                position: PATH
                required: true
                type: number
                desc: Todo ID

這裏修改主要內容有:

更多函數開發的配置內容和詳情,查看 PostgreSQL 數據庫獲取更多詳情信息:

更多 函數開發 的說明內容,查看 文檔鏈接 獲取更多詳情信息:

步驟 4:開發功能

修改 todos.js 完成相關功能的開發,最終該文件代碼如下:

"use strict";
const { Client } = require("pg");
const client = new Client({
  connectionString: process.env.PG_CONNECT_STRING,
});
/**
 * 初始化數據庫和表結構
 */
const initDB = async () => {
  const isConnected = client && client._connected;
  if (!isConnected) {
    await client.connect();
    await client.query(`
    CREATE TABLE IF NOT EXISTS todo (
      ID              SERIAL          NOT NULL,
      TITLE           VARCHAR         NOT NULL,
      NOTE            TEXT,
      IS_COMPLETE     BOOLEAN         DEFAULT FALSE
    );`);
  }
};
/**
 * 獲取所有Todo事項
 */
exports.all = async (event, context) => {
  // async 需要關閉事件循環等待,以避免日誌記錄超時或函數無返回的問題。
  context.callbackWaitsForEmptyEventLoop = false;
  await initDB();
  const { rows } = await client.query({ text: "SELECT * FROM todo" });
  return {
    message: "Tencent SCF execute successful!",
    data: rows,
  };
};
/**
 * 添加新的Todo事項
 */
exports.add = async (event, context) => {
  // async 需要關閉事件循環等待,以避免日誌記錄超時或函數無返回的問題。
  context.callbackWaitsForEmptyEventLoop = false;
  const { title, note } = JSON.parse(event.body);
  if (!title) {
    return {
      statusCode: 400,
      message: "Missing Todo Title",
    };
  }
  await initDB();
  const { rowCount } = await client.query({
    text: "INSERT INTO todo (title, note) VALUES($1, $2)",
    values: [title, note],
  });
  return rowCount === 1
    ? {
        statusCode: 201,
        message: "Todo added success.",
      }
    : {
        statusCode: 400,
        message: "Todo added failed.",
      };
};
/**
 * 完成指定Todo事項
 */
exports.comp = async (event, context) => {
  // async 需要關閉事件循環等待,以避免日誌記錄超時或函數無返回的問題。
  context.callbackWaitsForEmptyEventLoop = false;
  const todoId = event.pathParameters.id;
  if (!todoId && !isNaN(todoId)) {
    return {
      statusCode: 400,
      message: "Missing Todo Id",
    };
  }
  await initDB();
  const { rowCount } = await client.query({
    text: "UPDATE todo SET is_complete = true WHERE id=$1",
    values: [todoId],
  });
  return rowCount === 1
    ? {
        statusCode: 200,
        message: "Todo Complete success.",
      }
    : {
        statusCode: 400,
        message: "Todo Complete failed.",
      };
};

步驟 5:調試功能

1. Invoke 調試

要調試代碼除了使用第三方開發工具通過配置的 API 網關 url 調試,還可以使用 Serverless Framework 的 Invoke 功能 或 遠程調試 功能. 這裏使用 invoke 功能演示如何調試函數功能。

invoke 和 遠程調試功能 需要在組件的目錄內執行。

2. 獲取全部 Todo

在 src 目錄下執行;

$ serverless invoke -f allTodo

執行後可以得到結果

使用授權信息 default 授權中,如果需要使用臨時密鑰,請使用 --login 重新登陸
billDuration:      36
duration:          36
errMsg:
functionRequestId: fe6d302d-f6db-42ad-9c7b-8d0c61ead9b3
invokeResult:      0
log:
  """
    START RequestId: fe6d302d-f6db-42ad-9c7b-8d0c61ead9b3
    Event RequestId: fe6d302d-f6db-42ad-9c7b-8d0c61ead9b3
    END RequestId: fe6d302d-f6db-42ad-9c7b-8d0c61ead9b3
    Report RequestId: fe6d302d-f6db-42ad-9c7b-8d0c61ead9b3 Duration:36ms Memory:256MB MemUsage:11.3984MB
  """
memUsage:          11952128
---------------------------------------------
Serverless: 調用成功
{
  message: 'Tencent SCF execute successful!',
  data: []
}

在 invoke 返回的結果中,會包含函數執行後的 meta 信息,如運行時間,錯誤,RequestId,執行的日誌 和函數返回的結果。

3. 創建新的 Todo

在 src 目錄下執行

$  serverless invoke -f addTodo --data "{\"body\":\"{\\\"title\\\":\\\"Create multi-scf project demo\\\",\\\"note\\\":\\\"Todo App with postgreSQL\\\"}\"}"

執行後可以得到的結果;

使用授權信息 default 授權中,如果需要使用臨時密鑰,請使用 --login 重新登陸
billDuration:      35
duration:          35
errMsg:
functionRequestId: 93f50016-064f-468d-9e98-31645fc254fd
invokeResult:      0
log:
  """
    START RequestId: 93f50016-064f-468d-9e98-31645fc254fd
    Event RequestId: 93f50016-064f-468d-9e98-31645fc254fd
    END RequestId: 93f50016-064f-468d-9e98-31645fc254fd
    Report RequestId: 93f50016-064f-468d-9e98-31645fc254fd Duration:35ms Memory:128MB MemUsage:11.293MB
  """
memUsage:          11841536
---------------------------------------------
Serverless: 調用成功
{
  statusCode: 201,
  message: 'Todo added success.'
}

步驟 6:部署和日誌

1. 部署代碼到生產環境

使用下面命令可以快速部署項目到生產環境(這裏命名生產環境爲prod);

$ serverless deploy --stage prod

2. 即時查看生產環境日誌

在項目目錄src中執行以下命令可以查看項目的即時日誌信息;

$ sls logs --tail -f allTodo --stage prod

以下是返回結果:

使用授權信息 default 授權中,如果需要使用臨時密鑰,請使用 --login 重新登陸
serverless ⚡components
Action: "logs" - Stage: "prod" - App: "sls-demo-msn-todo-3e5a2134" - Name: "sls-demo-msn"
START RequestId:6f31857109130f092c547337c073ea91
Response RequestId:dbb3a8ed63a32be8e6b7a2dd8a32bbe2 RetMsg:{"message":"Tencent SCF execute successful!","data":[{"id":1,"title":"Create multi-scf project demo","note":"Todo App with postgreSQL","is_complete":false}]}
END RequestId:dbb3a8ed63a32be8e6b7a2dd8a32bbe2
Report RequestId:dbb3a8ed63a32be8e6b7a2dd8a32bbe2 Duration:4ms Memory:256MB MemUsage:12.113281MB
Response RequestId:485a87cfc6ad385b7e9c84343962391b RetMsg:{"message":"Tencent SCF execute successful!","data":[{"id":1,"title":"Create multi-scf project demo","note":"Todo App with postgreSQL","is_complete":false}]}
END RequestId:485a87cfc6ad385b7e9c84343962391b
Report RequestId:485a87cfc6ad385b7e9c84343962391b Duration:4ms Memory:256MB MemUsage:11.886719MB
START RequestId:0ede6d26dca55362a701c10ff51c9021
Serverless › 監聽中 ...

總結

感謝長久以來對 Serverless Framework 支持的廣大開發者,未來我們也會繼續迭代產品,推出新功能,改進產品使用體驗,最終我們會爲中國的開發者打造一套符合中國開發者習慣的無服務器開發的完整解決方案。

也歡迎大家到 Serverless 中文社區分享經驗和想法以及反饋問題和 Bug。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/PbxPamEeqWTH09WfUYOQ1w