弹力设计总结
容錯設計又叫彈力設計,其中著眼于分布式系統(tǒng)的各種“容忍”能力,包括容錯能力(服務 隔離、異步調用、請求冪等性)、可伸縮性(有 / 無狀態(tài)的服務)、一致性(補償事務、重 試)、應對大流量的能力(熔斷、降級)。可以看到,在確保系統(tǒng)正確性的前提下,系統(tǒng)的可用性是彈力設計保障的重點。
隔離設計
what:
顧名思義,它是指將系統(tǒng)按照一定的原則劃分為若干個服務模塊,各個模塊之間相對獨立,無強依賴。當有故障發(fā)生時,能將問題和影響隔離在某個模塊內部,而不擴散風險,不波及其它模塊,不影響整體的系統(tǒng)服務。它是借鑒于造船行業(yè)。
why:
任何軟件系統(tǒng),故障是不可避免的,并且大多數(shù)還是不可預測的,因此,我們只能在系統(tǒng)的設計之初就充分的考慮好應對措施,如何在故障發(fā)生時,去盡最大可能的止損和減少故障范圍。
沒有人敢說他的系統(tǒng)是百分百可用,我們能做的就是,使用一切方法去減少故障的影響面,盡可能的去提高系統(tǒng)的整體可用率。
而把系統(tǒng)分離成子服務,將子服務進行一定程度隔離的做法,能保證在有不可預測的故障發(fā)生時,縮小故障范圍的最佳手段。
我們需要定義好隔離業(yè)務的大小和粒度,過大和過小都不好。這需要認真地做業(yè)務上的需求 和系統(tǒng)分析。
how:
1.按服務/功能做隔離
如果我們要設計個電商平臺,可以將其中的用戶系統(tǒng)、訂單系統(tǒng)、支付系統(tǒng)、倉儲系統(tǒng)都分別進行獨立隔離,這樣做就是從服務層面實現(xiàn)了故障的隔離效果
2.按用戶分類隔離:
先部署多套一模一樣的業(yè)務服務,然后將用戶根據(jù)一定的特征去做分類,讓不同分類的用戶去訪問不同的業(yè)務實例,達到分流和隔離的效果。
3.在代碼層面也可以做隔離設計,比如不同業(yè)務使用不同的線程池,防止某個業(yè)務故障直接影響到其他的業(yè)務
異步通訊設計:
what:
通訊一般來說分同步和異步兩種。同步通訊就像打電話,需要實時響應,而異步通訊就像發(fā)郵 件,不需要馬上回復。
why:
1.整個同步調用鏈的性能會由慢的那個服務所決定
2.同步調用會導致調用方一直在等待被調用方完成,如果一層接一層地同步調用下去,所有的 參與方會有相同的等待時間。這會非常消耗調用方的資源。
3.同步調用不好的是,如果被調用方有問題,那么其調用方就會跟著出問題,會出現(xiàn)多 米諾骨牌效應。
所以,異步通訊相對于同步通訊來說,除了可以增加系統(tǒng)的吞吐量之外,大的一個好處是其可 以讓服務間的解耦更為徹底,系統(tǒng)的調用方和被調用方可以按照自己的速率而不是步調一致,各個服務間的性能不受干擾相對獨立。從 而可以更好地保護系統(tǒng),讓系統(tǒng)更有彈力。
how:
有幾種方式
1.請求響應式 在這種情況下,發(fā)送方(sender)會直接請求接收方(receiver),被請求方接收到請求后,直 接返回——收到請求,正在處理。
對于返回結果,有兩種方法,一種是發(fā)送方時不時地去輪詢一下,問一下干沒干完。另一種方式 是發(fā)送方注冊一個回調方法,也就是接收方處理完后回調請求方。這種架構模型在以前的網(wǎng)上支 付中比較常見,頁面先從商家跳轉到支付寶或銀行,商家會把回調的 URL 傳給支付頁面,支付 完后,再跳轉回商家的 URL。
很明顯,這種情況下還是有一定耦合的。是發(fā)送方依賴于接收方,并且要把自己的回調發(fā)送給接 收方,處理完后回調。
2.發(fā)布訂閱的方式
通過消息中間件(Broker),發(fā)送方(sender)和接收方(receiver)都互相看不到對方, 它們看得到的是一個 Broker,發(fā)送方向 Broker 發(fā)送消息,接收方向 Broker 訂閱消息。
在 Broker 這種模式下,發(fā)送方的服務和接收方的服務大程度地解耦。但是所有人都依賴于一 個總線,所以這個總線就需要有如下的特性:
必需是高可用的,因為它成了整個系統(tǒng)的關鍵;
必需是高性能而且是可以水平擴展的;
必需是可以持久化不丟數(shù)據(jù)的。
要做到這三條還是比較難的,所以一般下游系統(tǒng)會提供查詢接口給上游調用。
注意點:
消息順序很難保證,業(yè)務最好設計成不依賴消息順序的。
消息傳遞中,可能有的業(yè)務邏輯會有像 TCP 協(xié)議那樣的 send 和 ACK 機制。比如:A 服務 發(fā)出一個消息之后,開始等待處理方的 ACK,如果等不到的話,就需要做重傳。此時,需要 處理方有冪等的處理,即同一件消息無論收到多少次都只處理一次。
關于冪等性設計,現(xiàn)在我們一般是采用先查詢的方式,但是絕大多數(shù)請求應該都不會是重新發(fā)過來的,所以讓 100% 的請求都到這個存儲里去查一下,這會導致處理流程可能會很慢。
所以,好是當這個存儲出現(xiàn)沖突的時候會報錯。也就是說,我們收到交易請求后,直接去存儲 里記錄這個 ID(相對于數(shù)據(jù)的 Insert 操作),如果出現(xiàn) ID 沖突了的異常,那么我們就知道這個之前已經(jīng)有人發(fā)過來了,所以就不用再做了。
補償事務:
what:
業(yè)務補償主要做兩件事。
why:
在很多情況下,我們是無法做到強一致的ACID 的。特別是我們需要跨多個系統(tǒng)的時候。BASE理論強調軟狀態(tài)與最終一致性,
如果一個事務失敗了或是超時了,我們需要 不斷地重試,努力地達到最終我們想要的狀態(tài)。然后,如果我們不能達到這個我們想要的狀態(tài), 我們需要把整個狀態(tài)恢復到之前的狀態(tài)。另外,如果有變化的請求,我們需要啟動整個事務的業(yè)務更新機制。
how:
因為要把一個業(yè)務流程執(zhí)行完成,需要這個流程中所涉及的服務方支持冪等性。并且在上游 有重試機制
需要小心維護和監(jiān)控整個過程的狀態(tài)
補償?shù)臉I(yè)務邏輯和流程不一定非得是嚴格反向操作。有時候可以并行,有時候,可能會更簡 單。總之,設計業(yè)務正向流程的時候,也需要設計業(yè)務的反向補償流程
業(yè)務補償?shù)臉I(yè)務邏輯是強業(yè)務相關的,很難做成通用的
下層的業(yè)務方最好提供短期的資源預留機制。就像電商中的把貨品的庫存預先占住等待用戶 在 15 分鐘內支付。如果沒有收到用戶的支付,則釋放庫存。然后回滾到之前的下單操作,等待用戶重新下單。
重試設計:
重試的場景:
調用超時、被調用端返回了某種可以重試的錯誤(如繁忙中、流控中、維護中、資源不足等)。
而對于一些別的錯誤,則最好不要重試,比如:業(yè)務級的錯誤(如沒有權限、或是非法數(shù)據(jù)等錯 誤),技術上的錯誤(如:HTTP 的 503 等,這種原因可能是觸發(fā)了代碼的 bug,重試下去沒 有意義)。
重試的策略:
指數(shù)級退避策略。在這種情況下,每一次重試所需要的休息時間都會翻倍增加。這種機制主要是用來讓被調用方能夠有更多的時間來從容處理我們的請求。類似TCP 的擁塞控制
注意點:
要確定什么樣的錯誤下需要重試;
重試的時間和重試的次數(shù)。這種在不同的情況下要有不同的考量。有時候,而對一些不是很 重要的問題時,我們應該更快失敗而不是重試一段時間若干次。比如一個前端的交互需要用 到后端的服務。這種情況下,在面對錯誤的時候,應該快速度失敗報錯(比如:網(wǎng)絡錯誤請 重試)。而面對其它的一些錯誤,比如流控,那么應該使用指數(shù)退避的方式,以避免造成更 多的流量。
如果超過重試次數(shù),或是一段時間,那么重試就沒有意義了
需要考慮被調用方是否有冪等的設計。如果沒有,那么重試是不安全的,可能會導致 一個相同的操作被執(zhí)行多次。
熔斷機制:
借鑒于我們電閘上的 " 保險絲 ",當電壓有問題時(比如短路),自動跳閘,此時電路 就會斷開,我們的電器就會受到保護。
重試機制,如果錯誤太多, 或是在短時間內得不到修復,那么我們重試也沒有意義了,此時應該開啟我們的熔斷操作,尤其 是后端太忙的時候,使用熔斷設計可以保護后端不會過載。
熔斷器可以使用狀態(tài)機來實現(xiàn),內部模擬以下幾種狀態(tài)。
閉合(Closed)狀態(tài):我們需要一個調用失敗的計數(shù)器,如果調用失敗,則使失敗次數(shù)加 1。如果最近失敗次數(shù)超過了在給定時間內允許失敗的閾值,則切換到斷開 (Open) 狀態(tài)。此 時開啟了一個超時時鐘,當該時鐘超過了該時間,則切換到半斷開(Half-Open)狀態(tài)。該 超時時間的設定是給了系統(tǒng)一次機會來修正導致調用失敗的錯誤,以回到正常工作的狀態(tài)。
在 Closed 狀態(tài)下,錯誤計數(shù)器是基于時間的。在特定的時間間隔內會自動重置。這能夠防 止由于某次的偶然錯誤導致熔斷器進入斷開狀態(tài)。也可以基于連續(xù)失敗的次數(shù)。
斷開 (Open) 狀態(tài):在該狀態(tài)下,對應用程序的請求會立即返回錯誤響應,而不調用后端的 服務。這樣也許比較粗暴,有些時候,我們可以 cache 住上次成功請求,直接返回緩存(當 然,這個緩存放在本地內存就好了),如果沒有緩存再返回錯誤(緩存的機制最好用在全站 一樣的數(shù)據(jù),而不是用在不同的用戶間不同的數(shù)據(jù),因為后者需要緩存的數(shù)據(jù)有可能會很 多)。
半開(Half-Open)狀態(tài):允許應用程序一定數(shù)量的請求去調用服務。如果這些請求對服務 的調用成功,那么可以認為之前導致調用失敗的錯誤已經(jīng)修正,此時熔斷器切換到閉合狀態(tài) (并且將錯誤計數(shù)器重置)。
如果這一定數(shù)量的請求有調用失敗的情況,則認為導致之前調用失敗的問題仍然存在,熔斷器切 回到斷開狀態(tài),然后重置計時器來給系統(tǒng)一定的時間來修正錯誤。半斷開狀態(tài)能夠有效防止正在 恢復中的服務被突然而來的大量請求再次拖垮。
熔斷設計的重點:
錯誤的類型。需要注意的是請求失敗的原因會有很多種。需要根據(jù)不同的錯誤情況來調整相 應的策略。所以,熔斷和重試一樣,需要對返回的錯誤進行識別。一些錯誤先走重試的策略 (比如限流,或是超時),重試幾次后再打開熔斷。一些錯誤是遠程服務掛掉,恢復時間比 較長;這種錯誤不必走重試,可以直接打開熔斷策略。
日志監(jiān)控。熔斷器應該能夠記錄所有失敗的請求,以及一些可能會嘗試成功的請求,使得管 理員能夠監(jiān)控使用熔斷器保護的服務的執(zhí)行情況。
測試服務是否可用。在斷開狀態(tài)下,熔斷器可以采用定期地 ping 一下遠程的服務的健康檢查 接口,來判斷服務是否恢復,而不是使用計時器來自動切換到半開狀態(tài)。這樣做的一個好處 是,在服務恢復的情況下,不需要真實的用戶流量就可以把狀態(tài)從半開狀態(tài)切回關閉狀態(tài)。 否則在半開狀態(tài)下,即便服務已恢復了,也需要用戶真實的請求來恢復,這會影響用戶的真 實請求。
手動重置。在系統(tǒng)中對于失敗操作的恢復時間是很難確定的,提供一個手動重置功能能夠使 得管理員可以手動地強制將熔斷器切換到閉合狀態(tài)。同樣的,如果受熔斷器保護的服務暫時 不可用的話,管理員能夠強制將熔斷器設置為斷開狀態(tài)。
并發(fā)問題。相同的熔斷器有可能被大量并發(fā)請求同時訪問。熔斷器的實現(xiàn)不應該阻塞并發(fā)的 請求或者增加每次請求調用的負擔。尤其是其中的對調用結果的統(tǒng)計,一般來說會成為一個 共享的數(shù)據(jù)結構,這個會導致有鎖的情況。在這種情況下,最好使用一些無鎖的數(shù)據(jù)結構, 或是 atomic 的原子操作。這樣會帶來更好的性能。
資源分區(qū)。有時候,我們會把資源分布在不同的分區(qū)上。比如,數(shù)據(jù)庫的分庫分表,某個分 區(qū)可能出現(xiàn)問題,而其它分區(qū)還可用。在這種情況下,單一的熔斷器會把所有的分區(qū)訪問給 混為一談,從而,一旦開始熔斷,那么所有的分區(qū)都會受到熔斷影響。或是出現(xiàn)一會兒熔斷 一會兒又好,來來回回的情況。所以,熔斷器需要考慮這樣的問題,只對有問題的分區(qū)進行 熔斷,而不是整體。
限流設計:
保護系統(tǒng)不會在過載的情況下導致問題,那么,我們就需要限流,熔斷機制也是限流的一種。
限流的策略:
拒絕服務
服務降級
限流的實現(xiàn)方式:
計數(shù)器方式
隊列算法
漏斗算法
令牌桶算法
降級設計:
本質是為了解決資源不足和訪問量過大的問題
降級需要犧牲掉的東西有:
降低一致性。從強一致性變成最終一致性
停止次要功能。停止訪問不重要的功能,從而釋放出更多的資源。
簡化功能。把一些功能簡化掉,比如,簡化業(yè)務流程,或是不再返回全量數(shù)據(jù),只返回部分 數(shù)據(jù)
要點:
對于降級,一般來說是要犧牲業(yè)務功能或是流程,以及一致性的。所以,我們需要對業(yè)務做非常 仔細的梳理和分析。我們很難通過不侵入業(yè)務的方式來做到功能降級。
在設計降級的時候,需要清楚地定義好降級的關鍵條件,比如,吞吐量過大、響應時間過慢、失 敗次數(shù)過多,有網(wǎng)絡或是服務故障,等等,然后做好相應的應急預案。這些預案最好是寫成代碼 可以快速地自動化或半自動化執(zhí)行的。
功能降級需要梳理業(yè)務的功能,哪些是 must-have 的功能,哪些是 nice-to-have 的功能;哪 些是必需要死保的功能,哪些是可以犧牲的功能。而且需要在事前設計好可以簡化的或是用來應 急的業(yè)務流程。當系統(tǒng)出問題的時候,就需要走簡化應急流程。
降級的時候,需要犧牲掉一致性,或是一些業(yè)務流程:對于讀操作來說,使用緩存來解決,對于 寫操作來說,需要異步調用來解決。并且,我們需要以流水賬的方式記錄下來,這樣方便對賬, 以免漏掉或是和正常的流程混淆。
降級的功能的開關可以是一個系統(tǒng)的配置開關。做成配置時,你需要在要降級的時候推送相應的 配置。另一種方式是,在對外服務的 API 上有所區(qū)分(方法簽名或是開關參數(shù)),這樣可以由 上游調用者來驅動。
比如:一個網(wǎng)關在限流時,在協(xié)議頭中加入了一個限流程度的參數(shù),讓后端服務能知道限流在發(fā) 生中。當限流程度達到某個值時,或是限流時間超過某個值時,就自動開始降級,直到限流好
轉。
對于數(shù)據(jù)方面的降級,需要前端程序的配合。一般來說,前端的程序可以根據(jù)后端傳來的數(shù)據(jù)來 決定展示哪些界面模塊。比如,當前端收不到商品評論時,就不展示。為了區(qū)分本來就沒有數(shù) 據(jù),還是因為降級了沒有數(shù)據(jù)的兩種情況,在協(xié)議頭中也應該加上降級的標簽。
因為降級的功能平時不會總是會發(fā)生,屬于應急的情況,所以,降級的這些業(yè)務流程和功能有可 能長期不用而出現(xiàn) bug 或問題,對此,需要在平時做一些演練。
轉載于:https://www.cnblogs.com/laowz/p/10089376.html
總結
- 上一篇: 二分查找-数组实现(小trick)
- 下一篇: 福大软工1816:项目测评