一看即會:Serverless 應用開發的 7 個實戰小技巧(文末附好禮)

阿里巴巴雲原生發表於2022-04-25

![image](~tplv-k3u1fbpfcp-zoom-1.image "image")


*作者:劉宇(花名:江昱)*


> 作者說:Serverless 架構下的應用開發,與傳統架構的應用開發還是有比較大的區別點的,例如天然分散式架構會讓很多框架喪失一定的"便利性",無狀態的特點又讓很多"傳統架構下看起來再正常不過的操作"變得異常風險。

>

> 所以本篇我會介紹一些在 Serverless 架構下,常見的應用開發注意事項,分享一些個人的實戰經驗心得。如果你在 Serverless 開發過程中遇到問題,不妨往下聽聽看吧。


## 關於應用開發的 7 個心得


### 如何上傳檔案


在傳統 Web 框架中,上傳檔案是非常簡單和便捷的,例如 Python 的 Flask 框架:


```

f = request.files['file']

f.save('my_file_path')

```


但是在 Serverless 架構下,卻不能直接上傳檔案,原因有以下幾點:


-   一些雲平臺的 API 閘道器觸發器會將二進位制檔案轉換成字串;不便直接獲取和儲存;

-   此外,API 閘道器與 FaaS 平臺之間傳遞的資料包有大小限制,很多平臺被限制在 6M;

-   FaaS 平臺大都是無狀態的,即使儲存到當前例項中,也會隨著例項釋放而導致檔案丟失;


因此,傳統框架中常用的上傳方案,是不太適合在 Serverless 架構中直接使用的。若是想在 Serverless 架構上傳檔案,可以嘗試以下兩種方法:


-   一種是 BASE64 後上傳,持久化到物件儲存或者是 NAS 中,這種做法可能會觸及到 API 閘道器與 FaaS 平臺之間傳遞的資料包有大小限制,所以一般使用這種上傳方法的通常是上傳頭像等小檔案的業務場景;

-   第二種上傳方法是,透過物件儲存等平臺來上傳,因為客戶端直接透過金鑰等資訊,來將檔案直傳到物件儲存是有一定風險的。所以通常情況是客戶端發起上傳請求,函式計算根據請求內容進行預簽名操作,並將預簽名地址返回給客戶端,客戶端再使用指定的方法進行上傳,上傳完成之後,可以透過物件儲存觸發器等來對上傳結果進行更新,詳情如下圖所示:


![image](~tplv-k3u1fbpfcp-zoom-1.image "image")


### 檔案讀寫與持久化方法


應用在執行過程中,可能會涉及到檔案的讀寫操作,或者是一些檔案的持久化操作。在傳統的雲主機模式下,通常情況下是可以直接讀寫檔案,或者將檔案持久化某個目錄下,但是在 Serverless 架構下卻並不是這樣的。


由於 FaaS 平臺是無狀態的,並且用過之後會被銷燬,所以檔案如果需要持久化並不能直接持久化在例項中,可以選擇持久化到其他的服務中,例如物件儲存、NAS 等。


同時,在不配置 NAS 的情況下,FaaS 平臺通常情況下之後 /tmp 目錄具有可寫許可權,所以部分臨時檔案可以快取在 /tmp 資料夾下。


### 慎用部分 Web 框架的特性


函式計算(FC)是請求級別的隔離,所以可以認為這個請求結束了,例項就有可能進入到一個“靜默”的狀態。在函式計算中,API 閘道器觸發器通常是同步呼叫 *(以阿里雲函式計算為例,通常只在定時觸發器、OSS 事件觸發器、MNS 主題觸發器和 IoT 觸發器等幾種情況下是非同步觸發)* ,這就意味著當 API 閘道器將結果返回給客戶端的時候,整個函式就會進入“靜默”狀態,或者被銷燬,而不是會繼續執行完非同步方法。


所以通常情況下像 Tornado 等框架就很難在 Serverless 架構下發揮其非同步的作用。當然,如果使用者需要非同步能力,可以參考雲廠商所提供非同步方法,以阿里雲函式計算為例,阿里雲函式計算為使用者提供了一種非同步呼叫能力,當函式的非同步呼叫被觸發後,函式計算會將觸發事件放入內部佇列中,並返回請求 ID,而具體的呼叫情況及函式執行狀態將不會返回。如果使用者希望獲得非同步呼叫的結果,可以透過配置非同步呼叫目標來實現,詳情如圖中所示:


![image](~tplv-k3u1fbpfcp-zoom-1.image "image")


Serverless 架構下,應用一旦完成當前請求,就會進入到“靜默”狀態,例項甚至都會被銷燬,所以這就導致一些自帶定時任務的框架沒有辦法正常執行定時任務。因為函式計算通常是由事件觸發,不會自主定時啟動。例如 Egg 專案中設定了一個定時任務,但是在實際的函式計算中如果沒有透過觸發器觸發該函式,那麼該函式是不會被觸發,函式也不會從內部自動啟動來執行定時任務,所以此時可以使用各個雲廠商為其 FaaS 平臺提供的定時觸發器,透過定時觸發器觸發指定方法,來替代定時任務。


### 要注意應用組成結構


在 Serverless 架構下,靜態資源更應該在物件儲存與 CDN 的加持下對外提供服務;否則所有的資源都在函式中,透過函式計算對外暴露,不僅僅會讓函式的真實業務邏輯併發度降低,也會造成更多的成本支出。尤其是將一些已有的程式遷移到 Serverless 架構上,如 Wordpress 等,更是要注意將靜態資源與業務邏輯進行拆分,不然在高併發的情況下,效能與成本都將會受到嚴格的考驗。


在眾多雲廠商中,函式的收費標準都是依靠執行時間和配置的記憶體,以及產生的流量進行收費的。如果一個函式的記憶體設定不合理,會導致成本翻倍增加。因此我們既要保證記憶體設定合理,更要保證業務邏輯結構的可靠性。


以阿里雲函式計算為例,當一個應用,有兩個對外的介面,其中有一個介面的記憶體消耗在 128MB 以下,另一個介面的記憶體消耗穩定在 3000MB 左右。這兩個介面,平均每天會被觸發 10000 次,並且時間消耗均在 100ms。如果兩個介面寫到一個函式中,那麼這個函式可能需要將記憶體設定在 3072MB,同時在使用者請求記憶體消耗較少的介面時,在冷啟動的情況下可能難以得到較好的效能表現;但是,如果兩個介面分別寫到兩個函式中,那麼兩個函式記憶體分別設定成 128MB 以及 3072MB 即可:


![image](~tplv-k3u1fbpfcp-zoom-1.image "image")


透過上表,我們可以明確看出,當合理的、適當地把業務進行拆分之後,會在一定程度上更節約成本,以上述例子來看,成本節約近 50%!


### 傳統框架遷移方案與策略


Serverless 的範圍越來越廣,它本質上來講更像是一種設計理念,一種新的程式設計正規化。在這種新的架構下,或者說新的程式設計正規化下,使用全新的思路來做 Serverless 應用是再好不過的了。但是原生的 Serverless 開發框架是非常少的,以 Web 框架為例,目前的主流的 Web 框架“均不支援 Serverless 模式部署”,一方面是要嘗試接觸 Serverless,一方面又沒辦法完全放棄傳統框架,所以如何將傳統框架更簡單、更快速、更科學地部署到 Serverless 架構上是一個值得探討的問題。下面我結合案例分享一下遷移思路:


-   **傳統框架遷移案例**


請求整合方案實際上就是把真實的 API 閘道器請求,直接透傳給 FaaS 平臺,而不在中途增加任何轉換邏輯。以阿里雲函式計算的 HTTP 函式為例,當開發者想要把傳統框架(例如 Django,Flask,Express,Next.js 等)部署到阿里雲函式計算平臺上,並且體驗 Serverless 帶來的按量付費,彈性伸縮等便捷優勢。


得益於阿里雲函式計算的 HTTP 函式和 HTTP 觸發器,開發者不僅可以快速、簡單地將框架部署到阿里雲函式計算,更可以保持和傳統開發一樣的體驗。以 Python 的 Bottle 框架為例,當我們開發一個 Bottle 專案之後:


