Serverless開發編程思想

WUXIAOPING 7年前發布 | 44K 次閱讀 軟件架構 Serverless

題記:從去年開始,無服務器(Serverless)的后端開發逐漸被越來越多公司所接受,硅谷的很多公司都把后端服務遷移到AWS的Lambda平臺。阿里云推出FaaS(函數即服務)的產品,國內也有一些公司開始試水Serverless。Serverless和IaaS的模式相比,不但能完全免除Infrastructure的運維工作,而且由于其函數(任務)的彈性分配機制,能節省大量成本。對于后端架構和開發人員來說,了解Serverless的基本編程思想,為接下來的架構遷移做好準備,是非常必要的。

內容較多,分兩篇文章講解Serverless開發的各個方面,本文是第一篇。

首先,Serverless并不是不用服務器了。這個術語只是通俗地描述了用抽象的任務處理和調度技術來管理服務器的方法。在2012年的時候,“serverless”曾經定義如下:

“Serverless”不意味著不再涉及服務器。只是意味著開發人員不再需要考慮太多了。計算資源以服務的方式被使用,我們不必管理物理容量或限制。服務提供商承擔了大部分管理服務器,數據存儲和其他基礎架構資源的責任。讓開發人員將重點從服務器級別轉移到任務級別。無服務器解決方案讓開發人員專注于他們的應用程序或系統需要做什么,消除了后端基礎架構的復雜性。

在當時,“Serverless”一詞并不十分受歡迎, Hacker News上面的評論就充分證明了這一點 。隨著許多Serverless平臺的引入和微服務、事件驅動架構被人們逐漸接受,這種負面評價漸漸消退了。

示例

用一個例子可以方便我們討論Serverless的概念。我們將使用Serverless Pipeline的來處理電子郵件和檢測垃圾郵件,這是一個事件驅動的系統,因為當電子郵件進來時,它將觸發一系列特定于該電子郵件的作業或函數。

在此管道中,你可能會定義任務來解析電子郵件中的文本,圖像,鏈接,郵件屬性和其他嵌入對象。每個項目或元素可能具有不同的處理要求,這又需要一個或多個單獨的任務,甚至其自己的處理流程或序列。例如,拿圖像處理來說,可能你要經過多個不同處理單元分析圖像鏈接,以確定圖像的內容和合法性。取決于消息評分結果(是否垃圾郵件),然后將采取各種措施,這又涉及其他serverless函數。

 

在任務層面思考

無服務器環境中的擴容單位是任務(task)或工作(job)。它是圍繞特定工作負載進行有限處理的一個實例(instance)。任務處理自從有編程以來就存在,所以有可能看起來沒什么新鮮。但是考慮到處理工作負載的高度分布的性質和抽象的方式,在多個層次上了解這個過程是有必要的。

同步與異步

雖然處理任務的性質(無論是同步還是異步)更多是一個服務平臺的問題,但是也是在任務級別考慮的重要因素。傳統的作業處理系統大部分是異步的,這意味著調用進程不會與執行任務處理的實例保持持久連接。工作將排隊等候,因此,他們可能不會立即運行。調用函數和處理器之間唯一的連接是將任務排隊,然后等待運行。(請注意,某些平臺可能允許獲取內部任務狀態,但也是通過API調用而不是持續連接。)

許多新的無服務器平臺允許進行同步處理,從而維持連接,并且客戶端等待處理結束才繼續執行。同步處理的優點是可以直接從處理平臺獲得結果,而在異步處理中,獲得結果步驟必須獨立完成。我將在平臺部分中詳細介紹。一般規則是同步處理適用于輕量級功能(類似于獲取天氣信息的API調用),而異步處理適用于更長時間,更復雜的處理作業(音頻轉錄或批量處理一組事件等),以及啟動任務的(應用程序/組件/函數)和處理結果的(應用程序/組件/函數)不同的情況。

無狀態

無論處理方式如何,開發微服務和無服務器功能的核心原則之一是每個服務或功能都應被視為無狀態的。 無狀態指每個任務用于處理獨立的、不同的請求,任務內部包含足夠的信息來實現該請求。服務和函數不應在內部存儲任何全局的軟件配置或狀態。任何配置數據都應該來自函數外部,通常作為函數負載(payload)的一部分,或通過平臺內的配置功能來獲取。該函數僅作為計算資源使用,僅為處理單個工作負載而存在。

另外,任務應該有一個明確的開始狀態和結束狀態,并且服務、函數應該以相同的方式處理每個有效負載。一個原則是,如果微服務或無服務器函數試圖做太多事情,那就是糟糕的設計;而整潔的微服務和函數應該符合“單一責任原則”(Single Responsibility Principle)。思考無服務器函數的一個好方法是,每個函數應該有唯一的一個變化維度。換句話說,如果一個函數可以用多個方式擴展,那就應該把它分成多個函數。

在我們使用的例子中,每個電子郵件都是一個單獨的事件,因此每個郵件都將有一個單獨的任務序列。每個任務將接受一個有效負載,其中包含要處理的任務或函數數據。

短時性

無服務器功能也是短暫的。這意味著它們只在一段有限的時間內存在。無服務器應用程序的基礎主要在于事件處理、為這些事件服務而進行的任務處理。強大的容器技術的出現使得它能夠在分布式環境中處理任務,并在運行時決定運行的位置。

換句話說,任務處理基本上等價于容器處理,平臺以任務為單位啟動或者刪除容器。例如,電子郵件處理中的每個任務只能對特定電子郵件執行特定操作。完成后,任務和容器應立即終止。

可能存在需要持久或長時間運行的進程的情況,如應用程序服務器或API服務器的情況,但這些不是Serverless的典型應用。在大多數情況下,你覺得可能需要長時間運行的任務,其實有可能避免這種開銷。例如,無服務器平臺、消息隊列或其他組件可能組合起來能夠滿足任何路由需求。同樣,計劃任務也能夠提供定期狀態檢查或周期處理。一個例子是整合和處理來自各種IoT設備的流數據:如果把數據保存在一個或多個隊列或數據庫中,則計劃作業可以周期性運行,查看每一塊數據并啟動一個或者多個子任務處理、整合這些數據。

請注意,在同步和/或實時無服務器處理情況下,容器在每個任務之后可能不會終止,主要是出于能上的考慮。容器可以在任務切換時持續生存,但是它們的狀態和存儲將被擦除和重置,使得每個任務或事件處理循環被隔離,保證短時性。

 

冪等

冪等性是構建到微服務和無服務器功能的關鍵屬性。冪等的概念,是能夠每次運行相同的任務并獲得相同的結果,它也能保證多個相同的請求具有與單個請求相同的效果。當任務運行高度并發和異步的方式時,這對設計至關重要。在任何作業處理環境中,由于任何原因,任務可能無法完成,原因很多:服務器崩潰,資源限制,第三方服務超時,任務超時等。

在其他情況下,任務可能會完成,但客戶端可能已發起相同負載的重復請求。一個例子是注冊消息超時的隊列,因為任務可能仍然在處理請求(因此沒有及時刪除或取消保留消息)。因此,隊列可能會觸發該消息/負載的另一個處理請求。

如果在這種狀態下,任務繼續工作并處理有效負載,將其放在隊列中或將其寫入數據庫,則可能會產生不良影響,特別是存在事務(transaction)的時候。例如,可能會出現兩個重復訂單。因此,對于相同的請求負載,確保只有一個請求被處理是至關重要的。正是由于這個原因,在無服務器平臺中工作的開發人員在處理請求之前,或在寫入或輸出結果之前,都要小心檢查避免重復。

以一位開發者朋友的話來說,另一種思考方式是“想像一下,如果服務器崩潰,而任務在中間處理過程中,任務被重試。或者如果它只是排隊或安排了兩次。需要做些什么來確保你不會覆蓋數據,添加重復的事務,或者通常會因為重新運行而丟棄事務。“ 很簡單,在處理循環中的相應點檢查和驗證,以確保工作尚未執行。

多語種開發

Polyglot編程是指以多種語言編程。在無服務器編程的情況下,它是指以多種語言編寫和執行任務的能力。雖然每個函數只可能用一種語言,但一般來說無服務器平臺應該能夠處理不同的語言寫的函數。這意味著它也應該提供很大程度的代碼獨立性,使得開發人員可以透明地工作,而不用擔心操作系統和服務器級依賴性。

當然,這樣做的優勢是能夠使用合適的工具做正確的工作。或者,使用正確的團隊做正確的工作。在開發過程中經常出現一個現象:如果你有一個錘子,你會把一切都看成是釘子。使用單一語言,解決問題會受到很多限制。開發人員可能會努力使用該語言的代碼包來適應他們的需求,實際上其他語言的其他庫可以更好地服務于這個目的。

要計算貝葉斯統計信息或進行機器學習,您可能希望使用以C,C++,Python或Java編寫的軟件包。同樣,您使用的開發團隊可能會精通一種針對特定語言編寫的特定Web爬行軟件包。能夠利用這些知識和經驗,可以縮短發展周期,減少項目遲發或失敗的風險。

請注意,一些較新的無服務器平臺目前只支持少數幾種語言,但毫無疑問這個情況在今年(2017)能夠迅速改變。

兼容性

無服務器任務需要考慮到兩個兼容性的設計。第一個是任務之間的兼容性,第二個是跨版本兼容。毫無疑問,把功能分成無服務函數的時候,您需要組件之間有健壯的合同規范。可以通過常見的身份驗證和傳輸協議解決部分問題,但好的API需要開發人員負責定義有意義且易于理解的輸入和輸出數據格式。

除了干凈的界面外,開發人員還需要解決版本控制問題。例如,假設函數X在平臺內運行,并且調用函數Y。如果功能Y已經更新而函數X并不知道,并且函數Y的設計不夠健壯,則在處理時可能會失敗,或者可能產生不正確的結果(或者導致預期原始結果的任務中的下游故障)。

與傳統打包發布代碼一樣,無服務器任務中的更改和更新可能會波及應用程序其他部分。在打包發布的情況下,這些沖突可能會通過打包和編譯工具很早被捕獲。然而,通過微服務器和無服務器編程,它可能只能通過運行服務(最好在測試或Staging階段)來發現、解決問題。

這意味著,開發人員不但要保證任務無狀態、任務的獨立性,還需要注意的是設計良好的接口,而且還要解決向后兼容性,并以小心謹慎的方式發布更新。采用Semantic Versioning能解決一部分問題,它也不是靈丹妙藥,但隨著這個版本約定的傳播,它確實為可能正在使用您創建的任務的其他開發人員提供了一些保證。

這種兼容性和一致性的需求也意味著對生產中運行的任務,測試應該持續進行。幸運的是,對于無服務平臺,該功能在很大程度上已經是自帶的。由于無服務器任務本質上是可執行的,因此該功能為持續測試提供了幾乎獨立的框架。然后,只要提供各種測試輸入,設置常規測試就可以了。如果無服務器平臺提供計劃作業,那這個問題就解決了。

平臺級別的問題

分布式基礎設施的高并發作業處理是一件復雜的事情。其中包含許多組件:服務器、處理和監視作業的控制器、自動縮放和管理服務器的控制器,在整個服務器集群中分配作業的控制器、緩沖作業的隊列、確保作業完成和/或重試的其他組件、有助于維持服務級別(service level)的關鍵任務。本節將講解一下這些層次,以便了解無服務器平臺運行中的重要方面。

吞吐量

吞吐量一直是計算機處理領域的重要指標,代表處理事件、請求和工作負載的速度。在無服務器架構的上下文中,要討論吞吐量就不得不談談延遲和并發。基本上,無服務器架構確實比傳統應用程序和大型Web應用程序更容易提高吞吐率,因為它提供了更好的資源利用率。

資源的成本和利用效率是做無服務器的重要原因。如果您是一個擁有大量應用程序/API/微服務的大公司,您目前正在全天候使用計算資源,而且100%的時間都在使用,無論它們程序是否在運行。使用FaaS基礎架構,您可以24*7全天候運行應用程序,您可以根據需要執行任意數量的應用程序的函數,并共享所有相同的資源。理論上,您可以把浪費減少到幾乎為零,同時仍然提供快速響應時間。對于FaaS提供商來說,這種成本節省將轉移給最終用戶開發商。對于企業來說,這可以極大減少資本支出和運營費用。

還可以這樣看:離散的任務具有自包含的特性,可以在無服務架構通用平臺中的任何地方、任何時間運行。這與獨立的單一應用程序(monolith)形成對比:對于單一程序來說,運營團隊必須花費大量的時間來考慮對哪些應用進行擴展,何時擴展以及如何擴展。

  • 任務和項目圖

下圖顯示了無服務器平臺上單個帳戶的一系列任務。黃線表示帳戶的所有任務,其他曲線表示帳戶中的各個項目。項目曲線代表一個微服務或特定的一組應用程序功能。幾年前,這些項目會被構建為傳統的Web應用程序,并作為一個長期運行的應用程序進行托管。但是,您可以看到,每個服務或功能集具有不同的工作負載特性。在應用程序級別管理這些服務比在無服務器平臺中的任務級別管理要復雜得多,更不用說,擴展無服務任務,而不是更復雜的應用程序服務器能節省更多資源。

所有任務(應用程序視圖)與特定任務(無服務器視圖)

延遲

