戳破微服務的七大謊言

本文最初發佈於scottrogowski.com網站,經原作者Scott Rogowski授權由InfoQ中文站翻譯並分享。

在現代技術公司(無論大小)的架構中,微服務已經無處不在。但是,它們真的比以前的開發模型更優秀嗎?在這篇文章中,我將揭穿工程師們關於微服務所講述的七大謊言,以及爲什麼它可能是一種反模式。

免責聲明1:我不是架構師,也沒假裝自己是架構師。本文內容只是我多年來作爲軟件開發人員/經理所做的觀察總結。我曾見證兩家公司在微服務架構的壓力下陷入泥潭。由於很少有人深度質疑這種新生範式,因此我想表達自己的聲音。不過,我經驗有限,所以也歡迎反饋意見。

免責聲明2:互聯網上也有其他標題相似的演講/文章,這裏就不糾結這種相似度了。

單體架構和微服務之間有何區別?

開始研究謊言前,我們先來定義一下術語。後端軟件架構可以分爲單體和微服務兩種。單體架構指的是由一臺或多臺服務器運行單個應用程序,其通常從單個存儲庫中部署。使用多臺服務器時,這些服務器將運行相同的代碼。從90年代到2000年代,多數情況下這都是默認的架構。

隨着互聯網的發展,大型公司開始面臨單體架構的侷限。爲解決這一問題,公司開始將其代碼分割成在不同服務器上運行的多個組件。例如,一家公司可能會有運行日誌記錄的服務、調用外部API的服務以及管理數據庫的服務。亞馬遜AWS在這一風潮中扮演了重要角色,因爲它讓部署服務器和管理基礎設施的工作變得非常容易。

隨着時間流逝,中小型公司也開始接受這種新的發展範式。很快,一個圍繞微服務的產業發展壯大起來,它逐漸成爲尋求擴展的企業默認架構。不幸的是,現在有許多公司由於這種選擇而掉進了各種之前想不到的坑裏。

謊言1:跨服務的關注點分離降低了複雜度

“[關注點分離]”指的是在不相關的代碼間應存在隔離牆。當不相關的代碼需要協同工作時,應該使用抽象良好的接口並儘量減少狀態共享。很多入門編程課程都將其視爲標準的軟件開發公理。你的代碼對其他代碼的瞭解,越少越好。同樣,一個函數執行的功能越多,你就越需要考慮運動部件之間的複雜關係(即複雜度)。而且,我們作爲合格的工程師就應該努力降低複雜度。

我堅信這一公理。從邏輯上講,分離關注點的最佳方法是否就是讓你的無關代碼運行在不同的服務(服務之間以API溝通)中呢?

不,並非如此。

經典的單進程關注點分離之所以有效,是因爲它可以最小化並簡化不相關代碼之間的接口。在設計良好的程序中,此接口可以只有帶return語句的單個函數調用。不相關代碼之間的邊界本質上是複雜的,而簡單的接口有助於管理這種複雜性。

相比之下,在微服務中,函數調用被替換爲網絡請求。這種新的服務間障礙嚴格來說更加複雜且更不可靠。首先,每個網絡調用都需要一定數量的樣板。其次,工程師現在需要默認任何服務隨時會失效。相反,在單體中,當代碼失敗時整個服務都會失敗。儘管這聽起來很糟糕,但由於現在只有一種故障情況,因此它更易於管理。

在實踐中更糟

左下方的圖表是幾年前Uber的微服務架構。它很簡單,很容易理解。右邊是Uber的實際服務地圖。

我敢說Uber的任何人都不知道這個架構是如何工作的。曾在使用微服務架構的大型公司工作過的人都知道,Uber的經驗並不是特例。

模型與現實之間存在這種差異的原因有兩個。首先,這些圖表通常過於簡化。架構師設計這些圖表是爲了交流,而非完美反映現實。但因爲它們隱藏了複雜性,也就容易誤導決策。如果Uber的技術領導者知道自己的架構會變成什麼樣,他們還會走這條路嗎?

其次,一旦你投身於微服務,隨後的所有技術決策都將受其影響。因此,開發新功能時總要啓動新的服務。架構圖很快就會變得非常複雜,膨脹成上圖那種密密麻麻的網狀結構。

謊言2:微服務提高開發速度

當你採用“關注點分離”公理並將其應用在開發人員頭上時會發生什麼?你會得到一些孤立的團隊,他們之間各自獨立。從表面上看這似乎是有益的。如果團隊只需要操心自己的服務,那將減輕他們的認知負擔,並提高他們的生產力。現在,工程師無需擔心基礎架構中其他部分的複雜性了。

