Categories
程式開發

明明是SOA,做著做著就變單體架構了?


我曾經看到過一些文章,開發人員在文章中分享了他們的公司轉向面向服務架構(SOA),最後以失敗告終又遷移回單體架構的案例。當然,這樣做並沒有什麼錯。嘗試新架構本來就是我們工作的一部分,看看新架構是否有效,如果無效就放棄。 SOA 並不適合所有公司,如果不適合你的公司,那就堅持使用單體架構,如果適合,那就要用對它!多年來,我們一直使用大單體代碼庫,於是形成了對某些事情的看法和偏見。如果我們在採用 SOA 時仍然無意識地堅持過去的習慣,可能就意識不到將會在哪些方面遭遇失敗。

在本文中,我們將討論在 SOA 架構中採用單體系統開發實踐會存在哪些風險。

數據和架構高度耦合

想要在 SOA 世界中取得成功,就要避免數據和架構的高度耦合,這點很重要。每一個服務都應該建立在深思熟慮的抽象基礎之上。在某種程度上,服務應該是獨立的——理想情況下,它們不需要知道其他服務的存在。而在以下這些情況下,很容易創建出高度耦合的服務:

  • 為了臨時緊急用例,而構建了臨時的服務。
  • 抽象的成本可能很高。對於企業來說,耦合服務是阻力最小的一條路徑。
  • 有些產品的數據模型是中心化的,如果由數據模型來決定抽象的定義,那麼服務就會存在某種程度的耦合。

如果不注意,我們可能會得到一個高度耦合的服務生態系統。下面是一些耦合的依賴結構:

明明是SOA,做著做著就變單體架構了? 1

左邊:服務 A 調用服務 B,知道服務 B 需要調用服務 C,然後它再調用服務 C 獲取結果。

右邊:服務 A 依賴服務 B,服務 B 依賴服務 C,而服務 C 又依賴服務 A。這是一個依賴循環。

這些結構最終會變成分佈式單體。到了某個時候,你會遇到以下這些問題:

  • 你需要同時更新兩個服務,但不知道先更新哪一個,因為它們彼此依賴。你可以先在其中一個服務中引入非中斷的向後兼容性變更,更新它,然後再更新第二個服務,最後移除不需要的代碼,但這樣做不夠“優雅”。
  • 一個簡單的邏輯可能會跨越多個服務。如果出了問題,只能由對多個服務都了解的人來診斷問題。

“糟糕”的依賴結構是分佈式單體的徵兆——開發單體的痛苦一點都不會少,面向服務的好處卻一點都不會有。此外,高度耦合的服務通常會讓你越陷越深。換句話說,如果不解決這些問題,隨著服務生態系統的增長,情況會變得更糟。

數據耦合

當數據高度耦合時,你需要使用同步 API 和異步任務來保持數據同步。這些同步過程也可能以 Saga 的形式出現。但不管怎樣,你不得不做這些事情:

  • 在基礎設施上大力投入,並進行大量的測試,以確保數據正確同步。
  • 培訓開發人員,這樣他們就不會意外地做出一些會導致數據同步故障的變更。
  • 在多個服務之間同步足夠多的數據之後,開發人員肯定會犯錯——而且可能是災難性的錯誤。
  • 在可見性和異常檢測方面做很多工作,確保在同步過程出現中斷時能夠收到警報。當出現故障時(很可能會出現),你不得不去診斷問題並解決它們。根據系統要求,這兩種方法的成本都非常高。

但是,如果服務生態系統的數據耦合程度很低或者沒有耦合,就可以避免這些麻煩。

下一步

當你發現自己正處在這樣的境地,你該怎麼辦?當然,並不存在放之四海而皆準的解決方案,這裡只是提供一些建議:

  • 首先,如果服務很穩定,並且沒有人在修改它,那就不要管它了。你不會想成為那個打破這種“寧靜”的人。
  • 如果當前的架構出了問題,那麼你要做的第一件事就是分析當前系統的維護成本、修改系統的成本以及新系統所節省的成本之間存在怎樣的關係。做好這件事情並不容易,但如果去做了,你會驚訝地發現使用一些指標(比如交付時間、bug 個數或宕機次數、受影響的用戶數量等)會給你帶來些什麼。在進行了徹底的分析之後,你會發現遷移可能是沒有必要的。但是,如果分析之後確定需要進行遷移,那麼就可以使用分析結果來獲得管理層的支持。
  • 在很多情況下,修改已有的系統是不現實的。遷移會影響企業的運營,成本可能很高。不過沒關係,只要你確保系統的設計是正確的,那就可以繼續這樣下去。

單體系統開發習慣

