Categories
程式開發

微服務失敗的11個原因


本文最初發表在 Medium 博客,經原作者 Shekhar Gulati 授權,InfoQ 中文站翻譯並分享。

導讀:微服務是當前流行的架構。簡單地說,微服務就是一種面向服務的軟件架構,在這種架構中,服務器端應用程序是通過組合許多單用途、小容量的網絡服務來構建的。微服務架構讓邊界設計良好的服務的失效互不影響成為可能。但是,微服務和所有的分佈式系統一樣,也會存各種各樣的問題。 Shekhar Gulati 給我們分享了微服務失敗的11 個原因,這些原因還算比較常見,我們相信,他總結的心得對於想要嘗試轉型微服務的公司是大有裨益的,因此,我們翻譯並分享了這篇文章,以饗讀者。

在過去的幾年裡,我對進行數字化轉型的多家產品團隊進行了架構審查。大多數團隊都是遵循微服務架構來構建產品。他們完全有理由使用基於微服務的架構:更快的開發、更好的可擴展性、更小的獨立團隊、獨立的部署、使用正確的技術來完成工作,等等。但是,我經常發現,團隊在微服務方面舉步維艱。他們未能充分利用微服務的優勢。在本文中,我將分享我的觀點,闡述團隊在微服務方面為何舉步維艱的原因。

對於剛接觸微服務的新手來說,我推薦閱讀 Martin Fowler 關於微服務的文章。我很喜歡這篇文章中提到的微服務架構定義。

微服務架構風格就是一種將單個應用程序開發成一套小型服務的方法,每個應用程序都在自己的進程中運行,並與輕量級機制(通常是 HTTP 資源 API)進行通信。這些服務是圍繞業務功能而構建的,並且可以由完全自動化的部署機制來獨立部署。這些服務只有最低限度的集中管理,可以用不同的編程語言編寫,並使用不同的數據存儲技術。

原因 1:管理層低估了開發微服務的複雜性

我曾與許多非常看好微服務的客戶一起合作過。對他們來說,微服務就是解決他們所有問題的靈丹妙藥。在與我討論中,我發現大多數團隊及其管理層低估了微服務開發的複雜性。

要開發微服務,開發人員需要一個高效的本地環境設置。

隨著系統中的服務數量開始增加,在一台機器上運行應用程序的子集變得越來越困難。特別是當你使用消耗較多內存的語言(如 Java)構建應用程序時,更是如此。

下面是與本地開發設置相關的要點。

  1. 本地開發的第一個重要方面是要有一個好的開發機器。我發現,大多數組織想要使用所有最新的、最先進的技術,但卻不想替換掉糟糕的 Windows 開發機器。開發人員受限於他們的開發機器。我曾見過開發人員使用 VDI 映像或配置交叉的機器來構建基於微服務的系統。這降低了他們的工作效率,使他們無法完全投入工作。使用糟糕的開​​發機器帶來的副作用就是,開發人員無法得到快速的反饋。如果你知道必須等待數分鐘才能運行集成測試套件,那麼你就不會編寫更多的集成測試套件,免得給你帶來痛苦。糟糕的開發機器將會導致糟糕的開發實踐。
  2. 一旦你為開發人員配備了合適的開發機器,那麼下一步就是確保所有服務都使用構建工具。你應該能夠在一台新機器上構建整個應用程序,而不需要進行太多配置。根據我在微服務方面的經驗,使用能夠構建整個應用程序的根構建腳本也會有所幫助。
  3. 下一個要點就是要讓開發人員能夠輕鬆地在他們的系統上運行應用程序的各個部分。在配置了所有端口和卷的情況下,你應該使用多個 docker-compose 文件來提供不同的服務。
  4. 接下來,如果你使用的是類似於 Kubernetes 的容器編排工具,那麼你應該投資類似於 Telepresence 這樣的工具,以便輕鬆調試 Kubernetes 集群中的應用程序。

如果組織對微服務開發的複雜性缺乏理解,那麼團隊的速度將會隨著時間的推移而下降。

原因 2:未將庫和工具更新到最新版本

在我看來,我發現一個新的平台已經成為一種遺產。團隊沒有確保依賴項是最新的版本,或者將像數據庫這樣的工具升級到最新版本。因此,兩年前開始的現代化改造,如今已經背負了長達數月的技術債務。

