我在微服務世界中看到的災難 - joaoqalves

banq發表於2021-04-25

當Martin Fowler在2014年發表有關微服務的文章時,我工作的團隊已經在構建面向服務的架構。這個概念的炒作席捲了世界上幾乎每個軟體團隊。“ Netflix OSS堆疊”是當時最酷的東西,它使全球的工程師都可以在分散式系統中利用Netflix的經驗。六年多以後,如果我們現在研究軟體工程工作,那麼大多數人都在談論微服務的體系結構。 
 

炒作驅動的發展
在2010年代初期,許多組織在其軟體開發週期方面都面臨著挑戰。與其他50、100或200名工程師一起工作的人們在開發環境,繁瑣的QA流程和計劃的部署中苦苦掙扎。儘管馬丁·福勒(Martin Fowler)的《持續交付》一書闡明瞭許多這樣的團隊,但他們開始意識到雄偉的整體/單體Monolith正在為他們帶來組織上的問題。因此,微服務吸引了軟體工程師。在大型專案中引入持續交付或部署要比從開始做起更具挑戰性。 
因此,團隊開始分離出幾十或上百種微服務。他們中的大多數使用“ HTTP上的JSON”(其他人可能會說RESTful)來在這些元件之間進行遠端呼叫的API。人們非常瞭解HTTP協議,這似乎是將整體轉換成較小片段的相對簡單的方法。此時,團隊開始在不到15分鐘的時間內將程式碼部署到生產中。不再有“哦,A團隊中斷了CI管道,我無法部署我的程式碼”,感覺很棒!
但是,大多數工程師忘記了在軟體體系結構級別解決組織問題時,他們也引入了很多複雜性。分散式系統複雜性變得越來越明顯和快速,讓對這些團隊頭疼。
 

現實反擊
進行重大的體系結構更改不是免費的。團隊開始意識到共享資料庫是單點故障。然後,他們意識到將自己的領域分開創造了一個全新的世界:最終的一致性就變成一件需要注意的事情。當您要提取資料的服務關閉時該怎麼辦?問題的數量開始堆積。尋找錯誤,事件,資料一致性問題等,使實現高速開發步伐的希望破滅了。另一個問題是,工程師集中觀察的日誌需要跨越數十項服務,這樣菜鳥菜鳥並糾正其中Bug。
 

災難1:服務太小
由20名工程師組成的團隊維護著50項服務。每人不止一項服務!維護每項服務都是有代價的。這些服務是在不同的時間點啟動的,它們具有不同的體系結構以及業務邏輯和所使用的框架之間的糾纏。
當有人告訴我在服務A中部署新功能時也需要同時在服務B中進行部署。人們不僅可以在其IDE中檢視一個專案,而且還需要同時開啟多個專案才能理解所有這些混亂情況。 
 

災難2:開發環境
跨分散式系統的開發環境存在多個問題,尤其是在大規模環境中:

  1. 在雲提供商中執行200個服務需要多少費用?你能做到嗎?您還可以提升執行它們所需的基礎結構嗎?
  2. 這樣做需要花費多少時間?如果移動工程師開始開發功能時,該新版本中有一組服務,而當它們完成時,有十個新版本部署到生產中怎麼辦?
  3. 那測試資料呢?您是否有所有服務的測試資料?它在整個團隊中是否連貫,因此使用者和其他實體匹配嗎?
  4. 如果要開發多租戶,多區域應用程式,那麼配置和功能標誌呢?您如何與生產保持同步?如果同時更改預設值怎麼辦?

 

災難3:端到端測試
端到端測試與開發環境存在類似的問題。以前,使用虛擬機器或容器建立新的開發環境相對容易。使用Selenium建立一個測試套件來遍歷業務流程並斷言它們在部署新版本之前是有效的,這也非常簡單。
使用微服務後,即使我們可以透過設定環境解決上述所有問題,也無法宣告系統正在執行。
 