問題在於,大多數新功能都需要一些跨多個服務的補丁。

許多功能需要在兩個或多個服務上開發

開發多服務功能需要在具有不同優先級和能力的團隊之間安排大量會議。考慮到他們從事的多個不同項目,這些團隊可能需要異步協作。你現在還需要交付經理來分配工作和管理迭代。

從技術角度來看,實現多服務功能可能需要編輯多個存儲庫。至少,它需要一種方法來測試在多個服務上運行的代碼。

對許多公司而言,這種多服務測試的需求是事後纔會意識到的。架構師在設計技術棧時會假設大多數開發工作都將在單個服務上進行。多服務功能將很少見。你如何手動測試多服務功能?你需要在機器上啓動多個容器,並仔細設置每個容器的狀態。那單元測試呢?你將在哪裏對多服務功能進行單元測試?是倉庫A還是倉庫B?文檔寫好了嗎?部署往往會破壞未調整好的服務。很容易想象,數據流中的一個小錯誤會破壞多個下游服務。我們應該期望工程師理解所有可能依賴其代碼的下游服務嗎?

如果你的組織沒有投入大量的工程資源來構建多服務測試流程,那麼除了最常見的功能之外,開發新功能的速度會像蝸牛般緩慢。如果沒有質量測試框架,看似簡單的任務(例如“添加分頁”)也可能會變成歷時數月、跨多個團隊的工作。

謊言3:部署衆多小型服務比部署整個應用更安全

“回滾”是現代軟件工程需要面對的現實。作爲工程師,當你部署的代碼會破壞某些功能時,必須回滾部署並還原提交。沒有人想要回滾——尤其是部署代碼的工程師。但是,好的公司知道錯誤的部署總有可能出現,因此必須對其進行管理。

微服務架構的一個觀點是,部署多個獨立服務比部署整個應用更安全。當一項服務中斷時,其他服務還有回退可用。整個應用程序將繼續運行,客戶不會有什麼感覺。

這種方法存在多個問題。

首先,這要假設你的服務可以容忍其他任何服務的隨機消失。這是Netflix的“Chaos Monkey”方法。但是,將其構建到服務中並非易事,測試它需要資源,並且除非這是工程的最優先事項,否則實踐中人們多大程度上會遵守這一要求就不一定了。

其次,部署多服務功能時,服務的上線時間會有所不同。在一段時間裏,你的那些服務將有不同的版本。對此有多種處理方法。你是否在半夜部署?你是否並行維護不同的API版本?你是否使用託管流?所有解決方案都需要額外的工程資源。如果部署意外破壞了(甚至不是部署的一部分)服務中的狀態,會發生什麼情況?你是否有針對任何意外情況的預案?

雖然單體部署也會出錯,但是有多種方法可以緩解這種情況(藍色/綠色、金絲雀等等)。雖然這些方法也可用於微服務,但是設置和管理安全部署並非易事,應對一項服務總比應對多個服務要容易些。

謊言4:分開擴展服務通常是有利的

在每個應用程序中,都有經常運行的部分和很少運行的部分。很少運行的部件比頻繁運行的部件需要的資源要少一些。那麼分開擴展這些部件是否有意義?

從根本上講,擴展軟件的原因是因爲你的軟件需要更多的核心資源。這些資源可能是CPU週期、內存、磁盤空間或網絡。例如,當CPU以100%運行時,可以啓動另一個服務來減輕壓力。

對於大多數應用,水平擴展(克隆單體)就足夠了。水平擴展的複雜度較低,許多雲服務都可以用很少的配置來做到這一點。

相比之下,選擇分開擴展許多微服務有兩個常見原因。首先,如果你的代碼具有實質上並行的部分,則在某些情況下將計算塊分配給不同的“worker”可能會有些意義。重要的是,相對於每個任務的總計算量,數據傳輸和加速的開銷必須夠低。因此,將十個計算塊(每個計算耗時10ms)發送到服務器,開銷卻爲100ms就沒有並行的價值了。因爲順序執行耗時是10x10ms=100ms,而並行卻是10ms+100ms=110ms。但是,如果每次計算都花費100毫秒,則將它們並行化就能節省時間。

其次,如果資源需求在整個請求中出現變化,則單獨擴展各個微服務可能是有意義的。例如,如果一個請求在開始時是受內存限制的,而在結束時是CPU限制的,那麼就可以將請求的開始部分放在高內存服務中,將結束部分放在高CPU服務中。即便如此,除非你是獨角獸級別的企業,否則分開擴展服務帶來的財務優勢可能也無法抵消額外的複雜性。

