Java程式設計師須知:分散式微服務為什麼很難?

java架構codi發表於2019-04-16

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

Java程式設計師須知:分散式微服務為什麼很難?
在這篇文章中,我們將介紹分散式系統中一些障礙以及人們應對方法。

忘記康威定律(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的服務架構)是非常值得考慮的。

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

相關文章