在無服務世界中決定吞吐量的主要因素就是延遲和并發。延遲指的是開始處理任務所需的時間;并發意味著可以隨時運行的獨立任務的數量。(還有其他因素,比如任務處理時間長度,但是開發人員的主要關注點往往在于,在任何給定的時間內,可以啟動多少工作或可以處理多少事件。換句話說,單個任務的性能優化是另外一個話題。)

對于作業處理延遲的要求有很大不確定性。事務(transaction)相關事件可能需要立即處理,而其他事件可能放松延遲要求,一般來說大約數秒鐘或幾分鐘。更有甚者,有些任務延遲在數分鐘或數小時內都可以接受。就像汽車世界的60邁加速時間和馬力等指標,通常情況下,最高的性能不僅不必要,而且優化它會浪費資源并適得其反。在考慮任務和服務時,重要的是盡早定義延遲的需求,因為這將會影響您如何構建任務和管道,以及確定對于無服務器平臺的期望。

考慮延遲的一種方式是在對任務分類。粗略將處理任務分為三類:實時處理,后臺處理和批處理。

  • 實時處理(<1秒)

實時處理的概念因人而異,可以是瞬間完成,也可以是人類能感知的實時完成。它包含啟動任務的延遲和任務持續時間。任務啟動延遲一般標準通常在20ms的范圍內。然而,任務處理時間是一個難以確定的度量,一般的指導原則應該是人類反應的預期時間,通常從開始到結束(包括延遲)不到1秒。

對于分布式云處理來說,這速度要求的確高,特別是當你考慮網絡延遲,容器開銷和任務工作負載波動(即隊列中可能有多少任務)時。這種類型的處理需要在無服務器平臺和無服務器應用程序中選擇正確的體系結構。服務器需要針對特定的一組任務進行優化,預先裝載容器,嚴格定義任務內存和處理器資源限制。需要使用這些約束來構建應用程序,并監控負載,以維持SLA在可接受范圍。

實時處理 - 處理具有低延遲和快速響應速率的作業

  • 后臺處理(秒/分鐘級別)

后臺處理一般用于描述在主事件或響應循環之外處理的事件驅動工作負載,它們對時間較為敏感,需要隨時擴展。雖然處理要求可能不需要在毫秒級的延遲,但是這種任務通常需要在幾秒鐘內完成。

對社交網絡的更新,pdf的生成,ETL處理,流數據輸入的處理,圖像的處理,音頻或視頻的轉錄以及其他媒體處理需求,所有這些例子都是后臺處理的應用場景。在開始使用無服務器計算的時候,這種方式更為常見,因為它是基于傳統的任務和工作隊列的方式。

后臺處理 - 在請求/響應循環之外處理的作業

  • 批處理(分鐘和小時級別)

批處理與以前的定義沒有區別:處理大量重要的、相對獨立、時間要求不高的關鍵任務。現在的區別是,批處理不需要等待白天(或晚上)特定的時間開始運行,或者依賴于一組專門針對工作負載條件的強大服務器。批處理可以在多個地區的數千個核心的機器上、在任何時間進行。云中批量處理與傳統系統的運行模式可能相同,但任務的形式以及和自包含的特性與以前相比有顯著不同。

批量處理

  • 任務延遲分布

下圖顯示了特定時間點無服務器平臺上的任務延遲或等待時間。請注意,y軸使用對數刻度。數字表明,絕大多數任務的等待時間都很少。

 

并發

并發是指可以在任何一個時間執行的類似任務的數量。閾值可以由無服務器平臺來規定,或者,它可以是基于應用程序自身的限制,比如延遲時間超過了限定的閾值,或者資源有限,或者過高的并發可以導致下游服務的失敗等情況。尤其對于數據庫來說,在某個時間點運行的任務太多可能會超過連接限制。

容器技術允許在單個服務器中處理多個任務。五個,十個,二十個,五十個或更多個任務可以在單個服務器上運行,因此并發級別或閾值可能非常大,因為底層基礎架構可支持幾乎無限擴展的容量。許多無服務器平臺能夠自調整以滿足不同的并發需求(白天大,夜間小),或應對在特定時間段內訪問的爆發增長。這避免了大量的配置工作,同時仍然滿足并發SLA。

請注意,我們使用術語并發性(concurrency),這和并行性(parallel)不同。它們是相關但截然不同的概念。在編程中,并發是獨立執行過程的組合,而并行性是同時執行計算。并發是一次性處理很多事情,而并行是同時處理很多事情。

  • 任務流圖

下圖顯示了某個無服務器平臺的特定時間和區域的每分鐘任務流量。黃線表示任務開始,綠線表示任務完成,紅線表示錯誤和超時。

 

內存限制