災難4:龐大的共享資料庫
在保持整體架構的資料一致性的同時,擺脫整體架構的一種簡單方法是繼續使用共享資料庫。它不會增加運維負荷,並且使逐步分割整塊變得容易。但是,它也具有相當大的缺點。除了明顯的單點故障之外,它還打破了一些面向服務的體系結構的原則,還有更多。您是否為每個服務建立一個使用者?您是否具有細粒度的許可權,因此服務A只能從特定表讀取或寫入?如果有人無意中刪除了索引該怎麼辦?我們如何知道有多少服務正在使用不同的表?那麼縮放呢?
弄清所有這些問題本身就成為一個全新的問題。從技術上講,考慮到資料庫的壽命往往超過軟體,這可能並不容易。使用資料複製解決問題(無論是Kafka,AWS DMS還是其他方法),都需要工程團隊瞭解資料庫的具體資訊以及如何處理重複的事件等等。
 

災難5:API閘道器
API閘道器是面向服務的體系結構中的典型模式。它們有助於使後端與前端使用者脫鉤。當在整個系統上實施端點聚合,速率限制或身份驗證時,它們也很有用。最近,行業一直傾向於後端-前端(backend-for-frontend)的體系結構,在這些體系結構中,為每個前端消費者(iOS,Android,Web或桌面應用程式)部署這些閘道器,從而使它們的發展彼此分離。
就像這個世界上的一切一樣,人們開始為此有了新的,創造性的用例。有時,使移動應用程式向後相容是一個小技巧。突然,您的“ API閘道器”成為了單點故障,因為人們發現在一個地方更容易處理身份驗證,並且其中包含一些意外的業務邏輯。
現在,您有了一個自制的Spring Boot服務來獲取所有流量,而不再是一個整體架構來獲取所有流量!(這個SpringBoot服務又變成單點)。
API閘道器災難的根源在於它消耗了未分頁或返回大量響應的端點。或者,如果在沒有適當的回退機制的情況下進行聚合,則進行單個API呼叫會消耗閘道器的資源。
 

災難6:超時,重試和彈性
分散式系統不斷在部分故障模式下。服務A無法聯絡服務B會怎樣?我們可以重試我們的請求,對吧?但這立即導致我們陷入困境。我見過團隊使用斷路器,然後增加了對下游服務的HTTP呼叫的超時。但它會產生二階效應。
現在,斷路器將取消所有持續很長時間的請求,如果流量增加,則會有越來越多的請求排隊,從而導致比您要修復的情況更糟的情況。我已經看到工程師難以理解佇列理論以及為何存在超時。當團隊開始討論其HTTP客戶端的執行緒池之類的事情時,會發生同樣的事情。
從故障中恢復時的一個棘手的事情是並非所有的人都是冪等的。在某些情況下,我們可能期望我們的消費者即使用者是冪等的,但這意味著我們需要主動決定在每種故障情況下應該採取的措施。消費者是冪等的嗎?我可以重試這個呼叫嗎?我已經看到許多工程師忽略了這些,因為這是一個“極端情況”,後來才意識到他們有一個巨大的資料完整性問題。
重試甚至比所有這些都複雜,即使您設定了備用機制。想象一下,您的移動應用程式中有500萬使用者,並且用於更新使用者首選項的訊息匯流排已經停止工作了一段時間。您為這種情況設定了一個後備機制,該機制透過HTTP API呼叫使用者的首選項服務。現在,此服務突然出現了巨大的流量高峰,並且可能無法應付所有流量。甚至比這更糟:您的服務可能能夠獲得所有這些新請求,但是如果重試機制沒有實現指數退避和抖動( exponential backoff and jitter),您可能會遇到來自移動應用程式的分散式拒絕服務。
 
看到所有這些災難,您仍然愛上分散式系統嗎?
如果我告訴你我只寫了一部分我所見的災難怎麼辦?分散式系統很難掌握,只有最近大多數軟體工程師才始終如一地接觸它們。
好訊息是,我所談論的許多災難都有很好的答案,並且業界已建立了更好的工具,以使它們可以由FAANG以外的組織解決。 (banq注:以上問題基本是微服務邊界設計和運維問題,業界透過引入DDD上下文和服務網格等方式分別來解決,當然也額外帶來複雜性。催生了DevOps)
我仍然喜歡分散式系統,並且我仍然認為微服務是解決組織問題的很好的解決方案。但是,當我們將失敗視為“邊緣案例”或我們認為永遠不會發生的事情時,問題就來了。這些極端情況在一定程度上成為新常態,我們應該應對它們。


 

相關文章