什么是高并發,從字面上理解,就是在某一時刻產生大量的請求,那么多少量稱為大量,業界并沒有標準的衡量范圍。 原因非常簡單,不同的業務處理復雜度不一樣 。
而我所理解的 高并發, 它并不只是一個數字,而更是一種架構思維模式 ,它讓你在面對不同的復雜情況下,從容地選擇不同的技術手段,來提升應用系統的 處理能力。
但是,并不意味應用系統從誕生的那一刻,就需要具備強大的處理能力,這種做法并不提倡。要知道, 脫離實際情況的技術,會顯得毫無價值,甚至是一種浪費的表現 。
言歸正傳,那高并發到底是一種怎樣的架構思維模式,它對架構設計又有什么影響,以及如何通過它來驅動架構演進,讓我們接著往下讀,慢慢去體會這其中的精髓。
性能是一種基礎
在架構設計的過程中,思考固然重要,但目標更為關鍵。 通過目標的牽引力,可以始終確保推進方向,不會脫離成功的軌道 。 那高并發的目標是什么,估計你的第一反應就是性能。
沒錯, 性能是高并發的目標之一,它不可或缺,但并不代表所有 。而我將它視為是高并發的一種基礎能力,它的能力高低將會直接影響到其他能力的取舍。例如:服務可用性,數據一致性等。
性能在軟件研發過程中無處不在,不管是在非功能性需求中,還是在性能測試報告中,都能見到它的身影。那么如何來衡量它的高低呢,先來看看常用的性能指標。
每秒處理事務數(TPS)
每秒能夠處理的事務數,其中T(Transactions)可以定義不同的含義,它可以是完整的一筆業務,也可以是單個的接口請求。
每秒請求數(RPS)
每秒請求數量,也可以叫做QPS,但它與TPS有所不同,前者注重請求能力,后者注重處理能力。不過,若所有請求都在得到響應后再次發起,那么RPS基本等于TPS。
響應時長(RT)
從發出請求到得到響應的耗時,一般可以采用毫秒單位來表示,而在一些對RT比較敏感的業務場景下,可以使用精度更高的微秒來表示。
并發用戶數(VU)
同時請求的用戶數,很多人將它與并發數畫上等號, 但兩者稍有不同,前者關注客戶端,后者關注服務端,除非 每個 用戶僅發送一筆請求,且請求從 客戶端到服務端 沒有延遲,同時服務端有足夠的處 理線程。
以上都是些常用的性能指標,基本可以覆蓋80%以上的性能衡量要求。但千萬不要以單個指標的高低來衡量性能。比如:訂單查詢TPS=100萬就認為性能很高,但RT=10秒。
這顯然毫無意義。因此, 建議同時觀察多個指標的方式來衡量性能的高低,大多數情況下主要會關注TPS和RT,而你可以 將TPS視為一種水平能力,注重并行處理能力, 將RT視為一種垂直能力,注重單筆處理能力, 兩者缺一不可 。
接觸過性能測試的同學,可能會見過如下這種性能測試結果圖,圖中包含了剛才提到過的三個性能指標,其中橫坐標為VU,縱坐標分別為TPS和RT。
注:表格中的數據都是理想情況下的,實際上會有上下抖動,而這里只是為了想用它來解釋一種現象而已。
圖中的兩條曲線, 在不斷增加VU的情況下,TPS不斷上升,但RT保持穩定,但當VU增加到一定量級的時候,TPS開始趨于穩定,而RT不斷上升。
如果你仔細觀察,還會發現一個奇妙的地方,當RT=25ms時,它們三者存在著某種關系,即:TPS=VU/RT。但當RT>25ms時,這種關系似乎被打破了,這里暫時先賣個關子,稍后再說。
根據表格中的數據,性能測試報告結論: 最大TPS=65000,當 RT=25ms(最短)時,最大可承受VU=1500 。
感覺有點不對勁,用剛才的公式來驗證一下,1500/0.025s=60000,但最大卻是TPS=65000。那是因為,當VU=1500時,應用系統的使用資源還有空間。
再來觀察一下表格中的數據,VU從1500增加到1750時,TPS繼續上升,且到了最大值65000。此時,你是不是會理解為當VU增加到1750時,使用資源被耗盡了。話雖沒錯,但不嚴謹。
注:使用資源不一定是指硬件資源,也可能是其他方面,例如:應用系統設置的最大處理線程。
其實在VU增加到1750前,使用資源就已飽和,那如何來測算VU的臨界值呢。你可以將最大TPS作為已知條件,即:VU=TPS*RT,65000*0.025s=1625。也就是說,當VU=1625時,使用資源將出現瓶頸。
調整性能測試報告 結論: 最大TPS=65000, 當RT=25ms(最短)時 ,最大可承受VU=1625 。
有人會問,表格中的RT是不是平均值,首先回答為是。不過, 高并發場景對RT會特別敏感,所以除了要考慮RT的平均值外,建議還要考慮它的分位值 ,例如:P99。
舉例:假設1000筆請求,其中900筆RT=23ms,50筆RT=36ms,50筆RT=50ms
平均值 |
P99值 |
P95值 |
P90值 |
25ms |
50ms |
36ms |
23ms |
P99的計算方式,是將1000筆請求的RT從小到大進行排序,然后取排在第99%位的數值,基于以上舉例數據來進行計算,P99=50ms,其他分位值的計算方式類似。
再次調整性能測試報告結論: 最大TPS=65000,當 RT(平均)=25ms(最短)時,最大可承受VU=1625,RT(P99)=50ms,RT(P95)=36ms,RT(P90)=23ms 。
在非功能性需求中,你可能會看到這樣的需求,性能指標要求: RT(平均) <=30。結合剛才的性能測試報告結論, 當 RT(平均)=25ms(最短)時,最大可承受VU=1625。那就等于在RT上還有5ms的容忍時間。
既然是這樣的話,那我們不妨就繼續嘗試增加VU,不過RT (平均) 會出現上升,但只要控制不要上升到30ms即可, 這是一種通過犧牲耗時(RT)來換取并發用戶數(VU)的行為 。但請不要把它理解為每筆請求耗時都會上升5ms,這將是一個嚴重的誤區。
RT(平均) 的增加,完全可能 是 由于應用系統 當前 沒有足夠的使用資源來處理請求所造成的,例如:處理線程。如果沒有可用線程可以分配給請求時,就會將這請求先放入隊列,等前面的請求處理完成并釋放線程后,就可以繼續處理隊列中的請求了。
那也就是說,沒有進入隊列的請求并不會增加額外的耗時,而只有進入隊列的請求會增加。那么 進入隊列的請求會 增加多少耗時呢, 在理想情況下(RT恒定), 可能會 是正常處理一筆請求耗時的倍數,而倍數的大小又取決于并發請求的數量。
假設 最大處理 線程=1625,若每個用戶僅發送一筆請求,且請求從 客戶端到服務端沒有延遲的條件下, 當并發用戶數=1625時,能夠保證 RT=25ms,但當 并發用戶數 >1625時,因為線程只能分配給1625筆請求,那多余的請求就無法保證RT=25ms。
超過1625筆的請求會先放入隊列,等前面1625筆請求處理完成后,再從隊列中拿出最多1625筆請求進行下一批處理,如果隊列中還有剩余請求,那就繼續按照這種方式循環處理。
進入隊列的請求,每等待一批就需要增加前一批的處理耗時。在理想情況下,每一批都是 RT=25ms,如果這筆請求在隊列中等待了兩批,那就要額外增加50ms的耗時。
VU |
第一批 |
第二批 |
第三批 |
1000 |
1000筆請求 RT=25ms |
||
2000 |
1625筆請求 RT =25ms |
375筆請求 RT =50ms |
|
4000 |
1625筆請求 RT =25ms |
1625 筆請求 RT =50ms |
750筆請求 RT =75ms |
因此,并不能簡單通過VU=TPS*RT= 65000*0.03=1950 來計算最大可承受VU。而是需要 引入一種叫做 科特爾法則(Little’s Law) 的排隊模型來估算,不過由于這個法則比較復雜,這里暫時不做展開。
通過粗略估算后,VU大約在2032,我們再對這個值用上述表格中再反向驗算一下。
VU |
第一批 |
第二批 |
RT (平均) |
2032 |
1625筆請求 RT =25ms |
407筆請求 RT =50ms |
(1625*25+407*50)/(1625+407 )≈30ms |
最終 調整性能測試報告結論: 最大TPS=65000,當RT(平均)=25(最短)時,最大可承受VU=1625,RT(P99)=50,RT(P95)=36,RT(P90)=23;當RT(平均)=30(容忍)時,(理想情況)最大可承受VU=2032 , RT(P99)=RT(P95)=50,RT(P90)=25 。
這就解釋了為什么當RT>25ms時,VU=TPS*RT會不成立的原因。 不過,這些都是在理想情況下推演出來的,實際情況會比這要復雜得多。
所以,還是盡量采用多輪性能測試來得到性能指標,這樣也更具備真 實性。畢竟 影響性能的因素實在大多且很難完全掌控,任何細微變化都將影響 性能指標的變化。
到這里,我們已經了解了可以用哪些 指標 來衡量性能的高低。不過,這里更想強調的是, 性能是高并發的基礎能力,是實現高并發的基礎條件,并且你需要有側重性地提升不同維度的性能指標,而非僅關注某一項 。
限制是一種設計
上文說到,性能是高并發的目標之一。 追求性能沒有錯,但并非永無止境 。想要提升 性能,勢必投入成本,不過它們并不是一直成正比,而是隨著成本不斷增加,性能提升幅度逐漸衰減,甚至可能不再提升。所以,有時間我們要懂得適可而止。
思考一下,追求性能是為了解決什么問題,至少有一點,是為了讓應用系統能夠應對突發請求。換言之,如果能解決這個問題,是不是也算實現了高并發的目標。
而有時候,我們 在解決問題時,不要總是習慣做加法,還可以嘗試做減法 ,架構設計同樣如此。那么,如何通過做減法的方式,來解決應對突發請求的問題呢。讓我們來講講限制。
限制,從狹義上可以理解為是一種約束或控制能力 。在軟件領域中,它可以針對功能性或非功能性,而在高并發的場景中,它更偏向于非功能性。
限制應用系統的處理能力,并不代表要降低應用系統的處理能力 ,而是通過某些控制手段, 讓突發請求能夠被平滑地處理,同時起到 應用系統的保護能力, 避免癱瘓, 還能 將應用系統的資源進行合理分配,避免浪費 。
那么,到底有哪些控制手段,既能實現以上這些能力, 又能減少對客戶體驗上的影響,下面就來介紹幾種常用的控制手段 。
第一招:限流
限流,是在一個時間窗口內,對請求進行速率控制 。若請求達到提前設定的閾值 時 ,則對請求進行排隊或拒絕。常用的限流算法有兩種:漏桶算法和令牌桶算法。
漏桶算法,所有請求先進入漏桶,然后按照一個恒定的速率對漏桶里的請求進行處理,是一種控制處理速率的限流方式,用于平滑突發請求速率。
它的優點是,能夠確保資源不會瞬間耗盡,避免請求處理發生阻塞現象,另外,還能夠保護被應用系統所調用的外部服務,也免受突發請求的沖擊。
它的缺點是,對于突發請求仍然會以一個恒定的速率來進行處理,其靈活性會較弱一點,容易發生突發請求超過漏桶的容量,導致后續請求直接被丟棄。
令牌桶算法,應用系統會以一個恒定的速率往桶里放入令牌,請求處理前,會從桶里獲取令牌,當桶里沒有令牌可取時,則拒絕服務,是一種平均流入速率的限流方式。
它的優點是,在限制平均流入速率的同時,還能在面對突發請求的情況下,確保資源被充分利用,不會被閑置或浪費。
它的缺點是,舍棄了處理速率的強控制能力,那么如果某些功能依賴外部服務,可能將會讓外部服務無法承受壓力,導致無法正常返回,而且還浪費了這次獲取的令牌。
綜上, 兩種算法并沒有絕對的好壞,而是需要根據實際的情況,選擇合適的方式,從而在發揮限流作用的同時不會引發其他問題 。 但在一些秒殺活動中,軟件黨的高頻請求,會很容易觸發限流,導致大量正常請求被誤殺的問題。
雖然在請求被限流后,會返回友好話術,減輕對客戶體驗的影響,但也有可能他們的請求,會一直無法得到有效處理,這時候耐心再好的客戶也會離開及抱怨。
所以,我們 除了使用限流這招外,還得搭配其他的招數組合一起使用,從而讓 應用系統能夠對資源進行合理分配,避免資源浪費,減少正常請求被誤殺的情況。
第二招:降頻
降頻,是在一個時間窗口內,對同一特征的請求進行速率控制 。 若請求達到提前設定的閾值時,則會對請求進行拒絕。
雖然和限流 有點類似,但存在著細微的差別。對限流而言,它并不關心請求方,而只對服務端的速率進行控制,而對降頻而言,它會基于某種特征,對請求方的請求速率進行控制。
而降頻的目的,是為了減少 應用系統資源被不正常的請求所消耗,而導致正常的請求因限流被拒絕的情況發生 。它的實現方式也有多種,而且在前端和后端都可以使用。
識別不正常的請求是降頻的第一步,也是最關鍵的一步。一般會制定某種特征+某段時間+請求數量這種三段式的識別規則。
特征可以是賬號、會話、IP地址、設備號等,時間一般會是1秒,也可以設置更長。賬號+1秒+5筆,意思就是同一個賬號在1秒內可以發生5筆請求,但是這里請求數量與限流的設定參考依據不同。
限流大小主要依據性能來決定,而降頻中的請求數量,一般會以正常人的交互速率作為參考 。所以,并不能因為性能好,就設定賬號+1秒+100筆這種識別規則,這不但不科學還會浪費資源。
接下來,有了識別規則還得搭配對應的處置手段,常見的有兩種模式:挑戰和拒絕。
挑戰 |
彈出驗證碼,輸入并驗證通過后,可以繼續請求 |
僅適用于前端 |
拒絕 |
彈出“請求頻繁”提示,且這筆請求將直接被拒絕 |
適用于前端及后端 |
限流會發生誤殺,難道降頻就不會嗎,其實也會發生,特別是用戶的網絡環境是一個出口IP地址時。所以,如果是基于IP地址特征的識別規則,請求數量建議適當放大。
在降頻策略方面,建議配置多層+漸進式的方式 , 識別規則較為嚴格的 采用挑戰模式, 識別規則較為寬松的 采用拒絕模式,減少因 降頻而引發的誤殺情況,參考如下:
優先級 |
識別規則 |
處置手段 |
1 |
賬號+1秒+5筆 |
挑戰 |
2 |
賬號+1秒+10筆 |
拒絕 |
3 |
IP地址+1秒+20筆 |
挑戰 |
4 |
IP地址+1秒+40筆 |
拒絕 |
降頻確實可以使應用系統的資源,被合理地分配給請求方,但并不能保證萬無一失,特別對于那些技術高超的軟件黨們,他們仍然可以通過其他方式繞開這種控制手段。
不過, 你可以將此視為一種攻防戰,通過增強防守的方式,來提高攻擊者成本 ,而攻擊者一定會權衡成本和收益,當成本大于收益時,可能就不會有攻擊,畢竟沒有人會這么無聊透頂 。
雖然有了限流和降頻這兩招,但仍可能無法應對高并發的場景,況且在初期,限 流和 降頻的策略,也 無法 設計 得 非常完美。所以,有些時候還得使出最后一招。
第三招:降級
降級,是當應用系統處理超載時,對其服務進行裁剪的一種機制 。常見的是應用系統 處理阻塞時,會關閉 非核心服務,并將資源給到核心服務,從而確保核心服務正常。
經常有人將它與熔斷混為一談,但并非一回事 。降級主要是針對應用系統本身,若處理能力不足則可觸發,而熔斷主要是針對應用系統所調用的外部服務,若外部服務不穩定時則可觸發。
當然,兩者也有一定的關系,因為當發生熔斷時,也可以觸發降級機制,比如當同步調用外部服務出現性能問題時,可以降級為異步調用,避免造成線程阻塞而癱瘓 。
不過在降級前,必須得先梳理應用系統中的核心服務,可以采用經典的二八原則,將服務劃分為 20%核心服務+ 80%非核心服務。 而 這種分法的意圖,是希望讓你找到真正重要的核心服務,不然,你會覺得都很重要 。
在梳理過程中,建議通過多個維度來進行綜合評判,如下是我經常采用的一種梳理方法,你可以將此作為一種參考,并結合自己的服務分類標準進行調整。
首先,可以設計一張類似如下的矩陣圖,請盡量地簡約它,將應用系統中的各類服務,按照矩陣所設定的不同屬性進行分門別類。
操作類 |
查詢類 |
|
業務類 |
訂單下單訂單支付 訂單退貨... |
商品查詢訂單查詢退貨進度... |
基礎類 |
用戶登錄用戶登出密碼修改頭像修改 ... |
用戶查詢歷史瀏覽我的收藏我的分享...
|
然后,將業務類+操作 類的挑選出來作為 核心服務 ,你會不會認為這就結束了。不好意思,游戲才剛剛開始。不過你可以試想一下,假設 僅保留這些核心服務,會出現什么問題。
用戶登錄不了無法訂單支付,訂單查詢不了無法訂單退貨。所以, 我們還需引入服務關鍵路徑的概念,可以理解為在使用某個服務前,還必須要使用的其他服務 。
分別對挑選出來的核心服務,進行服務關鍵路徑的 梳理。
路徑1 :用戶登錄——>商品查詢 —— > 訂單下單
路徑2: 用戶登錄—— > 商品查詢 —— > 訂單下單—— > 訂單支付
路徑3:用戶登錄—— > 訂 單 查詢—— > 訂單退貨
待服務關鍵路徑梳理完成后,再對路徑上的所有服務進行合并及去重,將會得到一組新的核心服務:用戶登錄/商品查詢/訂單下單/訂單支付/訂單查詢/訂單退貨。
來計算下核心服務的占比,所有服務14個/核心服務6個,占42.86%,遠遠超過了20%。所以,建議繼續從這些核心服務中,識別更核心的部分,但仍然以服務關鍵路徑為整體。
相比訂單下單/訂單支付/訂單退貨這三條服務關鍵路徑,我想訂單支付可能會更有價值。最后,我們可以僅將訂單支付這條服務關鍵路徑上的服務作為核心服務。
重新再來計算下核心服務的占比, 所有服務14個/核心服務4個,占28.57%, 雖然還是超過了20%,但這并不是重點,重點是我們已經找到了最核心的服務 。
其余的核心服務,可以降級為準核心服務,重組后得到如下這份服務重要程度清單。
核心服務 |
準核心服務 |
非核心服務 |
用戶登錄 商品查詢 訂單下單 訂單支付
|
訂單查詢 訂單退貨
|
退貨進度 用戶登出 密碼修改 頭像修改 用戶查詢 歷史瀏覽 我的收藏 我的分享 |
當擁有這份清單后,若 應用系統處理阻塞時 ,就 可以按照非核心服務>準 核心服務> 核心服務這個順序依次進行降級 。不過, 降級 不一定要拒絕請求,也可以是限流請求,這樣可以減少對服務能力的裁剪力度 。
以上只是一種相對較粗的降級策略, 如果你想要制定更精細化的降級策略,還需要 對 每個服務進行優先級的設定 ,高低依據可以結合自身需要來制定,例如: 歷史 服務使用情況。
當有了限流、降頻、降級這三招,基本就能夠在資源有限的情況下,讓突發請求能夠被平滑地處理,將應用系統的資源能夠被合理地分配,以及當應用系統處理堵塞時, 確保核心服務正常。
取舍是一種權衡
現在,我們已經基本了解了如何衡量性能的指標,以及大致掌握了如何保護應用系統的招數。但這些就是高并發全部了嗎,我想說這僅僅只是入門級別。
上述內容,主要是為了讓你能夠清晰地看到應用系統的性能水位在哪里,以及在資源有限下,當面對突發請求時可以采取哪些招數,能讓應用系統安全地存活下來。
存活,即代表著可用性,它也是高并發的一個特性,而且是我認為相對比較重要的特性 。設想一下,如果你的應用系統連可用性都無法保證,那再高的性能又有何意義。
對于大部分應用系統而言,大家都會比較關注應用系統的可用性,99.9%不夠那就99.99%,甚至還有想做到99.999%的,畢竟可用性的不足會直接影響到業務運作。
但對于 一個想成為高并發的應用系統而言,僅單方面關注高可用,肯定無法稱得上這個頭銜 ,它仍然還需要在其他特性上具備極佳的表現。
讓我們拉回到最初的目標,性能是高并發的目標之一, 不過,這里我們不再談論性能指標,而是來研究如何來提升性能。 因為,高性能是高并發的另外一個重要特性。
想要提升性能,勢必投入成本。隨著成本不斷增加,性能提升幅度逐漸衰減。這兩句話,是不是覺得有點耳熟,但不管你是否還記得,先讓我在這里打個問號再說。
在架構設計的過程中,你是否經常會聽到“取舍”的這個詞,它是 通過犧牲一種能力來換取另外一種能力的方式 ,這些能力可以是性能、可用性、數據一致性或是其他能力。
等等,你是不是突然有意識到,提升性能并不只有投入成本這種方式,至少是在硬件資源方面,我們還可以通過犧牲一種能力去換取。 那到底選擇犧牲哪種能力呢,犧牲可用性,一般不會第一時間考慮,那是不是可以考慮犧牲數據一致性。
但在考慮前得先聲明一下, 所謂犧牲數據一致性,并不是完全不要,而是將數據強一致性降級為最終一致性 。 而對于數據最終一致性的理解,就是在數據更新后,要過一段時間后才能看到,而時間的長短就代表著犧牲了多少。
但并不是說,所有情況都必須犧牲數據一致性來提升性能,有些時候也可以考慮犧牲其他能力。但在取舍前,得先弄清楚當前要什么,但更需要弄清楚當前可以失去什么, 不合理的取舍,不但無法換取收益,反而還會引來更多的問題 。
情況1:數據緩存
緩存,是高并發架構設計中一種不可或缺的能力 ,一般是指那些經常被訪問的熱點數據,可以將它放入緩存中,從而提升數據被讀取的效率。
但是否所有的數據都適合放入緩存中,如果是靜態數據,那么你可以很安心地放入。原因很簡單,靜態數據不會更新,那么緩存和數據源始終保持一致,而且就算緩存中的數據丟失了,至少還有一份在數據源。
通過將靜態數據緩存,可以很輕易地提升靜態數據的訪問性能,甚至可能是幾十倍的效果。但應用系統中還有大量的動態數據,僅提升靜態數據可能對總體的提升并不一定顯著。
你是不是想說,那就把動態數據也放入緩存中不就行了。在下這個決定前,建議你先想一下,動態數據是會更新的, 這就意味著動態數據在放入緩存后,當數據源中的數據被更新后,再次訪問返回的都是更新前的數據,這種效果你是否可以接受。
我想應該沒有人會接受吧,而你是不是又想說,設置下緩存會過期不就能解決了。沒錯,但得等過期后才能解決,那還沒過期前呢,這種方式只能緩解,但并不能根治,而且還會引入一個新的問題,請問過期設置多久才合適。
設置緩存5秒鐘過期,可能永遠無法命中緩存,而且不但沒有提升性能,還增加了代碼復雜度,有點畫蛇添足的感覺。 設置緩存5分鐘過期,命中緩存的幾率可能會提高,但緩存后在5分鐘內的如果數據更新,要從緩存開始往后推5分鐘才能看到。
即: 第1分鐘緩存,第1-6分鐘內的任何數據更新,要第6分鐘后才能看到。
所以,如果你無法容忍這種情況,請你不要濫用緩存,雖然性能提高了,但問題可能也出現了。反之,如果你可以容忍這種情況,那就可以這么操作,而至于過期設置多久,可以結合業務場景及使用頻率綜合來評估,畢竟不同的業務容忍度是不同的。
對于是否要將動態數據進行緩存,本質上,其實就是一種取舍,是一種性能與數據一致性的權衡,而 緩存的過期時長,就像是保持這種平衡的支點,從而讓這種犧牲變得更有意義 。
情況2:單機限流
限流,前面已經有介紹過,它有兩種常用的限流算法, 漏桶算法和令牌桶算法。不過 ,這兩種算法都僅支持單機限流,不支持全局限流。
單機限流,就是對單節點設定一個限流閾值,如果單節點上的請求到達閾值,則會拒絕請求。例如: 限流閾值=每秒100次請求,如果在1秒內單節點上,有第101次的請求則拒絕。
全局限流,就是對一組節點設定一個限流總閾值,如果這組節點上的匯總請求到達閾值,則會拒絕請求。例如: 10個節點的限流總閾值=1000次請求,如果在1秒內這組節點上,匯總有第1001次請求則拒絕,不過單節點上有超過第100次的請求也會接受。
這么看下來,感覺單機限流控制能力更厲害一點,它能保證單節點的請求不會超過100次。而全局限流在極端情況下,單節點都有可能在1秒內會接受1000次請求。當然,這種情況的可能性比較低,比如在突發請求時,9個節點同時宕機。
既然如此,那全局限流有存在的意義嗎,難道這就是 漏桶算法和令牌桶算法都不支持全局限流的原因。全局限流就真的沒有存在的意義嗎。 存在即合理,既然存在,那就一定有它存在的道理 。
換個情況,還是將 10個節點為一組,不過這次換成采用單機限流。問題來了,每個節點的限流閾值該如何設定,如果采用平均分配,則 限流閾值=每秒100次請求,讓我們來測試一下,在1秒內依次發出1000次請求,會發生什么現象。
結果是在第100次請求后,從第101次到第1000次的請求中,可能有些請求會發生被拒絕的情況,而且請求一會兒成功一會兒拒絕,沒有任何規律。原因可能是10個節點請求負載不均所引發的,導致某個節點提前超過了100次請求。
基于以上情況,最終1000次請求沒有全部成功,這種情況等同于降低了應用系統的吞吐能力。而在實際情況中,就算采用 輪詢的負載算法,請求數不均的可能性仍然還是會存在的 。這么一看,單機限流好像也有缺陷。
估計你已經被我說暈了吧,讓我們整體再重新梳理一遍,并對 兩 種不同限流模式的影響進行對比。不過,這次 還加上每秒不同的請求數量。
每秒請求 |
單機限流10*100筆/秒 |
全局限流1000筆/秒 |
100筆 |
無影響 |
無影 響 |
1000筆 |
少量請求拒絕 |
耗時小幅波動 |
10000筆 |
請求拒絕>9000 |
請求拒絕=9000耗時大幅波動 |
注:每個節點的處理能力為100筆/秒
兩種限流對比下來,單機限流更強調單機的控制范圍,但可能會造成額外的請求拒絕,但對單節點不會造成性能壓力,而全局限流更強調整體的控制范圍,雖不會造成額外的請求拒絕,但可能會對單節點造成性能壓力,引發性能過載。
除此之外, 全局限流還是一種采用中心化的設計思路 ,因此在網絡開銷方面,還會產生額外的性能損耗,這種損耗在請求量少的時候估計還可以容忍,但在高并發的情況下可能是場災難,因為在每次限流判斷前,還會產生一次網絡開銷。
所以,不能為了想要實現更精準的限流,就盲目地采用全局限流,它將在高并發的情況下損耗更多的性能。而單機限流所額外造成的少量請求拒絕,在某些情況下,可以考慮采用某些技術手段進行補償。
不過,不管是單機限流還是全局限流,似乎都和數據一致性沒有關系。但事實上,全局限流這種精準限流的方式,也可以視為另一種一致性的表現,而單機限流就是通過對這種一致性的犧牲,來減少性能損耗,何嘗不是提升性能的另一種方式。
以上,只是簡單列舉了兩種不同情況下的取舍,而在高并發架構上,可取舍的地方遠不止這些。你得知道, 高并發的每一處設計或每一份設計方案的背后,都曾是通過不斷地取舍所獲得的 ,而沒有取舍的高并發架構決策,將會顯得毫無說服力。
取舍不但可以作為高并發架構決策的有力武器,也將是驅動架構演進最合理的一種方式 。但要切記,取舍的方向并不是一成不變的,而是會隨著外界環境的變化而變化,它將是一種獨特的藝術。
寫在最后
高并發的魅力之處,就在于它沒有唯一的答案,而答案是需要我們以不同的業務場景作為線索去不斷地尋找,這種尋找的過程也是一種不斷思考的過程 ,這就是我為什么說高并發是一種架構思維模式。
本文從淺到深依次講述了性能是實現高并發的基礎條件,控制是實現資源最大化利用的方式,以及如何通過取舍來換取當前應用系統更所需的能力,但這些僅僅只是高并發世界里的一個角落。因篇幅有限,今天就暫告一段落。
最后想說, 高并發其實并不可怕,可怕的是你知其然而不知其所以然 。對于追求技術的你, 需要不斷地拓寬你的技術深度與廣度,才能更好地掌握高并 發,以及運用高并發的思維模式來提升應用系統處理能力。
原文地址:https://mp.weixin.qq.com/s/apUVMfAI52uvc7U-iOVvKg?utm_source=tuicool&utm_medium=referral