天天看點

無伺服器系統的設計模式

作者 | Tridib Bolar

譯者 | 張衛濱

策劃 | 丁曉昀

在軟體架構和應用設計領域,設計模式是基本的建構塊之一。設計模式的概念是由 Christopher Alexander 在上世紀 70 年代末提出來的(The Timeless Way of Building, 1979 以及 A Pattern Language—Towns, Buildings, Construction, 1977):

每個模式都描述了一個在我們的環境中不斷出現的問題,然後描述了該問題的解決方案的核心。通過這種方式,我們可以無數次地使用那些已有的解決方案,而無需重複相同的工作。—— Alexander et al

随後,這個概念被軟體社群所采用,進而産生了應用于軟體設計領域的不同種類的設計模式。

面向對象的設計模式是一個抽象工具,用來設計遵循 OOP 方式的代碼級别的建構塊。Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides(Gangs of Four - GoF)合作撰寫的圖書 Design Patterns Elements of Reusable Object-Oriented Software(中譯本名為《設計模式:可複用面向對象軟體的基礎》由機械工業出版社出版——譯者注)為開發人員提供了面向對象設計領域的一個指導手冊。這本書于 1994 年首次出版,從那時起,設計模式就成為了軟體設計的一個組成部分。

設計模式也适用于組織。一個大型的組織确實就像一台龐大的機器,它有很多的齒輪、管道、過濾器、馬達等等。在數字時代,我們正在試圖将人腦數字化,是以将企業機器進行數字化并不是什麼了不起的事情。将企業的某一組成部分或者某一區域實作數字化是不夠的。實際上,要操控一個企業,就必須要內建其所有不同的組成部分。企業和解決方案架構師在嘗試使用模式來解決日常的內建場景。這個過程是真正靈活的。每天,來自世界各個角落的思想家們都在解決問題,并發明新的企業內建模式。在這裡,我想要提及該領域的兩位大師,Martin Fowler 和 Gregor Hohpe。

高層管理人員在不斷追逐新的技術趨勢,每天都有新的數字産品變種問世。商業人士都想方設法在這個數字海洋中擷取最大的利益,是以有必要對遺留系統進行現代化改造,也就是所謂的數字化轉型。在這個領域中,像 Ian Cartwright、Rob Horn、James Lewis 這樣的研究人員也基于他們多年的遷移經驗,在最近的 Patterns of Legacy Displacement 文章中提出了一些模式。

在這個快速變更的時代,靈活性是成功的關鍵。彈性、持續傳遞、更快的上市時間、高效開發等等,這些都是推動系統向微服務架構轉移的力量。但與此同時,并不是所有的場景都适合微服務。為了幫助我們了解這個邊界在哪裡,微服務模式的作者 Chris Richardson 為不同的使用場景提出了大量的微服務模式。

除了我上面提到的這些之外,還有更多的模式類别。事實上,關于企業系統架構和軟體的模式有大量的文獻。這意味着,架構師們需要明智地選擇該如何滿足他們的要求。

進入無伺服器的領域

到目前為止,我們已經讨論了針對不同需求和架構的不同類型的模式,但是我們忽略了一個重要的場景,也就是無伺服器的系統。在目前的技術範圍内,無伺服器是最重要和最有活力的方式之一,尤其是在 IaaS 和雲計算領域。

無伺服器平台可以分為兩大類,分别是函數即服務(Function as a Service,FaaS)和後端即服務(Backend as a Service,BaaS)。FaaS 模式允許客戶建立、部署、運作和管理他們的應用,而無需管理底層的基礎設施。與之不同的是,BaaS 提供線上服務,通過雲的方式處理特定的任務,比如認證、存儲管理、通知、消息等等。

所有面向無伺服器計算的服務都屬于 FaaS 這一類别(比如 AWS Lambda、Google Cloud Function、Google Run、Apache OpenWhisk),而其他的無伺服器服務則可以歸為 BaaS,比如無伺服器存儲(AWS DynamoDB、AWS S3、Google Cloud Storage)、無伺服器工作流(AWS Step Function)、無伺服器消息(AWS SNS、AWS SQS、Google PubSub)等等。