```

# index.pyp

import bottle

@bottle.route('/hello/<name>')

def index(name):

    return "Hello world"

if __name__ == '__main__':

    bottle.run(host='localhost', port=8080, debug=True)

```


可以直接在本地進行除錯。當想要把該專案部署到阿里雲函式計算上,只需要增加一個 default_app 的物件即可:


app=bottle.default_app()


整個專案:


```

# index.py

import bottle

@bottle.route('/hello/<name>')

def index(name):

    return "Hello world"

app = bottle.default_app()

if __name__ == '__main__':

    bottle.run(host='localhost', port=8080, debug=True)

```


此時,在阿里雲函式計算平臺,建立函式時,將函式入口設定為:index.app 即可。除了 Bottle 之外,其他的 Web 框架的操作方法是類似的,再以 Flask 為例:


```

# index.py

from flask import Flask

app = Flask(__name__)

@app.route('/')

def hello_world():

    return 'Hello, World!'

   

if __name__ == '__main__':

    app.run(

        host="0.0.0.0",

        port=int("8001")

)

```


在配置函式的時候寫上入口函式為:index.app 即可,就可以保證該 Flask 專案執行在函式計算平臺上。


當然,除了使用已有的語言化的 Runtime,還可以考慮使用 Custom Runtime 和 Custom Container 來實現,例如,一個 Web 專案完成之後,可以編寫一個 Bootstrap 檔案(在 Bootstrap 檔案中寫一些啟動命令即可),例如我要啟動一個 Express 的專案,我把我的 Express 專案準備完成之後,可以直接透過 Bootstrap 為:


```

#!/usr/bin/env bash

export PORT=9000

npm run star

```


-   **透過開發者工具快速遷移/部署**


![image](~tplv-k3u1fbpfcp-zoom-1.image "image")


如果透過開發者工具進行傳統框架的支援,可以直接透過 Serverless Devs 的命令,進行案例的專案的建立。目前 Serverless Devs 已經支援以下框架:


![image](~tplv-k3u1fbpfcp-zoom-1.image "image")