在 SOA 世界裡,溝通方式、團隊、運營、部署和流程與單體架構世界是不一樣的。我們開發單體系統使用的工具與開發麵向服務系統使用的工具不一定都一樣。如果開發人員(和企業)想要成功轉向 SOA,那麼舊工具和開發習慣也需要被新的工具和開發習慣所取代。

本地開發

在開發單體系統時,我們在本地機器運行大部分的基礎設施。開發人員或多或少都會遇到“它無法在我的機器上運行”的問題。企業會提供引導解決方案來避免這個問題,讓開發人員盡快恢復開發工作。大多數解決方案的目標是讓基礎設施更容易在本地機器上運行。

在開發麵向服務的系統時,在本地機器上運行所有的東西將會遇到伸縮性問題。

  • 在大多數情況下,開發機配置不高,無法在開發期間運行必需的服務。你的機器可能可以運行少量的大型服務,但總有一天配置會跟不上。
  • 要在本地運行服務,開發人員必須知道如何運行(或者部署)不是他們開發的服務。

在本地運行多個服務的解決方案是一種單體思維,以這種方式來開發麵向服務的系統可能會變成分佈式單體。一些公司提供了本地服務開發解決方案,它們通常是這樣的:

  • 通過依賴注入和客戶端開發庫來模擬與其他服務的交互。
  • 在雲端運行一部分基礎設施,在本地運行一部分基礎設施,並將本地的基礎設施與雲端集成在一起。

端到端測試

端到端測試有兩種形式:

  • 自動化測試,通常會藉助 CI/CD 管道。
  • 手動測試,這是開發人員在提交代碼之前和在進行代碼評審時所做的工作。

單體代碼庫的端到端測試比較容易。在準備好適當的數據之後,就可以在本地機器上執行測試。

雖然我們已經進行了單元和集成測試,讓測試人員在本地機器上進行端到端手動測試仍然會進一步提升我們的信心。隨著服務生態系統的發展,服務會越來越多,想要在本地測試所有東西是不可能的事情:

  • 準備數據可能需要很長時間。
  • 難以模擬系統(如消息代理、異步作業隊列等)交互。
  • 開發機無法運行所需的基礎設施。

SOA 架構的端到端測試則不太一樣。進行本地端到端測試的成本非常高,而且不具備伸縮性。

SOA 之所以流行,是因為它可以加快迭代速度。如果你花了大量時間在本地測試上,那就無法利用 SOA 的優勢。你需要放棄在本地測試一切的想法。

你的測試策略取決於你將要採用的本地開發解決方案。下面是一些無需進行本地測試就可以發布代碼的方法:

  • 啟用功能開關,在將功能發布給所有用戶之前就可以在生產環境中進行測試。
  • 金絲雀部署、影子部署、紅黑部署,等等。
  • 在發佈到生產環境之前對變更進行壓力測試。

當然,雖然你放棄了本地測試,但仍然可以通過以下這些途徑來提升信心:

  • 成熟的可觀測性。
  • 警報、工作簿和回滾過程,可在發生故障時進行回滾。

調試

另一方面,如果測試策略發生了變化,那麼調試策略也需要做出改變。

在單體系統中,一個堆棧跟踪信息就足以讓我們著手診斷問題。堆棧跟踪信息為我們指明了方向,我們層層深入,直到找到問題根源。堆棧跟踪信息和傳統的調試工具通常也可以用來調試 SOA 架構中出現的問題,但對某些問題是毫無用處的:

  • 臨時的網絡錯誤。
  • 多個服務之間的數據同步問題。
  • 不正確的配置——連接超時、讀寫超時、工作進程數量、伸縮配置等等。

除了看代碼找問題,你的調試工具箱中也需要包括這些:

  • 用於下載和篩選訪問日誌的腳本。
  • 分佈式跟踪,幫你了解用戶請求的生命週期。
  • 帶有 CPU、內存和 P99 指標的儀錶盤,用於捕獲沒有拋出堆棧跟踪信息的問題。
  • 用於模擬生產環境負載的策略。

結論

在採用 SOA 架構時很容易把系統開發成分佈式單體。我們要避免將單體系統的開發習慣帶到 SOA 架構中。以下是一些需要注意的症狀:

  • 高度耦合的數據或架構。
  • 糟糕的本地多服務開發策略。
  • 糟糕的測試策略,包括在本地測試所有東西。
  • 過時的調試工具,無法診斷網絡和跨服務問題。

單體系統並不是壞東西。很多公司為了轉向 SOA 而大肆投入資源,但卻沒有意識到,在某些情況下,單體可能更適合它們。無論你是堅持使用單體還是採用 SOA,都要避免把系統變成分佈式單體——這才是最糟糕的。

原文鏈接:

https://medium.com/better-programming/signs-of-failing-service-oriented-architecture-fd405c58f75b