高并發中的 限流、熔斷、降級、預熱、背壓!
首先,我們需要明確一下這幾個名詞出現的場景:分布式高并發環境。如果你的產品賣相不好,沒人鳥它,那它就用不著這幾個屬性。不需要任何加成,低并發系統就能工作的很好。
分布式系統是一個整體,調用關系錯綜復雜,其中某個資源異常,大概率會造成級聯故障。當系統處于超負荷的壓力之下,容器或者宿主機,將表現的異乎尋常的脆弱。load飆升、拒絕響應,甚至于雪崩,造成的后果都比較嚴重。
鑒于分布式系統病嬌娘樣式的反應,我們有各種手段來處理這些異常狀況。接下來,我們將簡要介紹一下這些場景,還有常用的手段。
1. 限流
“我的貼子被限流了!” 即使不是互聯網從業人員,也能言之鑿鑿的說出這樣的話。當他這么說的時候,他并不是在說高并發中的限流,它只是邏輯意義上的。
web開發中,tomcat默認是200個線程池,當更多的請求到來,沒有新的線程能夠去處理這個請求,那這個請求將會一直等待在瀏覽器方。表現的形式是,瀏覽器一直在轉圈(還沒超過acceptCount),即使你請求的是一個簡單的Hello world。
你可以把這個過程,也看作是限流。它在本質上,是設置一個資源數量上限,超出這個上限的請求,將被緩沖,或者直接失敗。
對于高并發場景下的限流來說,它有特殊的含義:它主要是用來保護底層資源的。如果你想要調用某些服務,你需要首先獲取調用它的許可。限流一般由服務提供方來提供,對調用方能夠做事的能力進行限制。
比如,某個服務為A、B、C都提供了服務,但根據提前申請的流量預估,限制A服務的請求為1000/秒、B服務2000/秒,C服務1w/秒。在同一時刻,某些客戶端可能會出現被拒絕的請求,而某些客戶端能夠正常運行,限流被看作是服務端的自我保護能力。
常見的限流算法有:計數器、漏桶、令牌桶等。但計數器算法無法實現平滑的限流,在實際應用中使用較少。
2. 熔斷
通常來說,皇帝在微服務里想夜生活過得舒服,能夠大刀闊斧單刀直入,不因私事丟江山,就不得不靠熔斷大總管。熔斷的作用,主要是為了避免服務的雪崩。
如圖,A→B→C互相依次調用,但C項目很可能出現問題(流量過大或者報錯等),就會引發線程一直進行等待,導致拖垮整個鏈路層,線程資源耗盡。
意如其名,熔斷就像是保險絲,超過負載了保險絲就燒掉了。當然,當后端服務緩和的時候,我們還可以再把它接上。熔斷功能一般由調用端提供,用在不太重要的旁路請求上,避免這些不重要的服務因為異常或者超時,影響正常的、重要的業務邏輯
在實現上,我們可以把熔斷看作是一種代理模式。當熔斷打開的時候,服務將暫停對其保護資源的訪問,并返回固定的或者不產生遠程調用的默認結果。
3. 降級
降級是一個比較模糊的說法。限流、熔斷,在一定程度上,也可以看作是降級的一種。但通常所說的降級,切入的層次更加高級一些。
降級一般考慮的是分布式系統的整體性,從源頭上切斷流量的來源。比如在雙11的時候,為了保證交易系統,將會暫停一些不重要的服務,以免產生資源爭占。服務降級有人工參與,人為使得某些服務不可用,多屬于一種業務降級方式。
在什么地方最適合做降級呢?就是入口。比如Nginx,比如DNS等。
在某些互聯網應用中,會存在MVP(Minimum Viable Product)這個概念,意為最小化可行產品,它的SLA要求非常高。圍繞著最小可行性產品,會有一系列的服務拆分操作,當然某些情況甚至需要重寫。
比如,一個電商系統,在極端情況下,只需要把商品顯示出來,把商品賣出去就行。其他一些支撐性的系統,比如評論、推薦等,都可以臨時關掉。在物理部署和調用關系上,就要考慮這些情況。
4. 預熱
請看下面一種情況。
一個高并發環境下的DB,進程死亡后進行重啟。由于業務處在高峰期間,上游的負載均衡策略發生了重分配。剛剛啟動的DB瞬間接受了1/3的流量,然后load瘋狂飆升,直至再無響應。
原因就是:新啟動的DB,各種Cache并沒有準備完畢,系統狀態與正常運行時截然不同。可能平常1/10的量,就能夠把它帶入死亡。
同理,一個剛剛啟動的JVM進程,由于字節碼并未被JIT編譯器優化,在剛啟動的時候,所有接口的響應時間都比較慢。如果調用它的負載均衡組件,并沒有考慮這種剛啟動的情況,1/n的流量被正常路由到這個節點,就很容易出現問題。
所以,我們希望負載均衡組件,能夠依據JVM進程的啟動時間,動態的慢慢加量,進行服務預熱,直到達到正常流量水平。
5. 背壓
考慮一下下面兩種場景:
沒有限流。請求量過高,有多少收多少,極容易造成后端服務崩潰或者內存溢出
傳統限流。你強行規定了某個接口最大的承受能力,超出了直接拒絕,但此時后端服務是有能力處理這些請求的
如何動態的修改限流的值?這就需要一套機制。調用方需要知道被調用方的處理能力,也就是被調用方需要擁有反饋的能力。背壓,英文Back Pressure,其實是一種智能化的限流,指的是一種策略。
背壓思想,被請求方不會直接將請求端的流量直接丟掉,而是不斷的反饋自己的處理能力。請求端根據這些反饋,實時的調整自己的發送頻率。比較典型的場景,就是TCP/IP中使用滑動窗口來進行流量控制。
反應式編程(Reactive)是觀察者模式的集大成者。它們大多使用事件驅動,多是非阻塞的彈性應用,基于數據流進行彈性傳遞。在這種場景下,背壓實現就簡單的多。
背壓,讓系統更穩定,利用率也更高,它本身擁有更高的彈性和智能。
總結
簡單總結一下:
限流 規定一個上限,流量超過系統承載能力時,會直接拒絕服務
熔斷 不因底層旁路應用的故障,造成系統雪崩。欲練此功,必先自宮
降級 從請求入口,大范圍的滅掉過載請求
預熱 給系統一些啟動預熱時間,加載緩存,避免資源死鎖
背壓 被調用方反饋自己的能力給調用方。溫柔的調用,需要堅實的溝通
簡單來講,只要流量不進系統,什么都好說,降級是最威猛最霸道的手段;一旦流量進入系統,就要接受系統內一系列規則的制約,其中限流是最直接的手段,將請求攔在外面。雖然用戶的請求失敗了,但我的系統還能活;沒有熔斷的系統就很兇殘,很容易讓三流功能影響主要功能,所以要在合適的時候打開它;至于預熱,不過是在愛情火花前的一系列前戲,直到服務的巔峰狀態;當然,相對于請求扔出去就不管的模式,如果被調用方能夠反饋自己的狀態,那么請求方就可以根據需要加大或者縮減馬力,這就是背壓的思想。
這些手段,都是在有限的資源下,有效的處理手段。但如果公司有錢,有彈性處理手段,這些都會變成輔助手段。畢竟,當所有的服務,能夠將自己的狀態,反饋到監控中心,監控中心能夠實現彈性擴容。只要服務拆分的滿足水平擴展,我們只需要增加實例就夠了。
作者簡介:小姐姐味道 (xjjdog),一個不允許程序員走彎路的公眾號。聚焦基礎架構和Linux。十年架構,日百億流量,與你探討高并發世界,給你不一樣的味道。