在上一篇文章中,我們討論了無伺服器的兩種進程模型:執行到完成 ** 與長期運作進程**。兩者關鍵差異在於函數實例是否在執行後立即終止。此外,我們探索了兩種場景:資料編排與服務編排。
你可能會好奇,這些場景能否用長期運作進程實現?答案是肯定的,但要注意,執行到完成 才是無伺服器最純粹的形式。那麼,背後的邏輯是什麼?
要完全理解這點,我們需要引出複雜網際網路應用架構演進中的一個關鍵概念:擴展,這也是本文的重點。
想像有 200 位使用者同時存取你本機開發的 Web 應用首頁 index.html,你的本地 Web 伺服器實例會發生什麼事?
讓我們描述一下你電腦的狀態。首先,客戶端與你的電腦之間建立了 200 個 TCP/IP 連線,這對它來說已勉強應付。接著,所有 200 個客戶端同時發起 HTTP 「GET/」請求。你的 Web 伺服器主進程會建立「CPU 核心數 - 1」個子進程並行處理這些請求。請注意,我們從 CPU 核心數減去 1,是為了保留一個給主進程。
例如,4 核心 CPU 會建立三個子進程,同時處理三個客戶端請求,其餘請求則排入佇列。子進程開始處理「GET/」請求,匹配路由規則,進入對應的控制函數,並將 index.html 返回給客戶端。一旦子進程發送完 index.html 文件,主進程就會回收該子進程,並建立一個新的子進程來處理下一個請求,直到所有請求處理完畢。
理解這點後,接下來的問題就簡單了。如何提升客戶端佇列的處理速度?
垂直擴展 vs. 水平擴展
一個明顯的解決方案是增加 CPU 核心數。我們可以透過升級單一機器的配置來達成,例如從 4 核心升級到 8 核心,從而得到 7 個並行子進程。
除了直接增加 CPU 核心數,我們還可以增加更多機器(每台 4 核心)。透過將 500 個客戶端分配到兩台機器,我們也能將並行子進程數增加到 6 個。
增加或減少單一機器的效能稱為 垂直擴展,但隨著效能提升,其成本曲線往往非常陡峭。因此採用這種方法時需要謹慎考慮。另一方面,增加或減少機器數量則是 ** 水平擴展**,這是一種更具成本效益的方式,也是我們預設的擴展方法。
現在,讓我們增加一些複雜性。雖然 index.html 是單一文件,但資料呢?無論是垂直還是水平擴展,我們都需要重新啟動機器。在我們的待辦事項清單範例中,資料儲存在記憶體中,每次重新啟動都會重置。那麼,在擴展時我們該如何保留資料?
有狀態 vs. 無狀態
網路拓撲中的節點,根據是否儲存狀態可分為 有狀態 與 無狀態。有狀態節點會保留狀態,也就是儲存資料。因此它們需要額外關注,必須保持穩定且不易頻繁變動。例如,資料庫通常採用主從結構,當主節點出現問題時,能立即切換到從節點,確保持續提供服務。
而無狀態節點則不儲存任何狀態,或僅暫時存放不可靠的資料。由於沒有狀態,無狀態節點可以水平擴展以應對高並發,並在沒有流量時縮減至零(聽起來很熟悉吧?)。但有狀態節點則無法做到這一點。在流量高峰與離峰時段波動較大的場景中,我們需要設計有狀態節點來應付高峰流量,同時即使在低流量時期也要維持營運成本。
資料庫是典型的有狀態節點,因為它會持續儲存使用者的待辦事項。同樣地,負載平衡器也是有狀態的,就像我們思想實驗中維護客戶端佇列的主進程。它需要儲存客戶端連線,以便將 Web 應用處理完的結果返回給客戶端。
回到我們的進程模型,執行到完成 ** 本質上是無狀態的,因為它在執行後終止,無法單獨用於持久化資料儲存。而長期運作進程** 則天然具有狀態,因為其主進程不會退出,從而可以儲存一些值。
然而在無伺服器中,即使我們在長期運作進程的主進程中儲存了值,雲端服務提供商仍可能回收它。即使有保留實例,擴展出的節點記憶體中的資料也是隔離的。
因此,要讓長期運作進程變得無狀態,我們需要避免在主進程中儲存值,或只儲存臨時變數。持久化資料應移至專用的有狀態節點,例如資料庫。
透過將資料儲存從主進程節點分離,並確保主進程不保留資料,我們的應用就變得無狀態了。我們將資料儲存在獨立的有狀態資料庫節點中。這個例子就轉變成了上一篇文章中討論的長期運作無伺服器場景:在主進程啟動時連線資料庫,並透過子進程存取資料。但這種方法有一個明顯的缺點:它會直接增加冷啟動時間。有沒有更好的解決方案?
讓我們考慮另一種持久化資料的方法。為什麼一定要自己連線資料庫呢?我們對資料的 CRUD(建立、讀取、更新、刪除)操作,本質上是子進程複用主進程建立的 TCP 連線,發送資料庫語句並取得資料。想像一下,如果我們能用 HTTP 請求(如 POST、DELETE、PUT、GET)向資料庫發送指令,那豈不是可以利用上一課提到的資料編排與服務編排概念了嗎?
什麼是 BaaS
沒錯,這些鋪陳正是為了引出今天的主角:BaaS 化。資料介面操作 POST、DELETE、PUT、GET 對應 RESTful API 的語義 HTTP 方法。以 MySQL 為例,POST 對應 CREATE 指令,DELETE 對應 DELETE,PUT 對應 UPDATE,GET 對應 SELECT。這種語義的一對一對應,讓我們可以自然地將 MySQL 操作轉換為 RESTful API 操作。
傳統資料庫方法由於 TCP 連線複用和低通訊開銷,執行相同操作時比 HTTP 更快。雖然無伺服器可以直接連線資料庫,但在具有 VPC 隔離的雲端環境中,使用 IP 位址連線傳統資料庫往往困難重重。因此,無伺服器資料庫連線通常依賴雲端服務提供商提供的 BaaS 服務,儘管許多 BaaS 服務尚未成熟。
再進一步,如果無伺服器不適合有狀態節點,為何不將所有有狀態操作外部化為資料介面?這樣我們的無伺服器函數就能利用上一課討論的資料編排方式,實現自由擴展。
總結
執行到完成 ** 模型被認為比長期運作進程** 模型更純粹的原因,在於後者容易誤導我們,讓我們將其視為 PaaS,並將其用作持久化資料儲存的有狀態節點。然而在無伺服器中,即使是長期運作進程,雲端服務提供商仍可能回收我們的函數實例。
就像我們範例中將資料儲存在記憶體,每次重新啟動就會重置一樣,透過採用資料編排思維,將後端資料庫操作轉換為資料介面,我們就能將無伺服器中的資料儲存卸載給後端應用,並如上一課所述透過資料編排與之互動。但我們不僅要為後端應用建立資料介面,還需要擁抱 BaaS 化,讓後端工程師在開發過程中無需煩惱伺服器端的營運問題。
關於擴展,我們可以選擇垂直或水平擴展。垂直擴展著重於提升單機效能,但成本往往急遽增加,因此需謹慎採用。水平擴展則是增加機器數量,成本曲線較平緩,是我們預設的擴展方式。
有狀態節點儲存資料,無狀態節點處理資料但不保留資料。只有無狀態節點可以自由擴展。有狀態節點負責儲存關鍵資料,需要謹慎處理。如果我們希望網路拓撲中的節點能自由擴展,就需要將其資料操作外部化到專用的有狀態節點。
當無伺服器函數存取有狀態節點時,最好讓這些節點提供資料介面,而非僅依賴資料庫指令,因為資料庫連線會為無伺服器函數帶來額外開銷。此外,為了簡化後端工程師的開發,我們應致力於有狀態節點的 BaaS 化。我們將在後續文章中深入探討 BaaS 化。
Novita AI 是一個全能雲端平台,助您實現 AI 抱負。整合 API、無伺服器、GPU 實例——您所需的經濟高效工具。無需基礎設施,免費開始,讓您的 AI 願景成為現實。
推薦閱讀
