1. 程式人生 > >Java程序員須知:分布式微服務為什麽很難?

Java程序員須知:分布式微服務為什麽很難?

狀態 混沌 期待 無序 增加 現在時間 組件 動態 管理

現在,我們不斷地贊美雲原生cloud native架構(容器化和微服務),然而現實是大多數公司仍然運行單體系統。為什麽?這不是因為我們非常不時尚,而是因為分布式是非常困難的。盡管如此,它仍然是創建超大規模的、真正彈性的和快速響應的系統的唯一途徑,因此我們必須圍繞它進行整合。

技術分享圖片
在這篇文章中,我們將介紹分布式系統中一些障礙以及人們應對方法。

忘記康威定律(Conway’s Law),分布式系統遵循的是墨菲定律:“任何可能出錯的地方都會出錯。

在分布式系統的大規模上看,統計不是你的朋友(事後諸葛亮)。你所擁有的任何服務器實例越多,其中一個或多個當機的可能性就越高。而且極可能在同一時間。

在你收到警報郵件之前,服務器已經當機,網絡將會丟失數據包,磁盤將失敗,虛擬機將意外終止。

有一些在單體架構中的保證在分布式系統中就不再會得到保障。組件(現在的服務)不再以可預測的順序啟動和停止。服務可能意外重新啟動,更改其數據庫狀態或版本。結果是,沒有服務可以對另一個服務進行假設 - 系統不依賴於1對1的通信。

許多從故障中恢復的傳統機制可能會使分布式環境惡化。強力重試可能會使您的網絡被洪水般數據包淹沒,備份恢復也並不簡單。過去解決所有這些問題的設計模式,都需要重新思考和測試。

如果沒有錯誤,分布式系統會很容易。樂觀主義會造成對安全的錯覺。分布式系統的設計必須具有彈性,能夠容納接受所有可能的發生錯誤,而不影響日常業務。

這裏通訊會失敗

在不可靠(即分布式)系統中,傳統應用程序消息傳遞有兩種高級方法:

  1. 可靠但緩慢:保存每條消息復制副本,直到您確認職責鏈中的下一個進程已經為此承擔全部責任。
  2. 不可靠但快速:將多個復制副本發送給潛在的多個接受人,並允許消息丟失和重復。

我們在這裏討論的可靠和不可靠的應用級通信與網絡可靠性(例如TCP與UDP)是不同。想象一下,兩個通過TCP直接發送消息(比如RPC通訊)的無狀態服務。即使TCP是可靠的網絡協議,這也不是可靠的應用級通信。任何服務都可能會丟失並丟失正在處理的消息,因為無狀態服務不能安全地保存正在處理的數據。(banq註:這是針對同步的RPC框架,比如國內的Dubbo或谷歌的gRPC)

我們可以通過在每個服務之間放置有狀態的隊列來使此設置應用程序級別可靠,以保存每個消息,直至其完全處理(banq註:引入消息隊列)。這樣做的不足之處在於它會慢一點,但是我們可能很樂意與之相處,因為如果它使生活更簡單,特別是如果我們使用可管理的有狀態的隊列服務時,那麽我們就不必擔心規模和彈性問題。

可靠的方法是可預測的,但會涉及到延遲(延遲)和復雜性:大量確認消息和彈性保存數據(狀態),直到您已經從職責鏈中的下一個服務確認完成了他們已經承擔責任。

一個可靠的方法卻不能保證快速的傳遞,但它確保所有的消息將最終至少一次傳遞。在每個消息至關重要且不能容忍丟失(例如信用卡交易)的環境中,這是一個很好的方法。AWS簡單隊列服務(Amazon的托管隊列服務)是以可靠方式使用狀態服務的一個例子。(banq註: Apache kafka提供類似正好一次的有效一次傳遞也是適用類似信用卡之類的交易)

第二種情況是,使用不可靠的方法可以實現端對端通訊得更快(比如RPC同步方式),但這意味著服務通常不得不期待重復和無序消息,並且一些消息將丟失。當消息是時間敏感的(即,如果他們不迅速采取行動,就不值得采取行動)或稍後的數據只是覆蓋早期的數據,這種情況下可能會使用不可靠的通信。對於非常大規模的分布式系統,可以使用不可靠的消息傳遞,因為它的開銷小且要快得多。然而,微服務設計卻需要處理應對消息的丟失和重復。

在上述每種情況方法中,存在許多變量(例如,有保證和不保證的順序性),所有這些變量需要在速度、復雜性和故障率方面進行不同的權衡。

一些系統可以同時使用上述多種方法,這取決於正在發送的消息的類型甚至系統上的當前負載。如果您有很多行為不同的服務,就很難正確恰當使用這些方法。需要在其API中明確定義服務的行為。為您的系統中的服務進行約束或推薦的通信行為的定義通常是有意義的,以獲得一定程度的一致性。

現在時間是幾點?

