復(fù)雜的系統(tǒng)一般是由很多離散的部件組成,這些部件會出現(xiàn)故障,而且有時候會有多個部件同時出現(xiàn)故障,所以復(fù)雜的系統(tǒng)通常是運行在故障模式下。在一個采用了微服務(wù)架構(gòu)的系統(tǒng)里,一個功能可能需要調(diào)用多個服務(wù),因此每個部件的可用性決定了整個系統(tǒng)的可用性。這是彈性工程背后的核心邏輯。假設(shè)一個功能依賴三個服務(wù),每個服務(wù)分別具有90%、95%和99%的可靠性,那么部分可用性就介于99.995%和84%之間(假設(shè)失效是單獨發(fā)生的)。彈性工程意味著在設(shè)計時要把失效作為常規(guī)的考慮因素。
預(yù)測失效是彈性工程的第一步,而第二步是擁抱它們。告訴客戶,可預(yù)知的失效好過未知或非期望的失效?;貕菏橇硪环N彈性模式。從根本上說,回壓就是要對資源強加限制。比如隊列長度限制、帶寬限制、流量控制、消息速度限制、消息大小限制等等。如果不顯式地進行限制,它們就會變成隱式的(比如服務(wù)器的內(nèi)存會被耗盡,不過因為這種限制是隱式的,所以無法準(zhǔn)確地預(yù)測在什么時候會發(fā)生什么問題)。使用無邊界的隊列或其它一些隱式的限制就好比有人聲稱知道自己什么時候可以戒掉酒癮,因為人總有一死,或許到了那個時候就不會再喝酒了。
速率限定不僅能夠防止那些糟糕的actor破壞你的系統(tǒng),它同時也是為了防止你自己對系統(tǒng)造成破壞。隊列限制和消息大小限制是最有趣的,因為它們讓很多開發(fā)者感到疑惑,同時也讓他們感到沮喪,因為他們還沒有完全搞清楚它們背后的動機。它們其實也是速率限定的另一個形式,或者說回壓。下面我們拿消息大小限制作為例子。
假設(shè)我們有一個分布式系統(tǒng),系統(tǒng)里的actor可以給其它actor發(fā)送消息,接收消息的actor對收到的消息進行處理,當(dāng)然它們也可能往外發(fā)送消息。好的軟件工程師都知道,分布式計算的第八個謬論是“均等網(wǎng)絡(luò)”。所以,并不是所有的actor都使用相同的硬件、軟件或者網(wǎng)絡(luò)。我們有運行Ubuntu的擁有128G內(nèi)存的服務(wù)器,有運行macOS的擁有16G內(nèi)存的筆記本電腦,有運行Android的擁有2G內(nèi)存的移動客戶端,還有512M內(nèi)存的物聯(lián)網(wǎng)設(shè)備,在這些設(shè)備上面運行著各種各樣的軟件和網(wǎng)絡(luò)接口。
如果我們不對消息的大小進行限制,那么我們就是在制造隱式的限制(上面我們對此做過討論)。換句話說,你和你的交互方正在遵循一種無言的協(xié)議,雙方都無法請求退出。因為任何一個actor都可以發(fā)送任意大小的消息,那么下游的消費者必須直接或者間接地支持任意大小的消息。我們怎么可能對任意大小的東西進行測試呢?我們做不到。我們只有兩種選擇:要么做出顯式的限制,要么保持這種隱式的限制。如果選擇了前者,我們可以定義我們的行為邊界,并且對其進行測試。而后者要求我們基于未定義的生產(chǎn)規(guī)模進行測試,這是在拿系統(tǒng)可靠性作為賭注。第二種情況的限制依然存在,只不過被隱藏了起來。如果我們不讓它們變成顯式的,很容易在生產(chǎn)環(huán)境受到DoS攻擊。在云基礎(chǔ)設(shè)施環(huán)境中,因為它們的多租戶特性,這些限制變得尤為重要。這些限制可以防止糟糕的actor(包括你自己)拖垮服務(wù),或者壟斷基礎(chǔ)設(shè)施和系統(tǒng)資源。
在我們的異構(gòu)actor系統(tǒng)里,我們針對移動設(shè)備和Web瀏覽器進行消息限制,它們一般都是單線程或內(nèi)存受限的消費者。如果沒有顯式的消息大小限制,客戶端可能會因為請求過多的數(shù)據(jù)或接收無法處理的數(shù)據(jù)而崩潰,這就是為什么有些協(xié)議雖然沒有明確規(guī)定但必須存在。
讓我們從企業(yè)的角度來看待這些問題。假設(shè)有另一個系統(tǒng):美國國家高速公路系統(tǒng)。美國交通局通過Federal Bridge Gross Weight Formula來防止大量的汽車對道路和橋梁造成破壞。這里存在著相同的工程問題,只不過規(guī)則和基礎(chǔ)設(shè)施不太一樣。
2007年8月,明尼阿波里斯市的洲際35W密西西比河大橋坍塌,這個事故引起了人們對于卡車重量和大橋承受力之間關(guān)系的思考。2008年11月,美國國家交通安全局給出大橋坍塌的幾個原因:有缺陷的加固板、不精確的勘察、過重的建材以及高峰時期的車流重量。
交通局依賴地磅來確??ㄜ囍亓颗c官方允許的重量合規(guī),并對超重的車輛進行處罰。
官方規(guī)定的最大重量是80000磅。超過這個重量的卡車依然可以在高速上行駛,不過行程會受到限制。超重許可只會被發(fā)放給那些無法拆分至符合官方標(biāo)準(zhǔn)的貨物,而且除了卡車以外沒有其他方式可以運載這些貨物。
重量限制需要被硬性規(guī)定,這樣工程師們在建造道路、橋梁和其它基礎(chǔ)設(shè)施時就有章可循。計算機系統(tǒng)也一樣。這也就是為什么很多計算機系統(tǒng)硬性規(guī)定了很多限制。例如,Amazon就對他們的Simple Queue Service做了明確的限制——標(biāo)準(zhǔn)隊列最多可承載12萬個不落地的消息,而FIFO隊列是2萬個,而且消息大小被限制在256K以內(nèi)。Amazon Kinesis、Apache Kafka、NATS和Google App Engine所使用的消息都限制在1M以內(nèi)。系統(tǒng)設(shè)計者可以通過這些限制來優(yōu)化他們的基礎(chǔ)設(shè)施,并降低多租戶環(huán)境存在的風(fēng)險——雖然置之不理會讓資源計劃變得更簡單。
不管是隊列、消息大小、查詢或者流量,不對它們進行限制是一種彈性工程反模式。不對它們進行顯式地限制,故障會以不可預(yù)期和非期望的方式發(fā)生。要記住,限制其實是時刻存在的,只是有時候它們被隱藏起來了。通過顯式的限制,可以讓故障的發(fā)生更加地可預(yù)期,而且發(fā)生故障的平均時間變長,而從故障中恢復(fù)的時間變短,只要在事先多做一些稍微復(fù)雜一點的工作。
事先做出顯式的限制,好過讓系統(tǒng)在不可預(yù)期的情況下發(fā)生故障。后者雖然在前期會少作一些工作,但從長期來看會帶來更多的問題。要求開發(fā)者們直接做出顯式的限制,他們會因此認(rèn)真思考他們的API和業(yè)務(wù)邏輯,并設(shè)計出穩(wěn)定、可伸縮、高性能的交互系統(tǒng)。