幾年前,許多團隊開始將 Spring Cloud Netflix OSS 項目用於微服務。他們使用像 Kubernetes 這樣的容器編排工具,但是因為他們是從 Netflix OSS 開始的,所以他們沒有使用 Kubernetes 提供的所有功能。當 Kubernetes 內置了服務發現時,他們仍然使用 Eureka 作為服務發現。此外,通過類似 Lstio 這樣的服務網格,你就可以擺脫 Netflix OSS 提供的大部分服務。這有助於降低複雜性,並將許多“橫向關注點”(cross cutting concerns)轉移到平台上。

需要記住的另一點是,要使所有服務的依賴項版本保持同步。我最近在幫助一個客戶,他使用 Spring Boot 來構建微服務。在過去兩年中,他們已經構建了 20 多個 Spring Boot 服務。在他們的環境中,他們使用的 Spring Boot 版本從 1.5 到 2.1 不等。這意味著當有人設置他們的機器時,他們必須下載多個版本的 Spring Boot。此外,他們還缺乏自版本 1.5 以來在 Spring Boot 中所做的許多改進。

我們的建議是,組織應該在其積壓中為這些升級創建技術債務項。這些技術債務項應作為架構委員會會議的一部分加以討論,並定期予以解決。在我的上一個項目中,我們每三個月設置為期一周的 sprint,將所有依賴項更新到最新版本。

原因 3:利用共享服務促進本地開發

由於本地開發狀況不佳,大多數團隊開始依賴於共享環境來獲得關鍵服務。開發人員機器中的第一件事就是數據庫。大多數年輕的開發人員並沒有意識到基於共享數據庫的開發是“邪惡的”。下面,是我在共享數據庫中看到的主要問題:

  1. 團隊成員必須建立一個工作的社會契約,以避免最後寫入者勝出(Last write wins,LWW)問題。一個開發人員可以刪除其他開發人員為他們的工作編寫的數據。這種工作方式既痛苦又容易失敗,遲早會影響整個團隊。
  2. 開發人員害怕實驗,因為他們的工作會影響其他團隊成員。我們都知道,更好的學習方法是實驗和快速反饋。有了共享數據庫,就可以進行實驗了。我們需要進行實驗,以提出數據庫模式,並執行任務,如性能調優之類。
  3. 另一個副作用就是,很難單獨測試更改。你的集成測試將變得不可靠,從而進一步降低了開發速度。
  4. 共享數據庫必須像寵物一樣對待,因為你不希望它出現不一致和不可預測的狀態。你可能會遇到這樣一種場景,開發人員希望在表是空的時候測試邊緣情況,但其他開發人員需要一個表來記錄。
  5. 只有共享數據庫擁有系統工作所需的所有數據。隨著時間的推移,團隊成員失去了更改的可追溯性,因此沒有人知道,他們該如何在他們的機器上複製相同的設置。唯一的方法是獲取完整的數據庫轉儲並使用它。
  6. 如果未連接到網絡,就很難開展工作。這種情況通常發生在你通勤時間過長或乘飛機的時候。

數據庫只是共享服務的一個示例,但它也可以是消息隊列、集中緩存(如 Redis)或任何其他服務可以發生改變的服務。

解決這一問題的最好方法是,讓開發人員可以輕鬆地在他們的機器上運行數據庫(作為 Docker 容器),並投資創建 SQL 腳本來設置模式和初始主數據。這些 SQL 腳本應該保存在版本控制中,並像維護任何其他代碼一樣進行維護。

原因 4:版本控制託管平台缺乏可見性

我曾與一個客戶進行合作,當時,他們的版本控制系統中有 1000 多個倉庫。他們使用的是 Gitlab 版本控制平台。他們有 5 個產品,每個產品都由多個微服務組成。我問他們的第一個問題是幫助我們了解哪些服務及其各自代碼庫是產品 A 的一部分。他們的首席架構師不得不花一天時間弄清楚所有產品 A 的倉庫。一天過去了,她還是不能確定是否弄清楚了所有的服務。

解決這個問題的最好方法是,從一開始就以某種方式對你的微服務進行分組,這樣,你就可以隨時了解產品的生態系統。 Gitlab 提供了一種方法來創建一個組,然後在其中創建項目倉庫。 Github 並沒有組功能,因此你可以使用主題或命名約定來實現它。

我個人更喜歡 mono repos,因為我發現他們真的很方便。我遇到的大多數開發人員都認為它是一種反模式。我同意 Dan Lua 的觀點,他提到了 mono repo 的以下好處:

  • 簡化的組織
  • 簡化的依賴關係
  • 工具
  • 跨項目變更

原因 5:服務沒有明確的定義

大多數團隊並不知道什麼應該被視為服務。關於究竟是什麼構成一個單一的微服務,人們對此存在很多混淆的認識和困惑的概念。讓我們舉一個例子,假設你的應用程序具有類似插件的架構,在這個架構中,你集成了多個第三方服務。每個集成應該是一個微服務嗎?我看到很多團隊,都在為每個集成創建一個微服務。隨著集成數量的增加,這種情況很快就會失控,以至於無法管理。這些服務通常太小,以至於將它們作為單獨的進程運行,會增加更多的開銷。

我認為,哪怕只擁有少量的大型服務,總比提供太多的小型服務要好得多。我將從創建一個服務開始,該服務對業務組織中的整個部門進行建模。這也符合 DDD(領域驅動設計, Domain Driven Design)。我將一個域分為子域和有界上下文。有界上下文表示公司內部的一個部門,如財務部門和營銷部門。你可能認為,這會導致大型服務的出現,你是對的。但是,以我的經驗來看,將整體重構為微服務總之比反之更容易。隨著你的知識經驗越來越多,你可以轉向代表更小問題的細粒度微服務。你可以應用單一責任原則(Single Responsibility Principle)來了解你的微服務是否變得過大,做的事情是否過多。然後,你可以將它分解成更小的獨立服務。任何服務都不應該直接與其他服務的數據庫通信。他們應該只通過已發布的合同進行溝通。你可以在 Microservices.io 網站上閱讀更多關於按子域模式分解 的內容。

我也遵循了 Backendlore 文檔中提到的建議。這個建議可以幫助將服務限制在服務通信上,而服務通信是微服務系統性能低下的首要原因。如果兩條信息相互依賴,那麼它們應該屬於同一個服務器。換句話說,服務的自然邊界應該是其數據的自然邊界。

原因 6:代碼重用策略不明確

我曾經和一個客戶合作,該客戶在他們所有基於 Java 的微服務複製了四個與特定問題相關的 Java 文件。因此,如果在該代碼中發現 bug 的話,就需要將其修復應用到所有地方。我們都知道,在時間緊迫的情況下,我們會錯過將更改應用於一個或多個服務。這樣會浪費更多的時間,增加挫敗感。

這並非說開發團隊不懂正確的事情。但是,按照組織結構,人們總是默認採用簡單且容易出錯的做事方式。

正確的方法是,使用像 Bintray 或 Nexus 這樣的工件管理器,並在那裡發布依賴項。然後,每個微服務都應該依賴於這個庫。你需要構建工具,以便在發布新版本的庫時,所有的微服務都應該進行更新和重新部署。

使用微服務並不意味著你不應該使用迄今為止對我們有用的最佳實踐。你需要對工具進行投資,使微服務的升級變得更容易,這樣人們就不必這樣做了。

在沒有適合的工具和自動化的情況下,使用微服務會導致災難。

原因 7:多語言編程設計

我發現團隊使用多種編程語言、多種數據庫、多種緩存,並以最佳工具的名義進行工作。所有這些都在項目的初始階段起作用,但隨著你的產品投入生產,這些選擇開始顯露出它們的本色。原因就像我們在構建 JavaSpringBoot 應用程序,但是我們意識到 Java 佔用了更多的內存,且性能也很差,所以我們決定改用 Node.js。在我上一次任務中,我向團隊解釋說他們的推理能力很弱。

  1. Note.js 比 Java 性能更好。 如果你的工作負載是基於 I/O 的,Node.js 通常會表現的更好。但在任何計算密集型的工作負載上,Java 都勝過 Node.js。通過響應式範式(reactive paradigm),你可以使用 Java 為 I/O 工作負載提供更好的性能。在 I/O 工作負載方面,Spring Boot Reactor 的性能相當於 Node.js。
  2. Node.js 比 Java 消耗更少的內存。 這在一定程度上是正確的說法。 Java Spring Boot 應用程序並不像大多數想像的那麼糟糕。我在一個 Spring Boot Java 微服務上運行了負載測試,內存消耗仍然沒超過 1 GB。你可以通過 OpenJ9 JVN,限制對類路徑的依賴,並通過調優默認的 JVM 參數來優化 Java 內存利用率。此外,在 Java 中還有 Spring Boot 的新替代品,如 Micronaut 和 Quarkus,它們消耗的內存相當於 Node.js。
  3. Node.js 比 Java 效率更高。 這取決於編寫代碼的開發人員。使用靜態類型和靜態分析工具的 Java 可以幫助在開發生命週期的早期發現問題。

大多數情況下,這完全取決於上下文。如果你的開發人員還不夠成熟的話,那麼無論你使用什麼編程語言,你開發的都將是糟糕的產品。

我建議一家組織要發布一個團隊可以使用的語言列表。我認為 2~3 就是個很不錯的數字。另外,要列出一種語言比另一種語言更適合的理由。

在選擇一門語言之前,你應該考慮以下一些問題:

  1. 找到成熟的企業軟件開發人員有多容易?
  2. 重新培訓開發人員掌握新技術有多容易?我們發現 Java 開發人員可以相對容易地學習 Golang。
  3. 初始團隊之外的開發人員貢獻、轉移和維護其他人編寫的代碼有多容易?
  4. 就工具和庫的方面而言,生態系統有多成熟?

這不僅僅局限於編程語言,也適用於數據庫領域。如果你的系統中已經有了 MongoDB,那麼你為什麼要在生態系統中使用 ArangoDB 呢?它們都主要是文檔數據庫。

要始終考慮使用多種技術的維護和操作方面。

原因 8:人員的依賴性

這並非微服務特有的現象,但在微服務生態系統中卻變得更加普遍。原因是,大多數團隊專注於他們的特定服務,因此他們並不了解完整的生態系統。在我與不同客戶的工作中,我發現,只有一群架構師了解整體情況。但是,這些架構師的問題在於,他們並不積極參與日常活動,因此他們對開發的影響力是有限的。

我認為最好的辦法是,確保所有團隊都有一個架構團隊的代表,這樣他們就可以使他們的團隊與整個架構團隊的路線圖和目標保持一致了。要成為一個成熟的組織,你需要投資於建立一個輕量級的治理。

原因 9:文檔的缺乏

在過去幾年裡,我們接觸過的大多數組織都在文檔方面遇到了困難。大多數開發人員和架構師要么不去編寫文檔,要么編寫的文檔毫無用處。即使他們想寫,他們也不知道應該如何記錄他們的架構。

我們至少應該記錄以下內容:

  1. 設計文檔。
  2. C4 模型中的上下文和容器圖。
  3. 架構決策記錄的形式跟踪關鍵架構決策。
  4. 開發人員入門指南。

我建議在版本控制系統中維護所有的文檔。

原因 10:功能超過平台成熟度

我已經在其他觀點中簡要地提到了這個原因,但我認為,它值得作為一個頂級原因來提及。微服務要比傳統的單體式應用(monolithic application)更為複雜,因為你正在構建一個包含許多活動部件的分佈式系統。大多數開發人員還不了解系統的不同故障模式。大多數微服務在構建時都考慮了令人快樂的路徑。因此,如果你的管理層只想僅僅關注功能,那麼你注定會失敗。因為在薄弱平台上構建的功能是無法提供價值的。

組織需要有平台思維。平台思維可不僅僅意味著使用容器和 Kubernetes。它們是解決方案的一部分,但本身並非完整的解決方案。你還需要考慮分佈式跟踪、可觀察性、混沌測試、函數調用與網絡調用、服務間通信的安全服務、可調試性等等。這需要在構建正確的平台和工具團隊方面付出認真的努力和投資。

如果你是一家資源有限的初創公司,我的建議是,你要重新考慮微服務戰略。了解你所面臨的問題是什麼。

原因 11:缺乏自動化測試

大多數團隊都知道自動化測試對產品的整體質量有多重要,但是他們仍然沒有做到。微服務架構為測試地點和測試方式提供了更多的選擇。如果你不進行徹底的自動化測試,那麼你將會失敗得很慘。關於這一點,我不會再贅述,因為網上很多人都寫過這方面的內容了。下圖是我從微服務測試的文章找到的,這篇文章來自 Martin Fowler 的網站,討論了基於微服務的系統的測試金字塔。

微服務失敗的11個原因 1

微服務測試金字塔

作者介紹:

Shekhar Gulati,軟件工程師。崇尚自學成才。信奉禁慾主義者的苦修者。

原文鏈接:

https://medium.com/xebia-engineering/11-reasons-why-you-are-going-to-fail-with-microservices-29b93876268b