無伺服器這個術語非常具有吸引力,但是它可能會有一定的誤導性。真的有服務能夠無需伺服器就能存在嗎?在雲供應商提供的所有無伺服器元件的背後,隐藏着一個很簡單的魔法:在這些元件幕後,都有一個伺服器。雲提供商負責管理實體機和 / 或虛拟伺服器的可擴充性(自動擴充)、可調用性、并發、網絡等,同時還會為終端使用者提供一個接口來配置它們,包括像自定義運作時、環境變量、版本、安全庫、并發、讀 / 寫容量等。

如果我們專注于使用無伺服器方式實作一個架構的話,那麼随之而來的是一些基本的、高層次的問題。

使用無伺服器建構塊設計一個系統時,首選的架構風格是什麼?

我們的應用要采取純粹的無伺服器方式,還是采用混合方式?

我們該在哪些用例中采用無伺服器方式呢?

在實作無伺服器應用的時候,有哪些可重用的架構建構塊或模式呢?

在本文剩餘的内容中,我将會闡述上述四個問題的答案。

無伺服器模式

在技術領域,無伺服器模式相對比較新,而且正處于快速發展之中。它所涉及的不同方面,包括運作機制、适用性、使用場景、使用模式、實作模式等,每一步都在不斷發生着變化。不僅如此,随着雲供應商不斷發明新的無伺服器産品,同樣的微服務模式可以通過各種方式來實作,它們的價格和性能也各不相同。在世界範圍内,軟體工程師都在從不同的視角出發,使用不同的方式在思考。是以,到目前為止,尚未形成建構無伺服器系統的通用方式。

在 API Days 澳洲會議上,來自亞馬遜雲科技的解決方案架構師 Cassandra Bonner 做了一個關于 Lambda 無伺服器服務的五個主要使用模式的演講。她從需求的角度定義了這五個模式:

事件驅動的資料處理。

Web 應用。

移動和物聯網應用。

應用生态系統。

事件工作流。

Peter Sbarski 在他的 Serverless Architectures on AWS 一書中給出了在無伺服器架構下解決通用設計問題的五個模式。它們是:

指令(Command)

消息(Messaging)

優先級隊列(Priority queue)

扇出(Fan-out)

管道和過濾器(Pipes and filters)

這些模式并不是無伺服器架構所特有的。實際上,它們是分布式系統模式的一個子集,比如由 Gregor Hohpe 和 Bobby Woolf 總結整理的 65 個消息模式,它們代表了這種模式最廣泛的集合。

無伺服器系統的設計模式

我撰寫本文的目的是在 AWS 雲環境中按照無伺服器的方式實作管道(Pipe)和過濾器(Filter)模式。我将會讨論一些可供選擇的實作方式以及它們各自的優勢和劣勢。在實作過程中,可重用性是我要考慮的一個具體的方面。

無伺服器架構的管道和過濾器模式

在靈活程式設計中,以及對微服務友好的環境中,設計和編碼的方式已經與單體時代不同了。靈活和微服務開發者不再把所有的邏輯放到一個功能單元中,而是傾向于更加細粒度的服務和任務,遵循單一職責原則(single responsibility principle,SRP)。有了這一點,開發人員就可以将複雜的功能分解成一系列可獨立管理的任務。每個任務會從用戶端擷取一些輸入,然後消費這些輸入以執行其特定的職責,并生成一些輸出,這些輸出會轉移到下一個任務中。根據這一原則,多個任務構成了一個任務鍊。每個任務都将輸入資料轉換成所需的輸出,而這些輸出又會作為下一個任務的輸入。這些轉換器(transformer)傳統上被稱為過濾器,而将資料從一個過濾器傳遞到另一個過濾器的連接配接器(connector)被稱為管道。

管道和過濾器一個非常常見的用法是這樣的:當用戶端的請求到達伺服器的時候,請求載荷必須要經曆一個過濾和認證的過程。當請求被處理的時候,可能會有新的流量進來,在執行業務邏輯之前,系統必須要執行一些通用的任務,比如解密、認證、校驗并從請求載荷中移除重複的消息或事件。

另外一個場景就是在電子商務應用中将商品添加到購物車的過程。在這種情況下,任務鍊可能會包含如下的任務:檢查商品的可用性、計算價格、添加折扣、更新購物車總數等。對于其中的每個步驟,我們都可以編寫一個過濾器,然後使用管道将它們全部連接配接起來。

