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平台在特定時間段內的任務持續時間的分布情況。如上圖所示,垂直計數軸使用對數刻度,這意味著短時任務代表了任務總量的絕大部分。每個任務是獨立於其他任務運行的無狀態和短暫的工作單元。
同步與非同步
前面介紹了同步處理和非同步處理 。同步處理是在無伺服器任務執行時與調用進程維護的連接。處理完成後,響應將發送回調用進程。非同步處理是調用函數發送處理請求的地方,但在處理運行時不會阻塞。
許多新的無伺服器平台允許進行同步處理。同步處理的優點是可以直接從處理平台獲得結果,而在非同步處理中,獲得結果必須作為獨立的任務來完成。
請注意,以同步處理模式運行可能會引入額外的異常或失敗問題。由於調用進程可能阻塞,因此無伺服器平台能夠處理髮送給它的任務的規模至關重要。如果不能,則調用應用可能會阻塞,遠遠超出預期的等待時間。另外,錯誤恢復機制(即任務重試)和度量收集可能不像非同步模式那樣有效,原因在於非同步任務往往可以做出妥協。
因此,開發人員可能需要構建異常處理來處理阻塞,以及記錄日誌和作業處理分析,以確保任務按照要求執行。
推薦閱讀: