Serverless的4種錯誤打開方式

自 2014 年 AWS 發佈 Lambda 以來,Serverless 技術的採用率逐年上升。這是因爲 Serverless 提供了雲服務開發人員無法抗拒的產品,其主要優勢如下:

  • 將服務器管理的工作抽象給了供應商
  • 隨用隨付模式,您只需要爲你使用的量來付費
  • 可自動擴展並且高可用

這些好處是通過這種技術具備的一些特性來實現的。Serverless 的應用程序是無狀態的分佈式系統,可以根據需求進行擴展,提供的是基於事件的異步開發模式。正是因爲這些特性,這種技術才爲雲環境提供了理想的解決方案。

不過,它是否真的像期望的那樣理想嗎?

經過進一步的調查可以發現,毫無疑問,在 Serverless 的應用過程中,很多開發者也陷入了該模式的一種反模式裏面,特別是那些高度利用 Serverless 的場景。隨着越來越多的行業從中獲得好處,我們必須要清醒地認識到哪些東西是有效的,哪些是無效的。毫無疑問,Serverless 是有效的,不過錯誤的使用方式也會讓它引發很多問題,使行業遠離此項技術。

因此,本文的主要目的是揭示那些困擾 Serverless 體系架構的反模式以及如何規避他們,從而推動 Serverless 應用的成功,提升其採用率。

異步特性中的瑕疵

在異步的場景中,Serverless 應用程序往往最合適。埃裏克·約翰遜(Eric Johnson) 在伊斯坦布爾 ServerlessDays 演講中提出了一個概念,即“Serverless 的異步思考方式”。之後,他去了 ServerlessDays Nashville 上發表了更長的演講版本。

不過,受到追捧的異步特性也是其最大的反模式。在理解這句話之前,你需要記住,Serverless 是即付即用的模式。因此當一個 function(譯者注:Serverless 中,每個函數被稱爲 function,下同。)或者一個服務在等待另一個異步調用的 function 或者服務返回響應結果時,第一個 function 是處在空閒狀態的。只需要等待第二個 function 的響應即可。

這是從單體架構轉換到無服務架構過程中,不關注細節導致的結果。例如,在一個單體的系統中,一個方法可能需要執行讀寫 DynamoDB 的操作。然而,爲了避免等待此項操作而阻塞整個控制流,可以使用異步的方式進行調用。允許該方法繼續調用另一個方法執行其他任務,只是在方法的最後等待 DynamoDB 的響應結果。第二個方法可能是按照自己的方式開始操作 S3。

當上面的邏輯遷移到 Serverless 上面時,不能用相同的方式處理。因爲每個方法會映射到每個單獨的 Serverless 的 function 上面,但是你需要記住的是,每個 function 都有可能會超時,又或者只完成了他們剩餘的任務,之後變成空閒狀態等待回調。

結果就是這個處在空閒狀態的 function 也會被收費,因爲在技術層面上它是活躍狀態的。儘管 function 只是在等待,仍有一個 worker 節點使用所有必需的基礎架構爲該 function 提供服務。

當很多 function 連接在一起的時候,這個問題會更加嚴重。在流程中,一個 function 對另一個 function 進行異步調用,等待響應,而第二個 function 對另一個 function 進行調用,又或者在對存儲服務進行讀寫操作。

這大大增加了出現不可靠情況的概率,因爲第一個 function 有可能會超時。當函數調用的是雲服務供應商之外的存儲設備或者本地存儲服務時,這個情況有可能會更糟糕。

解決方案

解決方案並不是放棄異步的模式,因爲問題的關鍵點不在於異步調用,而是在於合併此類調用的方式。例如,在分解單體系統時,通常存在控制器的 function,這隻會增加不必要的開銷,而且還會增大超時帶來的 function 不可靠的概率。

在這個例子裏面,解決方案很簡單,只需要重新考慮控制流程。因此上圖的函數構造可以轉換爲如下圖所示的結構:

從圖裏面可以看出,現在存在三個執行一般任務的 function,每個 function 都是由流程中的前一個 function 觸發。除了 Serverless 之外,在任何平臺上面擁有三個單獨的 function 都可能會被認爲是效率低下的。但是,必須記住的是,在 Serverless 的場景下,成本取決於執行的時長,而不是 CPU 的資源。因此如果改用 EC2 實例來安排這個任務,可能跟上圖的結構就會大大不同。