實作這種模式最簡單的方式就是使用 lambda 函數。我們知道,有兩種調用 AWS 服務的方式,也就是同步方式或異步方式。在同步場景中,lambda 運作函數并等待,直到發起調用的 lambda 接收到被調用 lambda 的響應為止,而在異步的情況中,不需要等待。AWS 支援回調方法和 future 對象來異步接收響應。在這裡,管道的角色就由内部網絡來扮演。

在這種直接的 lambda 到 lambda 的調用中,不管是同步還是異步,都有可能出現節流的情況。當請求的流入速度超過了函數的擴充能力,并且函數已經到了最大的并發水準(預設是 1000),或者 lambda 的執行個體數量達到了配置的預留并發限制,所有額外的請求都會因為節流錯誤(狀态碼為 429)而失敗。為了處理這種情況,我們需要在兩個 lambda 之間添加一些中間存儲,這樣能夠臨時存儲無法立即處理的請求并實作針對被節流消息的重試機制,一旦有 lambda 執行個體可用,它就會擷取這些消息并開始對其進行處理。

我們可以通過使用 AWS 的簡單隊列服務(Simple Queue Service,SQS)來實作這一點,如下圖所示。每個 lambda 過濾器處理一個事件并将其推送到隊列中。在這種設計中,Lambda 可以從 SQS 輪詢多個事件,并作為一個批次進行處理,這也可以提高性能和降低成本。

這種方式可以減少節流的風險,但是并不能完全避免。這裡有一些可配置的參數,我們可以使用它們來平衡節流。除此之外,我們還可以為 lambda 實作一個死信隊列(Dead Letter Queue,DLQ)來處理被節流的事件 / 消息,并能夠防止這些消息丢失。有一篇很好的文章題為“在資料項目中組合使用 SQS 和 Lambda 的經驗教訓”,讀者可以通過它來了解解決該問題的關鍵參數。

在下一節中,我将會建構一個通用的、可重用的解決方案,該方案會用到另外一個适用于無伺服器事件處理的 AWS 元件,即 Amazon EventBridge,我會實作管道和過濾器設計模式。

在無伺服器架構中實作管道和過濾器模式

Amazon EventBridge 是一個無伺服器事件總線,它可以利用從你的應用程式、內建的軟體即服務(SaaS)應用程式和 AWS 服務中産生的事件,進而能夠更容易地建構大規模的事件驅動應用。

在了解它如何運作之前,我們需要了解一些與 AWS EventBridge 相關的術語。

事件總線是 EventBridge 的關鍵元件之一。事件總線接收來自不同源的事件 / 消息,并将它們與一組定義的規則相比對。EventBridge 有一個預設的事件總線,但使用者也可以建立自己的事件總線。在這個 POC 中,我建立了一個名為“pipe”的事件總線。

無伺服器系統的設計模式

規則(Rule)必須要與特定事件總線關聯。在這個 POC 中,我為三個不同的過濾器建立了三個規則,如下圖所示。

無伺服器系統的設計模式

對于每個規則來講,事件模式和目标是兩個非常基本的配置。

事件模式是一個條件。它與自己所比對的事件具有相同的結構。如果傳入的事件具有相比對的模式,那麼規則就會被激活,并将傳入的事件傳遞給目标(目的地)。目标是一個資源或端點,EventBridge 能夠将事件發送給它。對于特定的模式,我們可以設定多個目标。

在我們的例子中,我将 lambda 名設定為模式中的detail.target,一旦 lambda 名稱比對,目标 lambda 就會被觸發。

無伺服器系統的設計模式

注意:detail.target是一個 json 字段。目标是事件的一個可配置的端點 / 目的地。

在事件流中,可以執行的不同步驟如下所示:

源生成一個事件(它必須遵循事件源生成器和 event bridge 規則建立者所定義的模式)。

為了測試我們的實作,我使用了如下的事件:

無伺服器系統的設計模式

基于測試事件的具體detail.target值,會有一個規則比對并執行。在我們的場景中,這将會導緻事件 / 消息會路由到與規則關聯的目标 lambda 上,即filter1_lambda。