在分布式系統中沒有這樣的常見的所謂全球時鐘。例如,在團體聊天中,我的評論和我的朋友在澳大利亞、哥倫比亞和日本發表的評論的出現將不會遵循嚴格順序先後出現。沒有任何保證機制保證我們看到的都是相同的時間表 - 雖然總有一個順序,但是前提是我們有段時間先不說話。

基本上,在分布式系統中,每臺機器都有自己的時鐘,整個系統沒有一個正確的時間。機器時鐘可能會進行同步,但是即使在同步時傳輸時間也會不同,物理時鐘也會以不同的速率運行,所以一切都會立即失去同步。

在單個機器上,一個時鐘可以為所有線程和進程提供通用的時間。在分布式系統中,這在物理上都不可行。

在我們的新世界中,時鐘時代不再提供無可置疑的順序定義。在微服務世界中並不存在“什麽時候”的單一概念,所以,我們的設計不應該依賴於服務間消息。

真相就在那裏?

在分布式系統中,沒有全局共享內存,因此沒有單一版本的真相。數據將分散在不同物理機器上。此外,任何指定的數據在機器之間更可能處於相對較慢和無法訪問的傳輸中,而不像在單體架構下的情況。因此,真正運行情況需要基於當前的當地的信息。

這意味著系統的不同部分的運行情況並不總是一致的。在理論上,它們最終應該在整個系統中傳播消息時變得一致,但是如果數據不斷變化,我們可能永遠不會達到完全一致的狀態,除非關閉所有新的輸入和等待。因此,服務必須處理這樣一個事實,即他們相互調用時可能會因為自己的問題而獲得“舊”的或者不一致的信息。

說話快點!

在一個單體的應用程序中,大多數重要的通信發生在一個組件和另一個組件之間的單個進程中。流程內部的通信非常快,所以很多內部消息的傳遞不是問題。但是,一旦將單體組件拆分成單獨的服務,通常會在不同的機器上運行,那麽事情變得越來越復雜。

假設你知道如下背景知識:

  1. 在最好的情況下,將消息從一臺機器發送到另一臺機器比將內部從一個組件傳遞到另一個組件時要花費大約100倍的時間。
  2. 許多服務使用基於文本的RESTful消息進行通信。RESTful消息是跨平臺的,易於使用,讀取和調試,但傳輸速度慢。相比之下,與二進制消息協議配對的遠程過程調用(RPC)消息不是人類可讀的,因此更難調試和使用,但傳輸和接收速度要快得多。通過RPC方式發送消息的速度快20倍,比如gRPC相對RESTful而言。

在分布式環境中的結果卻是

  1. 你應該發送更少的消息。您可以選擇在分布式微服務器之間發送的消息數量少於在單件中的組件之間發送的消息量,因為每個消息都會引入延遲(即延遲)。
  2. 考慮更有效地發送消息。對於您發送的內容,您可以通過使用RPC而不是REST來傳輸消息來幫助您的系統運行得更快。或者甚至就使用UDP並手工自己處理不可靠性。(banq註:RPCC通訊屬於通訊快但不可靠類型)

狀況報告?

如果您的系統可以次秒級(時間上短於1秒)速度更改,這是動態管理的分布式架構的目標,那麽您需要以這種速度了解問題。許多傳統的日誌工具並不是為了跟蹤這種情況而設計的。你需要確保能使用它們。

測試破壞

了解您的分布式系統是否正常工作並從不可預測的錯誤中恢復的唯一方法是:持續攻克這些錯誤並持續修復系統。Netflix使用Chaos Monkey(混沌猴)隨機造成故意的崩潰測試。您的系統的彈性和完整性是需要測試的,同樣重要的是,測試您的日誌記錄,以確保如果發生錯誤,您可以追溯地診斷和修復它 - 即使您的系統已經恢復在線運行。

這聽起來很困難 我一定需要嗎?

創建分布式、可擴展的、有彈性的系統是非常困難的,特別是對於有狀態的服務(服務需要寫數據庫保存變動的數據)。現在是決定是否需要它的時候了。你的客戶需求是否可以容忍慢一點響應還是小型規模系統?這樣,您可以先設計一個更小、更慢、更簡單的系統,並在構建專業知識同時逐步增加更多的復雜性。

像AWS,Google和Azure這樣的雲計算提供商也正在開發和推出這些大部分功能,特別是彈性狀態(托管的消息隊列和數據庫)。這些服務似乎是昂貴的,但構建和維護復雜自己的分布式服務也是昂貴的。

任何雖然可能限制您但是會處理這些復雜性的框架(如Linkerd或Istio或Azure的服務架構)是非常值得考慮的。

關鍵的挑戰是不要低估建立正確的彈性和高度可擴展的服務的難度。如果決定你真的需要它,那麽全面教育大家,引入有用的約束,逐漸做好一切,並期待挫折和成功。

Java程序員須知:分布式微服務為什麽很難?