因此可以看出來,異步按照正確的方式來處理時會帶來極大的好處。它可以減少執行的時長,同時在必要的地方支持並行化。不過,如果不加考慮,異步不僅會損害系統的需求,而且還會損害 Serverless 的整個收益模型。

共享不是收養

使用 Serverless 進行構建的目標是,用獨立的且高度分離的 function,來拆解業務邏輯。不過說起來容易做起來難。而且開發人員經常會遇到必須在 function 之間共享某些庫或者業務邏輯,又或者只是一些基礎代碼的場景。從而導致了某種程度的依賴和耦合關係,違背了 Serverless 架構的初衷。

不同的 function 之間依賴於一些共享的代碼和邏輯,會帶來一系列的問題。最突出的問題就是影響到了伸縮性。隨着系統規模和功能之間不斷地相互依賴,錯誤、停機和延遲的風險成倍增加。微服務誕生的出發點就是要避免這些問題的。此外,Serverless 的一大賣點也是它的可擴展性。通過共享邏輯和代碼庫將功能耦合在一起的系統不僅不利於微服務,而且不利於 Serverless 可伸縮性的核心價值。

下圖描述了這個場景,因爲 function A 中數據邏輯的變更,導致 function B 中,數據通信和處理方式也要發生必要的改變。根據實際的使用情況,function C 也可能會受到影響。

解決方案

在研究解決方案之前,必須要承認的是,在某些場景下可能沒有解決方案,不得不共享代碼邏輯或者代碼庫。此類問題出現在機器學習的應用程序中,其中大型庫必須在用於處理測試、驗證以及訓練數據的各種 function 之間共享。在這個過程中需要使用相同的模型,在訓練的數據集上進行驗證和強化。

在大多數情況下,共享代碼庫和邏輯需求不僅是一種反模式,而且也同樣是 Serverless function 的一種技術限制。例如,AWS 的 Lambda function 在 /tmp 目錄下存儲上的限制是 512MB。這意味着,開發人員在構建他們 AWS Lambda function 代碼的時候,必須要意識到這件事情並且合理運用。畢竟,/tmp 目錄主要用於臨時存儲,因此,一旦 serverless 節點被移除,/tmp 目錄下的內容也會不復存在。

AWS 最近通過發佈備受期待的 Amazon EFS 和 AWS Lambda 集成解決了這個問題。這種新的集成方式允許 function 通過集成 Amazon EFS 實例的方式,訪問共享的類庫或者數據。然而,這不能成爲 function 之間相互依賴合理性的一種依據。目前可以實現某些事情並不意味着它是上述反模式陷阱的最佳解決方案。

因此,這個解決方案目前只是一個最基本而且有效的解決方案,是一個需要不斷構建架構意識的起點。耦合和相互依賴性並不是因爲引入 serverless 而帶來的新問題。業界各個團隊已經提出並實施了很多提高感知度的解決方案。

例如,目前最流行的解決方案之一是 DRY(Don’t Repeat Yourself),這個概念在 1999 年,是由 Andrew Hunt 和 David Thomas 在他們的圖書 《實用程序員》 一書中首次提出。取代 DRY 的方式是 WET(Write Everything Twice)。

總體來說,將 function 之間緊緊的耦合在一起,會讓微服務和 serverless 帶來的收益歸零。瞭解如何構建雲架構模式是可以有效避免這種問題的唯一方案。將業務場景拆分成單獨的 function ,在概念上可能並不那麼容易,不過這仍是必須進行的一項活動,而且還需要謹慎行事。

到底多小纔算特別小

將大型緊湊的業務案例拆分成較小、單獨的 function 概念這件事,最終證明,有很大概率會到一種有害的粒度級別。拆解單體系統無疑是有好處的,不過也要注意一些開銷的問題,否則最終會出現間接費用超過帶來收益的情況,所以必須找到這個平衡點。

可以預見的最大開銷之一就是這些實例直接通信的開銷需求。這個是可以想象到的,因爲 serverless 之間是事件驅動的。因此,就像單體大型系統具備的流量控制那樣,需要確保事件可以在架構的不同組件之間流動。

在各個 function 之間交流事件的需求,會讓人直接想到 Webhook 和 API。這會無形中增加工作量、安全風險和等待的時間。隨着 function 的數量不斷增加,這個問題會幾何倍數的增大。

Serverless 的主要目標是將複雜的基礎架構抽象化,從而讓人們把更多的關注重點放在業務邏輯上面。但是很明顯的是,隨着業務邏輯被拆分成各個 function,慢慢達到了平衡點,額外開銷也開始逐漸超過了拆分帶來的收益。因此這種情況可以被列爲一種反模式。