詳情可以參考:[https://github.com/devsapp/start-web-framework](https://github.com/devsapp/start-web-framework)


以 Express 專案為例,可以在命令列工具執行:


```

s init start-express

```


即可進行專案的初始化:


```

_____                             

    |  ___|                            

    | |____  ___ __  _ __ ___  ___ ___ 

    |  __\ / / '_ | '__/ _ / __/ __|

    | |___>  <| |_) | | |  __/__ __ \

    ____/_/_\ .__/|_|  ___||___/___/

              | |                      

              |_|                      

                                        

? please select credential alias default

    Welcome to the start-express application

     This application requires to open these services: 

         FC :

     Express development docs:

     * 額外說明:s.yaml中宣告瞭actions:

        部署前執行:npm install --production

       如果遇到npm命令找不到等問題,可以適當進行手動專案構建,並根據需要取消actions內容 

     * 專案初始化完成,您可以直接進入專案目錄下,並使用 s deploy 進行專案部署

?‍ Thanks for using Serverless-Devs

? You could [cd /Users/jiangyu/Desktop/fc-custom-lua-event/image-prediction-app/start-express] and enjoy your serverless journey!

?️ If you need help for this example, you can use [s -h] after you enter folder.

? Document ❤ Star:

```


完成之後,可以進入專案並部署:


```

$ s deploy

framework: 

  region:   cn-beijing

  service: 

    name: web-framework

  function: 

    name:       express

    runtime:    custom

    handler:    index.handler

    memorySize: 128

    timeout:    60

  url: 

    system_url:   

    custom_domain: 

      - 

        domain: http://express.web-framework.1583208943291465.cn-beijing.fc.devsapp.net

  triggers: 

    - 

      type: http

      name: httpTrigger

```


此時可以透過瀏覽器開啟頁面:


![image](~tplv-k3u1fbpfcp-zoom-1.image "image")


此時,可以根據案例提供的 Bootstrap 以及 s.yaml 進行參考,並將自身的專案部署/遷移到阿里雲 Serverless 架構。其中 Bootstrap 為:


```

#!/bin/bash

node index.js

其中 s.yaml 為:

# ------------------------------------

#   歡迎您使用阿里雲函式計算 FC 元件進行專案開發

#   元件倉庫地址/幫助文件:https://github.com/devsapp/fc

#   Yaml參考文件:https://github.com/devsapp/fc/blob/jiangyu-docs/docs/zh/yaml.md

#   關於:

#      - Serverless Devs和FC元件的關係、如何宣告/部署多個函式、超過50M的程式碼包如何部署

#      - 關於.fcignore使用方法、工具中.s目錄是做什麼、函式進行build操作之後如何處理build的產物

#   等問題,可以參考文件:https://github.com/devsapp/fc/blob/jiangyu-docs/docs/zh/tips.md

#   關於如何做CICD等問題,可以參考:/blob/master/docs/zh/cicd.md

#   有問題快來釘釘群問一下吧:33947367

# ------------------------------------

edition: 1.0.0          #  命令列YAML規範版本,遵循語義化版本(Semantic Versioning)規範

name: framework         #  專案名稱

access: "default"       #  秘鑰別名

services:

  framework: # 業務名稱/模組名稱

    component: fc  # 元件名稱

    actions:

      pre-deploy: # 在deploy之前執行

        - run: npm install --production  # 要執行的命令列

          path: ./code # 命令列執行的路徑

    props: # 元件的屬性值

      region: cn-beijing

      service:

        name: web-framework

        description: 'Serverless Devs Web Framework Service'

      function:

        name: express

        description: 'Serverless Devs Web Framework Express Function'

        codeUri: './code'

        runtime: custom

        timeout: 60

        caPort: 9000

      triggers:

        - name: httpTrigger

          type: http

          config:

            authType: anonymous

            methods:

              - GET

      customDomains:

        - domainName: auto

          protocol: HTTP

          routeConfigs:

            - path: '/*'

```


### 可觀測性


Serverless 應用的可觀測性是被很多使用者所關注的。可觀測性是透過外部表現判斷系統內部狀態的衡量方式,在應用開發中,可觀測性幫助判斷系統內部的健康狀況。在系統出現問題時,幫助定位問題、排查問題、分析問題;在系統平穩執行時,幫助評估風險,預測可能出現的問題。


在 Serverless 應用開發中,如果觀察到函式的併發度持續升高,很可能是業務推廣團隊的努力工作導致業務規模迅速擴張,為了避免達到併發度限制觸發流控,開發者就需要提前提升併發度,以阿里雲函式計算為例,阿里雲函式計算就在可觀測性層面提供了多種緯度,包括 Logging、Metrics 以及 Tracing 等內容。


![image](~tplv-k3u1fbpfcp-zoom-1.image "image")


如圖,在控制檯監控中心,可以檢視到整體的 Metrics,服務級 Metrics 以及每個函式的 Metrics。除此之外,還可以看到當前函式的請求記錄:


![image](~tplv-k3u1fbpfcp-zoom-1.image "image")


根據不同的請求記錄,我們可以檢視到函式的詳細資訊:


![image](~tplv-k3u1fbpfcp-zoom-1.image "image")


除了在控制檯的監控中心處可以檢視到函式的日誌等資訊,在函式詳情頁面,也可以看到函式的詳細日誌資訊:


![image](~tplv-k3u1fbpfcp-zoom-1.image "image")


以及 Tracing 相關資訊:


![image](~tplv-k3u1fbpfcp-zoom-1.image "image")


當然,透過 Serverless Devs 開發者工具,以及函式計算元件也可以進行觀測相關操作,下面我們來一起看一下是怎麼進行的。


-   **透過工具進行 Metrics 檢視**


詳情參考:


[https://github.com/devsapp/fc/blob/main/docs/zh/command/metrics.md](https://github.com/devsapp/fc/blob/main/docs/zh/command/metrics.md)


-   有資源描述檔案(Yaml)時,可以直接執行 s metrics 檢視函式的指標資訊;

-   純命令列形式(在沒有資源描述 Yaml 檔案時),需要指定服務所在地區以及服務名稱,函式名等,例如 sclifcmetrics--regionch-hangzhou--service-namemyService--function-namemyFunction;


上述命令的執行結果示例如下:


```

[2021-06-07T12:20:06.661] [INFO ] [FC-METRICS] - 請用瀏覽器訪問Uri地址進行檢視:

```


此時,透過瀏覽器開啟地址,可以看到函式指標資訊:


![image](~tplv-k3u1fbpfcp-zoom-1.image "image")


P.S.需要開啟請求級別指標,才能檢視函式指標資訊,否則圖表不展示資料。


關於如何開通請求級別指標:


1.[]()


2.服務及函式中-找到自己region-對應的服務名稱-在操作欄點選配置開啟請求級別指標


-   **透過工具進行 Logs 檢視**


詳情參考:


[https://github.com/devsapp/fc/blob/main/docs/zh/command/logs.md](https://github.com/devsapp/fc/blob/main/docs/zh/command/logs.md)


-   有資源描述檔案(Yaml)時,可以直接執行s logs 進行線上函式的日誌查詢;

-   純命令列形式(在沒有資源描述 Yaml 檔案時),需要指定服務所在地區以及服務名稱,函式名等,例如s cli fc logs --region cn-hangzhou --service-name fc-deploy-service --function-name http-trigger-py36


上述命令的執行結果示例:


```

FunctionCompute python3 runtime inited.


FC Invoke Start RequestId: 84d6ae81-02ff-4011-b3ca-45e65b210cc3

FC Invoke End RequestId: 84d6ae81-02ff-4011-b3ca-45e65b210cc3


FC Invoke Start RequestId: de4812be-9137-4a33-9869-370cb61ac427

FC Invoke End RequestId: de4812be-9137-4a33-9869-370cb61ac427

```


如果需要以 tail 模式進行日誌的查詢,可以增加 --tail 引數,例如 s logs --tail;


查詢指定時間段的日誌,可以透過增加 --start-time 和 --end-time 引數實現(例如 s logs -s 2021-11-04T15:40:00 -e 2021-11-04T15:45:00)


### 如何對應用進行除錯


在應用開發過程中,或者應用開發完成,但是所執行結果不符合預期時,通常要進行一定的除錯工作。但是在 Serverless 架構下,除錯往往會受到極大的環境因素限制,有可能出現的情況是,所開發的應用在本地是可以比較健康的、符合預期的執行,但是在 FaaS 平臺上,則會出現一些不可預測的問題;或者是在一些特殊的環境下,本地沒有辦法模擬線上環境,難以進行專案的開發和除錯。


Serverless 應用除錯被視為 Serverless 落地最大的痛點與挑戰,但是各個雲廠商並沒有因此放棄在除錯方向的不斷深入探索。以阿里雲函式計算為例目前就提供線上除錯、本地除錯等多種除錯方案。一些具體的操作可參考 Serverless 公眾號文章:<硬核除錯實操 | 手把手帶你實現 Serverless 斷點除錯>,這裡就不再贅述啦。


## 結語


以上就是我在 Serverless 應用開發中的一些經驗分享,Forrester 曾提出:Serverless 架構的興起,讓 FaaS (Function As A Service) 成為繼 IaaS、PaaS、SaaS 之後一種新的雲端計算能力提供方式。未來也將會有大量主流企業的核心應用,從原來的主機架構遷移到 Serverless 架構。


Serverless 架構正當時,其已然開啟從概念到實踐的大規模落地之路,正如 Gartner 報告中的預測:到 2025 年,全球一半的企業將採用 FaaS 部署;


Serverless 不僅可以做到讓開發者無需再管理伺服器,無需再對資源做預估並考慮擴充套件;發揮降本提效,按量付費的優勢,它能讓你進入只專心在業務邏輯的狀態,從而全面提升工作中的研發效能。


好啦,今天是世界讀書日,感謝收聽(閱讀),希望我的分享能夠對你有所幫助。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69953029/viewspace-2888698/,如需轉載,請註明出處,否則將追究法律責任。

相關文章