在任務級別考慮開發工作可以讓您從許多基礎設施的顧慮中解脫出來,但是您也不會擺脫資源限制的制約。內存和處理時間限制在無服務器平臺中仍然扮演重要的角色。因為任務可能在容器中運行,所以它們受到內存(以及可能在容器或平臺級別執行的IO,端口和其他限制)的限制。

目前,大多數平臺的限制比較嚴格,所以開發人員需要意識到他們任務的內存要求,并保持在平臺的限度內。您可能必須通過減少分配給任務的數據分片的大小來限制任務可以處理的數據量(即通過更多任務/更大的并發來解決問題)。這也意味著開發時需要意識到任務如何使用內存,確保使用正確的數據結構來避免不必要的內存分配。正如您將要分析(profile)應用程序的一部分以確保最佳性能和內存使用一樣,您將要在無服務器環境中執行相同操作,盡管在這種情況下,它是以逐個任務和逐個服務的方式進行的。

請注意,大多數無服務器平臺將提供本地臨時讀/寫數據存儲供任務使用(在任務結束時被擦除)。有效使用這種類型的存儲可以減少分配大塊內存的需要。鍵值數據存儲的有效使用也是無服務器編程的重要組成部分。

在未來,無服務器系統可能能夠對任務進行分析,并進行適當的路由來實現可變內存需求,現在還沒有這樣的平臺。然而,一些無服務器平臺確實提供不同的內存配置,以適應內存密集型任務。這種任務的類型可以包括圖像,音頻和視頻處理,大文件處理和科學數據分析。在這種情況下,一旦通過配置或請求頭部數據或通過分析來識別工作負載,平臺就可以將任務路由到高級存儲器處理集群,或者即時調整容器/函數的資源限制。

 

處理時間限制

任務需要多長時間處理會對系統的吞吐量產生重大影響。即使使用自動縮放功能,持續高數量的長時間運行的任務最終將消耗并阻止其他任務在其所需的時間范圍內運行。正是由于這個原因,無服務器平臺通常對任務可以處理多長時間有嚴格的限制。AWS Lambda允許最大處理時間為300秒,IronWorker最大可能是3600秒。

一些無服務系統可能能夠提供較寬松的處理時間限制。一種方法是隔離長時間運行的任務,將其路由到專用群集,并通過自動縮放或調高最大閾值來提供足夠的資源。

處理時間限制可能會對如何構建無服務器工作流程產生重大影響。很像內存限制,處理時間限制可以影響任務可能處理的數據量。開發人員可能需要減少輸入數據的大小,并增加任務并發性。任務可能需要被分解成更多離散的功能。它也可能導致開發人員必須考慮可能阻塞(block)的外部資源請求或操作,這些操作有可能會導致任務超時。

如果服務和功能的架構正確,內存和處理限制不是什么大問題。大多數無服務器平臺的資源限制通常足以滿足大多數處理需求(某些平臺可用的限制有所增加)。考慮本文提到的注意事項將有所幫助。一旦無服務器處理的使用達到“AHA”的時刻:上傳代碼并觀察它們以高并發性運行的時候,你的思維很可能已經適應無服務平臺,你已經掌握如何構建您的功能和工作流程。

下圖顯示了 http:// Iron.io 平臺在特定時間段內的任務持續時間的分布情況。如上圖所示,垂直計數軸使用對數刻度,這意味著短時任務代表了任務總量的絕大部分。每個任務是獨立于其他任務運行的無狀態和短暫的工作單元。

 

同步與異步

前面介紹了同步處理和異步處理 。同步處理是在無服務器任務執行時與調用進程維護的連接。處理完成后,響應將發送回調用進程。異步處理是調用函數發送處理請求的地方,但在處理運行時不會阻塞。

許多新的無服務器平臺允許進行同步處理。同步處理的優點是可以直接從處理平臺獲得結果,而在異步處理中,獲得結果必須作為獨立的任務來完成。

請注意,以同步處理模式運行可能會引入額外的異常或失敗問題。由于調用進程可能阻塞,因此無服務器平臺能夠處理發送給它的任務的規模至關重要。如果不能,則調用應用可能會阻塞,遠遠超出預期的等待時間。另外,錯誤恢復機制(即任務重試)和度量收集可能不像異步模式那樣有效,原因在于異步任務往往可以做出妥協。

因此,開發人員可能需要構建異常處理來處理阻塞,以及記錄日志和作業處理分析,以確保任務按照要求執行。

 

 

 

 

來自:https://zhuanlan.zhihu.com/p/28079865

 

 本文由用戶 WUXIAOPING 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!