解決方案

雲供應商已充分認識到使用無服務器 function 的開銷。例如,AWS 發佈了 serverless event bus 服務—— AWS EventBridge。該服務減輕了與 function 之間傳遞事件有關的問題,甚至允許第三方工具將事件發送到 AWS 架構。但是,這不能完全解決問題。

想要找到解決方案,需要先知道什麼場景會出現這種問題。使用 serverless 的 function 來提高開發便捷性這件事已經是衆所皆知了。構建 function 相對來說非常容易, 因此開發人員更傾向於不斷創建新的 function ,導致了過度設計。

因此解決方案應該從開發過程開始的時候,就開始對架構設計進行思考,並且深入理解業務邏輯。這可以先分析客戶預期如何使用應用程序,根據其使用的場景來考慮性能問題以及用例,從而實現需求。

主要的目標還是瞭解用戶期望採用的流程,以及在哪個區域的應用程序期望承受更高的負載。因此,對這些要求更爲清晰的瞭解將有助於確定實際所需要的 function 以及它們的作用域。當務之急是與產品經理或其他人一起,制定出業務的目標和流程。

遞歸可不是朋友

遞歸是計算機科學中不可或缺的一個概念,它會降低計算機計算時的複雜性,相關的文獻中廣泛使用的“大 O 表示法”就是遞歸的典型應用。不過在 serverless 中,遞歸可能產生無法預期的影響。特別是它的伸縮性會極大地加劇這種影響,應該抵制使用遞歸,尤其是在遞歸算法導致無限遞歸的場景。

在對容器或其他以 CPU 爲中心的實例進行編程時,核心問題在於,由於遞歸會增加處理能力,從而使 CPU 利用率達到最大化。一個個 function 會不斷地自我觸發,這可能導致在各個線程中觸發的 function 數量呈指數增長。

在 Serverless 的場景下,實例的 CPU 利用率達到最大並不是問題,因爲它可以自動伸縮。從理論上講,可以增加無限數量的工作節點,甚至可以滿足最嚴格的遞歸算法的需求。但是,從成本的角度出發,遞歸會導致 DoW(Denial on Wallet) 的攻擊。

時刻需要記住,在 Serverless 的場景下,計費裏面不止包含計算能力,還包括調用和運行時間。結果就是遞歸導致你雲供應商收費激增。選擇 Serverless 模式是爲了節約成本,而這種使用方式卻抵消了這種優勢,你以爲費用下降了,但實際卻恰恰相反。

解決方案

顯而易見的解決方案是在構建無服務器應用程序時注意代碼庫中的遞歸算法。但是,可能有些應用程序仍需要遞歸操作。例如,在機器學習的應用中,重複訓練模型直到在訓練數據或驗證數據上達到一定有意義的精度。但是,問題是可以提供多少次遞歸?

就像前面說的,遞歸函數最大的威脅點是無限遞歸的可能性。不過在大多數情況下,這是一種意料之外的結果,程序理論上不需要特殊處理無限遞歸。因此如果需要使用到遞歸,請確保進行嚴格的測試,儘可能完全避免這種問題。

此外,您也可以在自己的 function 之間傳遞數據,以保持一個遞歸計數,並設置一個失效保護的開關,以便當遞歸計數達到一定的數量時停止正在運行的 function。這將使系統知道遞歸發生的次數,並允許可配置的限制隨着時間的推移而變化,同時考慮到無服務器應用程序的成本和其他因素。這樣你的系統就知道遞歸實際發生的次數,並允許配置一個限制,可以隨着時間的變化而變化,同時牢記成本和無服務器應用程序的其他因素。

結論

無服務器無疑正在徹底改變應用程序在雲服務上面的構建方式。但是,隨着這種新模型和體系結構的出現,它具有了自己獨特的反模式。

如果不小心謹慎的話,這些反模式很容易浮現,抹去選擇無服務器架構獲得的所有好處。實際上,根據問題的嚴重性,無服務器架構可能對業務應用程序有害無益。

但是,該技術的優勢極大地促進了其應用。因此多年來,雲社區已經看到了很高的採用率。我們有必要積極瞭解如何在無服務器場景下構建軟件,同時注意避免反模式。俗話說,強大的力量伴隨着巨大的責任!

原文鏈接:

https://medium.com/thundra/breaking-down-serverless-anti-patterns-c91cdfd2d6a2

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章