分布式系統(tǒng)中經(jīng)典的八個謬誤
你在分布式系統(tǒng)上工作嗎?微服務,Web API,SOA,Web 服務器,應用服務器,數(shù)據(jù)庫服務器,緩存服務器,負載均衡器 - 如果這些描述了系統(tǒng)設計中的組件,那么答案是肯定的。分布式系統(tǒng)由許多計算機組成,這些計算機協(xié)調(diào)以實現(xiàn)共同的目標。
20 多年前,Peter Deutsch 和 James Gosling 定義了分布式計算的 8 個謬誤。這些是許多開發(fā)人員對分布式系統(tǒng)做出的錯誤假設。從長遠來看,這些通常被證明是錯誤的,導致難以修復錯誤。
八個謬誤是:
- 網(wǎng)絡可靠。
- 延遲為零。
- 帶寬是無限的。
- 網(wǎng)絡是安全的。
- 拓撲不會改變。
- 有一個管理員。
- 運輸成本為零。
- 網(wǎng)絡是同質(zhì)的。
讓我們來看看每個謬誤,討論問題和潛在的解決方案。
1、網(wǎng)絡可靠
問題
通過網(wǎng)絡呼叫將失敗。
今天的大多數(shù)系統(tǒng)都會調(diào)用其他系統(tǒng)。您是否正在與第三方系統(tǒng)(支付網(wǎng)關(guān),會計系統(tǒng),CRM)集成?你在做網(wǎng)絡服務電話嗎?如果呼叫失敗會發(fā)生什么?如果您要查詢數(shù)據(jù),則可以進行簡單的重試。但是如果您發(fā)送命令會發(fā)生什么?我們舉一個簡單的例子:
var creditCardProcessor = new CreditCardPaymentService();
creditCardProcessor.Charge(chargeRequest);
如果我們收到 HTTP 超時異常會怎么樣?如果服務器沒有處理請求,那么我們可以重試。但是,如果它確實處理了請求,我們需要確保我們不會對客戶進行雙重收費。您可以通過使服務器具有冪等性來實現(xiàn)此目的。這意味著如果您使用相同的收費請求撥打 10 次,則客戶只需支付一次費用。如果您沒有正確處理這些錯誤,那么您的系統(tǒng)是不確定的。處理所有這些情況可能會非常復雜。
解決方案
因此,如果網(wǎng)絡上的呼叫失敗,我們能做什么?好吧,我們可以自動重試。排隊系統(tǒng)非常擅長這一點。它們通常使用稱為存儲和轉(zhuǎn)發(fā)的模式。它們在將消息轉(zhuǎn)發(fā)給收件人之前在本地存儲消息。如果收件人處于脫機狀態(tài),則排隊系統(tǒng)將重試發(fā)送郵件。MSMQ 是這種排隊系統(tǒng)的一個例子。
但是這種變化將對您的系統(tǒng)設計產(chǎn)生重大影響。您正在從請求/響應模型轉(zhuǎn)移到觸發(fā)并忘記。由于您不再等待響應,因此您需要更改系統(tǒng)中的用戶行程。您不能只使用隊列發(fā)送替換每個 Web 服務調(diào)用。
結(jié)論
你可能會說網(wǎng)絡現(xiàn)在更可靠 - 而且它們是。但事情發(fā)生了。硬件和軟件可能會出現(xiàn)故障 - 電源,路由器,更新或補丁失敗,無線信號弱,網(wǎng)絡擁塞,嚙齒動物或鯊魚。是的,鯊魚:在一系列鯊魚叮咬之后,谷歌正在加強與 Kevlar 的海底數(shù)據(jù)線。
還有人為因素。人們可以開始 DDOS 攻擊,也可以破壞物理設備。
這是否意味著您需要刪除當前的技術(shù)堆棧并使用消息傳遞系統(tǒng)?并不是的!您需要權(quán)衡失敗的風險與您需要進行的投資。您可以通過投資基礎架構(gòu)和軟件來最小化失敗的可能性。在許多情況下,失敗是一種選擇。但在設計分布式系統(tǒng)時,您確實需要考慮失敗的問題。
2、延遲是零
問題
通過網(wǎng)絡撥打電話不是即時的。
內(nèi)存呼叫和互聯(lián)網(wǎng)呼叫之間存在七個數(shù)量級的差異。您的應用程序應該是網(wǎng)絡感知。這意味著您應該清楚地將本地呼叫與遠程呼叫分開。讓我們看看我在代碼庫中看到的一個例子:
var viewModel = new ViewModel();
var documents = new DocumentsCollection();
foreach (var document in documents)
{
var snapshot = document.GetSnapshot();
viewModel.Add(snapshot);
}
沒有進一步檢查,這看起來很好。但是,有兩個遠程呼叫。第 2 行進行一次調(diào)用以獲取文檔摘要列表。在第 5 行,還有另一個調(diào)用,它檢索有關(guān)每個文檔的更多信息。這是一個經(jīng)典的 Select n + 1 問題。為了解決網(wǎng)絡延遲問題,您應該在一次調(diào)用中返回所有必需的數(shù)據(jù)。一般的建議是本地調(diào)用可以細粒度,但遠程調(diào)用應該更粗粒度。這就是為什么分布式對象和網(wǎng)絡透明度的想法死了。但是,即使每個人都同意分布式對象是一個壞主意,有些人仍然認為延遲加載總是一個好主意:
var employee = EmployeeRepository.GetBy(someCriteria)
var department = employee.Department;
var manager = department.Manager;
foreach (var peer in manager.Employees;)
{
// do something
}
您不希望財產(chǎn)獲取者進行網(wǎng)絡呼叫。但是,每個。在上面的代碼中調(diào)用實際上可以觸發(fā)數(shù)據(jù)庫之旅。
解決方案
帶回您可能需要的所有數(shù)據(jù)
如果您進行遠程呼叫,請確?;謴涂赡苄枰乃袛?shù)據(jù)。網(wǎng)絡通信不應該是嘮叨的。
將 Data Closer 移動到客戶端
另一種可能的解決方案是將數(shù)據(jù)移近客戶端。如果您正在使用云,請根據(jù)客戶的位置仔細選擇可用區(qū)。緩存還可以幫助最小化網(wǎng)絡呼叫的數(shù)量。對于靜態(tài)內(nèi)容,內(nèi)容交付網(wǎng)絡(CDN)是另一個不錯的選擇。
反轉(zhuǎn)數(shù)據(jù)流
刪除遠程調(diào)用的另一個選項是反轉(zhuǎn)數(shù)據(jù)流。我們可以使用 Pub / Sub 并在本地存儲數(shù)據(jù),而不是查詢其他服務。這樣,我們就可以在需要時獲取數(shù)據(jù)。當然,這會帶來一些復雜性,但它可能是工具箱中的一個很好的工具。
結(jié)論
雖然延遲可能不是 LAN 中的問題,但當您轉(zhuǎn)移到 WAN 或 Internet 時,您會注意到延遲。這就是為什么將網(wǎng)絡呼叫與內(nèi)存中的呼叫明確分開是很重要的。在采用微服務架構(gòu)模式時,您應該牢記這一點。您不應該只使用遠程調(diào)用替換本地呼叫。這可能會使你的系統(tǒng)變成分布式的大泥球。
3、帶寬是無限的
問題
帶寬是有限的。
帶寬是網(wǎng)絡在一段時間內(nèi)發(fā)送數(shù)據(jù)的容量。到目前為止,我還沒有發(fā)現(xiàn)它是一個問題,但我可以看到為什么它在某些條件下可能是一個問題。雖然帶寬隨著時間的推移而有所改善,但我們發(fā)送的數(shù)據(jù)量也有所增加。與通過網(wǎng)絡傳遞簡單 DTO 的應用相比,視頻流或 VoIP 需要更多帶寬。帶寬對于移動應用程序來說更為重要,因此開發(fā)人員在設計后端 API 時需要考慮它。
錯誤地使用 ORM 也會造成傷害。我見過開發(fā)人員在查詢中過早調(diào)用.ToList()的示例,因此在內(nèi)存中加載整個表。
解決方案
領(lǐng)域驅(qū)動的設計模式
那么我們怎樣才能確保我們不會帶來太多數(shù)據(jù)呢?域驅(qū)動設計模式可以幫助:
首先,您不應該爭取單一的企業(yè)級域模型。您應該將域劃分為有界上下文。
要避免有界上下文中的大型復雜對象圖,可以使用聚合模式。聚合確保一致性并定義事務邊界。
命令和查詢責任隔離
我們有時會加載復雜的對象圖,因為我們需要在屏幕上顯示它的一部分。如果我們在很多地方這樣做,我們最終會得到一個龐大而復雜的模型,對于寫作和閱讀來說都是次優(yōu)的。另一種方法可以是使用命令和查詢責任隔離 - CQRS。這意味著將域模型分為兩部分:
- 在寫模式將確保不變保持真實的數(shù)據(jù)是一致的。由于寫模型不關(guān)心視圖問題,因此可以保持較小且集中。
- 該讀取模型是視圖的擔憂進行了優(yōu)化,所以我們可以獲取所有所需的特定視圖中的數(shù)據(jù)(例如,我們的應用程序的屏幕)。
結(jié)論
在第二個謬誤(延遲不是 0)和第三個謬誤(帶寬是無限的)之間有延伸,您應該傳輸更多數(shù)據(jù),以最大限度地減少網(wǎng)絡往返次數(shù)。您應該傳輸較少的數(shù)據(jù)以最小化帶寬使用。您需要平衡這兩種力量,并找到通過線路發(fā)送的 正確 數(shù)據(jù)量。
雖然您可能不會經(jīng)常遇到帶寬限制,但考慮傳輸?shù)臄?shù)據(jù)非常重要。更少的數(shù)據(jù)更容易理解。數(shù)據(jù)越少意味著耦合越少。因此,只傳輸您可能需要的數(shù)據(jù)。
4、網(wǎng)絡是安全的
問題
網(wǎng)絡并不安全。
這是一個比其他人更多的媒體報道的假設。您的系統(tǒng)僅與最薄弱的鏈接一樣安全。壞消息是分布式系統(tǒng)中有很多鏈接。您正在使用 HTTPS,除非與不支持它的第三方遺留系統(tǒng)進行通信。您正在查看自己的代碼,尋找安全問題,但正在使用可能存在風險的開源庫。一個 OpenSSL 的漏洞允許人們通過盜取 SSL / TLS 保護的數(shù)據(jù)。Apache Struts 中的一個錯誤允許攻擊者在服務器上執(zhí)行代碼。即使你正在抵御所有這些,仍然存在人為因素。惡意 DBA 可能錯放數(shù)據(jù)庫備份。今天的攻擊者掌握著大量的計算能力和耐心。所以問題不在于他們是否會攻擊你的系統(tǒng),而是什么時候。
解決方案
深度防御
您應該使用分層方法來保護您的系統(tǒng)。您需要在網(wǎng)絡,基礎架構(gòu)和應用程序級別進行不同的安全檢查。
安全心態(tài)
在設計系統(tǒng)時要牢記安全性。十大漏洞列表在過去 5 年中沒有發(fā)生太大變化。您應遵循安全軟件設計的最佳實踐,并檢查常見安全漏洞的代碼。您應該定期搜索第三方庫以查找新漏洞。常見漏洞和暴露列表可以提供幫助。
威脅建模
威脅建模是一種識別系統(tǒng)中可能存在的安全威脅的系統(tǒng)方法。首先確定系統(tǒng)中的所有資產(chǎn)(數(shù)據(jù)庫中的用戶數(shù)據(jù),文件等)以及如何訪問它們。之后,您可以識別可能的攻擊并開始執(zhí)行它們。我建議閱讀高級 API 安全性的第 2 章,以便更好地概述威脅建模。
結(jié)論
唯一安全的系統(tǒng)是關(guān)閉電源的系統(tǒng),不連接到任何網(wǎng)絡(理想情況下是在一個有形模塊中)。它是多么有用的系統(tǒng)!事實是,安全是艱難而昂貴的。分布式系統(tǒng)中有許多組件和鏈接,每個組件和鏈接都是惡意用戶的可能目標。企業(yè)需要平衡攻擊的風險和概率與實施預防機制的成本。
攻擊者手上有很多耐心和計算能力。我們可以通過使用威脅建模來防止某些類型的攻擊,但我們無法保證 100%的安全性。因此,向業(yè)務部門明確表示這一點是個好主意,共同決定投資安全性的程度,并制定安全漏洞何時發(fā)生的計劃。
5、拓撲不會改變
問題
網(wǎng)絡拓撲不斷變化。
網(wǎng)絡拓撲始終在變化。有時它會因意外原因而發(fā)生變化 - 當您的應用服務器出現(xiàn)故障并需要更換時。很多時候它是故意的 - 在新服務器上添加新進程。如今,隨著云和容器的增加,這一點更加明顯。彈性擴展 - 根據(jù)工作負載添加或刪除服務器的能力 - 需要一定程度的網(wǎng)絡靈活性。
解決方案
摘要網(wǎng)絡的物理結(jié)構(gòu)
您需要做的第一件事是抽象網(wǎng)絡的物理結(jié)構(gòu)。有幾種方法可以做到這一點:
- 停止硬編碼 IP - 您應該更喜歡使用主機名。通過使用 URI,我們依靠 DNS 將主機名解析為 IP。
- 當 DNS 不夠時(例如,當您需要映射 IP 和端口時),則使用發(fā)現(xiàn)服務。
- Service Bus 框架還可以提供位置透明性。
無價值的,而非重要的
通過將您的服務器視為沒有價值的,而不是很重要的,您確保沒有服務器是不可替代的。這一點智慧可以幫助您進入正確的思維模式:任何服務器都可能出現(xiàn)故障(從而改變拓撲結(jié)構(gòu)),因此您應該盡可能地自動化。
測試
最后一條建議是測試你的假設。停止服務或關(guān)閉服務器,看看您的系統(tǒng)是否仍在運行。像 Netflix 的 Chaos Monkey 這樣的工具可以通過隨機關(guān)閉生產(chǎn)環(huán)境中的 VM 或容器來實現(xiàn)這一目標。通過帶來痛苦,您更有動力構(gòu)建一個可以處理拓撲更改的更具彈性的系統(tǒng)。
結(jié)論
十年前,大多數(shù)拓撲結(jié)構(gòu)并沒有經(jīng)常改變。但是當它發(fā)生時,它可能發(fā)生在生產(chǎn)中并引入了一些停機時間。如今,隨著云和容器的增加,很難忽視這種謬誤。你需要為失敗做好準備并進行測試。不要等到它在生產(chǎn)中發(fā)生!
6、有一位管理員
問題
這個知道一切的并不存在。
嗯,這個看起來很明顯。當然,沒有一個人知道一切。這是一個問題嗎?只要應用程序運行順利,它就不是。但是,當出現(xiàn)問題時,您需要修復它。因為很多人觸摸了應用程序,知道如何解決問題的人可能不在那里。
有很多事情可能會出錯。一個例子是配置。今天的應用程序在多個商店中存儲配置:配置文件,環(huán)境變量,數(shù)據(jù)庫,命令行參數(shù)。沒有人知道每個可能的配置值的影響是什么。
另一件可能出錯的事情是系統(tǒng)升級。分布式應用程序有許多移動部件,您需要確保它們是同步的。例如,您需要確保當前版本的代碼適用于當前版本的數(shù)據(jù)庫。如今,人們關(guān)注 DevOps 和持續(xù)交付。但支持零停機部署并非易事。
但是,至少這些東西都在你的控制之下。許多應用程序與第三方系統(tǒng)交互。這意味著,如果它們失效,你可以做的事情就不多了。因此,即使您的系統(tǒng)有一名管理員,您仍然無法控制第三方系統(tǒng)。
解決方案
每個人都應對釋放過程負責
這意味著從一開始就涉及 Ops 人員或系統(tǒng)管理員。理想情況下,他們將成為團隊的一員。盡早讓系統(tǒng)管理員了解您的進度可以幫助您發(fā)現(xiàn)限制因素。例如,生產(chǎn)環(huán)境可能具有與開發(fā)環(huán)境不同的配置,安全限制,防火墻規(guī)則或可用端口。
記錄和監(jiān)控
系統(tǒng)管理員應該擁有用于錯誤報告和管理問題的正確工具。你應該從一開始就考慮監(jiān)控。分布式系統(tǒng)應具有集中式日志。訪問十個不同服務器上的日志以調(diào)查問題是不可接受的方法。
解耦
您應該在系統(tǒng)升級期間爭取最少的停機時間。這意味著您應該能夠獨立升級系統(tǒng)的不同部分。通過使組件向后兼容,您可以在不同時間更新服務器和客戶端。
通過在組件之間放置隊列,您可以暫時將它們分離。這意味著,例如,即使后端關(guān)閉,Web 服務器仍然可以接受請求。
隔離第三方依賴關(guān)系
您應該以不同于您擁有的組件的方式處理控制之外的系統(tǒng)。這意味著使您的系統(tǒng)更能適應第三方故障。您可以通過引入抽象層來減少外部依賴的影響。這意味著當?shù)谌较到y(tǒng)出現(xiàn)故障時,您將找到更少的地方來查找錯誤。
結(jié)論
要解決這個謬論,您需要使系統(tǒng)易于管理。DevOps,日志記錄和監(jiān)控可以提供幫助。您還需要考慮系統(tǒng)的升級過程。如果升級需要數(shù)小時的停機時間,則無法部署每個 sprint。沒有一個管理員,所以每個人都應該對發(fā)布過程負責。
7、運輸成本為零
問題
運輸成本 不是 零。
這種謬論與第二個謬誤有關(guān),即 延遲為零。通過網(wǎng)絡傳輸內(nèi)容在時間和資源上都有代價。如果第二個謬誤討論了時間方面,那么謬誤#7 就會解決資源消耗問題。
這種謬論有兩個不同的方面:
網(wǎng)絡基礎設施的成本
網(wǎng)絡基礎設施需要付出代價。服務器,SAN,網(wǎng)絡交換機,負載平衡器以及負責此設備的人員 - 所有這些都需要花錢。如果您的系統(tǒng)是在內(nèi)部部署的,那么您需要預先支付這個價格。如果您正在使用云,那么您只需為您使用的內(nèi)容付費,但您仍然需要付費。
序列化/反序列化的成本
這種謬誤的第二個方面是在傳輸級別和應用程序級別之間傳輸數(shù)據(jù)的成本。序列化和反序列化會消耗 CPU 時間,因此需要花錢。如果您的應用程序是內(nèi)部部署的,那么如果您不主動監(jiān)視資源消耗,則會隱藏此成本。但是,如果您的應用程序部署在云端,那么這筆費用就會非常明顯,因為您需要為使用的內(nèi)容付費。
解決方案
關(guān)于基礎設施的成本,你無能為力。您只能確保盡可能高效地使用它。SOAP 或 XML 比 JSON 更昂貴。JSON 比像 Google 的 Protocol Buffers 這樣的二進制協(xié)議更昂貴。根據(jù)系統(tǒng)的類型,這可能或多或少重要。例如,對于與視頻流或 VoIP 有關(guān)的應用,傳輸成本更為重要。
結(jié)論
您應該注意運輸成本以及應用程序正在執(zhí)行的序列化和反序列化程度。這并不意味著您應該優(yōu)化,除非需要它。您應該對資源消耗進行基準測試和監(jiān)控,并確定運輸成本是否對您有用。
8、網(wǎng)絡是同質(zhì)的
問題
網(wǎng)絡 不是 同質(zhì)的。
同質(zhì)網(wǎng)絡是使用類似配置和相同通信協(xié)議的計算機網(wǎng)絡。擁有類似配置的計算機是一項艱巨的任務。例如,您幾乎無法控制哪些移動設備可以連接到您的應用。這就是為什么重點關(guān)注標準協(xié)議。
解決方案
您應該選擇標準格式以避免供應商鎖定。這可能意味著 XML,JSON 或協(xié)議緩沖區(qū)。有很多選擇可供選擇。
結(jié)論
您需要確保系統(tǒng)的組件可以相互通信。使用專有協(xié)議會損害應用程序的互操作性。
設計分布式系統(tǒng)很難
這些謬論發(fā)表于 20 多年前。但他們今天仍然堅持,其中一些比其他人更多。我認為今天許多開發(fā)人員都知道它們,但我們編寫的代碼并沒有顯示出來。
我們必須接受這些事實:網(wǎng)絡不可靠,不安全并且需要花錢。帶寬有限。網(wǎng)絡的拓撲結(jié)構(gòu)將發(fā)生變化。其組件的配置方式不同。意識到這些限制將有助于我們設計更好的分布式系統(tǒng)。