另外,你試圖省錢的做法可能會適得其反:

快樂的雲客戶

謊言5:微服務架構性能更高

我在學校學到了一些經驗法則。

  1. 讀取內存所需的時間是讀取二級緩存的10倍
  2. 讀取硬盤驅動器所需時間是讀取內存的10倍。
  3. 從網絡讀取所需的時間是從硬盤讀取的10倍。

這些數字是粗略的近似值。我們來看一下數據中心中的網絡通信與從內存讀取之間的實際差異:2009年,從內存中順序讀取1MB的耗時估計爲250000ns;2019年,在AWS數據中心中,兩個EC2實例之間的通信速度可以達到5Gbps

簡單算一算:

  • 2009年的內存:0.25毫秒內1兆字節
  • 2019年的數據中心:1秒內5Gbit=1秒內0.625GBytes=0.64秒內1兆字節

覺得差距不算大?可我們要意識到:

  • 上面的網絡速度是最好的情況
  • 我們正在對比2009年與2019年的指標
  • 要通過超高速的AWS網絡發送這一兆數據,我們仍然需要從內存中讀取它。

假設你只有一個依賴項,那麼這也意味着幾千倍的速度差距。實際情況中這一差距還會大得多。

難怪我們現在使用字節流來讓每個請求快那麼幾毫秒。當然,對於字節流來說,調試服務間通信也是需要工具的。

謊言6:管理多個服務並不難

軟件工程師喜歡自欺欺人。也許你以前聽過,什麼“不需要很長時間”“請給我幾個小時”或“我可以在週末完成任務”,諸如此類。優秀的項目經理會理解這一點,並將工程師的估算值乘以四(或四十……)。

使用微服務的決策也會有同樣的樂觀情緒。這項工作並不是"爲所有人獲取AWS證書並移動一些代碼"那麼簡單。實踐中會有大量意外開銷。下面是你需要的一些人員和工具類別:

  1. 架構師。你需要一些人來繪製美觀且過於簡化的圖表並做演示。
  2. 發佈管理。現在,你需要協調各個部署並管理多個pipelines。這種協調工作將需要通用工具鏈,還要有團隊來維護這一工具鏈。
  3. DevOps。“常規”工程師既沒有專業知識,也沒有意願來正確配置他們的服務。他們也很難正確處理安全性問題。
  4. 數據工程師。如果你很幸運能夠按照這些建議來成功分解數據存儲,那麼你現在需要一個團隊來將這些數據提取到一個地方進行分析。
  5. 配置文件。雖然一些額外的YAML文件聽起來並不那麼糟糕,但這裏會出現最危險的錯誤。它們也難以測試和調試。

你不僅需要支付所有這些額外人員的薪酬,而且還指數級增加了工程組織中的溝通渠道數量。這會拖慢所有人的步伐。

謊言7:如果你從頭開始精心設計微服務,它們將會起作用

這裏我引用一段文字:

正常運作的複雜系統一定是從一個正常運作的簡單系統演變而來的。從頭開始設計的複雜系統永遠無法正常工作,也無法靠打補丁來正常運作。你必須從一個簡單系統起步。——Gall定律

總結

既然有這麼多如此明顯的缺點,爲什麼微服務還這麼受歡迎呢?

我相信大多數工程師(包括我本人)都有一定程度的自我能力否定傾向。很多時候,我們需要面對自身能力不足以應付的狀況,卻依舊要跨過眼前的障礙。在這種情況下,依靠他人的成果和“最佳實踐”是更安全的。但是,我們很快就認爲這些“最佳實踐”是經過深思熟慮的,或肯定適用於我們的問題。當你啓用更多服務時,雲供應商會受益。微服務倡導者在你購買他們出的書時也會賺錢。他們倆都有動力向你兜售你本來用不到的技術。

不管怎樣,我認爲在某些情況下微服務可能是正確的選擇。如果你是谷歌或Facebook那樣的企業,並且要應對數十種產品上數以十億計的活躍用戶,那麼單體架構肯定是不夠的。如果你有大量可並行化的任務,那麼只用單體也是不行的。

我的目的是要告訴大家,後端服務設計是非常重要的,沒有哪種選擇是銀彈。無論我們是在談論微服務還是單體,SQL還是NoSQL,Python還是Node,本質都一樣。任何技術都不可能完美適應所有用例。

因此,你應該認真思考各種想法,質疑所有假設並清醒地做出架構決策。你的選擇可能會成就或拖垮你的公司。

英文原文:

The seven deceptions of microservices

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