目标 lambda 完成其任務,并将事件目标(detail.target)替換為detail.filterlist json 清單中的下一個 lambda,也就是filter2_lambda。

目标 lambda 随後調用 lambda 層的工具函數next_filter()。

next_filter()函數負責建構最終的事件并将其放到 event bridge 中。

基于新的目标值(即filter2_lambda),另外一條規則能夠被比對,進而會調用一個單獨的過濾器 lambda。

在完成所有的任務之後,終端過濾器會将消息發送給下一個非過濾器的目的地。在本 POC 中,終端過濾器是filter3_lambda。這個 lambda 不再調用next_filter函數,而是調用 DynamoDb API,将資料儲存到 DynamoDb 的表中。

正如我們所看到的,借助 EventBridge 的模式比對路由功能,我們可以用單一的事件總線來實作管道和過濾器模式,即便鍊中的某個後繼階段依然在忙于處理前一個事件,鍊中的其他階段都可以自由地開始處理下一個事件,進而提高整體效率。

無伺服器系統的設計模式

如上圖所示,事件最初會到filter1_lambda中,因為用戶端事件的detail.target屬性與目标為filter1_lambda的filter-rule1事件模式相比對。執行完成後,filter1_lambda将事件的detail.target設定為下一個lambda,即filter2_lambda,并将修改後的事件發回給事件總線。由于detail.target的值是filter2_lambda,是以filter-rule2就會被觸發,如此反複。通過這個遞歸過程,所有的過濾器都會被執行。最後一個過濾器可以調用一些其他資源,而非調用next_filter()工具層。

在上面的實作中,每個 lambda 共同的重要任務之一就是将事件目标(detail.target)修改成filterlist中的下一個 lambda。為了完成這個任務,我們使用了 lambda 層(lambda layer)。

lambda 層是 lambda 的一個特性,它可以幫助開發者從 lambda 代碼中提取通用功能或庫,并将其放入一個層中。這個層可以作為一個工具式的代碼塊,實際的 lambda 代碼可以在這個層上面執行。Lambda 可以根據需要重用該層的通用功能和 / 或庫。AWS 文檔這樣說:

Lambda 層是一個包含額外代碼的歸檔檔案,如庫、依賴,甚至是自定義運作時。

對于這個 POC 來講,我寫了一個工具層,它導出了 next_filter 函數。Lambda 過濾器使用這個函數從 filterlist 中推斷出下一個過濾器的名字。相關的代碼片段在本文末尾的附錄中給出。

無伺服器系統的設計模式

整個 POC 代碼以及 AWS 雲開發工具包(AWS Cloud Development Kit,CDK)的基礎設施代碼可以在 github 倉庫中找到。

總 結

模式是軟體設計領域中最有用、最有效的工具之一。為了以标準的方式解決常見的設計問題,我們可以使用合适的設計模式。模式就像一個設計插件。在技術方面,無伺服器是一個快速增長的領域,所有的雲計算供應商都在定期推出新托管的無伺服器服務。是以,要決定一個合适的無伺服器管理服務的技術棧是很困難的。在這篇文章中,我讨論了如何使用不同的 AWS 無伺服器托管服務,以無伺服器的方式完成一種設計模式的不同實作方法。

附 錄

next_filter的代碼片段:

參考資料

Lambda SQS 擴充

(https://aws.amazon.com/cn/premiumsupport/knowledge-center/lambda-sqs-scaling/)

SQS 消息的短輪詢和長輪詢

(https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-short-and-long-polling.html#sqs-long-polling)

節流

(https://docs.aws.amazon.com/lambda/latest/operatorguide/throttling.html)

在資料項目中組合使用 SQS 和 Lambda 的經驗教訓

(https://data.solita.fi/lessons-learned-from-combining-sqs-and-lambda-in-a-data-project/)

作者簡介:

Tridib Bolar 在印度加爾各答工作,是一家 IT 公司的雲計算解決方案架構師。他已經在程式設計領域工作了 18 年以上。他主要從事 AWS 平台相關的工作,同時也在探索 GCP。除了是雲計算無伺服器模式的支援者之外,他也是物聯網技術的愛好者。

https://www.infoq.com/articles/design-patterns-for-serverless-systems/

繼續閱讀