應對「高併發」的思路

應對「高併發」的思路

我是一個著迷於產品和運營的技術人,樂於跨界的終身學習者。歡迎關注我喲~

每週五12點 按時送達~

我的第「191」篇原創敬上

大家好,我是Z哥。

最近正好在編寫一套新的面試題,其中有一道是與高併發相關的。出題的目的是想了解一下候選人面對大流量場景下的最佳化思路。在出這道題的過程中,我自己也梳理了一下自己對這個問題的思路。

然後之所以想在這裡分享出來,是因為這是我這些年經過實戰所得出的躺著“血汗”的經驗,所以我覺得這可能對很多讀者都有所幫助。

不過醜話說在前面,我的思路並不是完美的,畢竟條條大路通羅馬,解決「高併發」的思路也有很多種。所以歡迎大家在評論區分享你的看法。

/01  高併發的定義/

首先,高併發的定義是什麼?

高併發意味著大流量,需要運用技術手段抵抗流量的衝擊。這些手段的效果好比你可以操縱流量,讓流量可以乖乖地按照你的期望、更平穩地被系統所處理,帶給使用者更好的體驗。對,就和大禹治水那樣。

我想,幾乎每個程式設計師心裡都有過一個疑問,達到多少QPS或者TPS才算是高併發?

其實,這個問題無法單純地透過一個統一標準的數字來判斷。因為不同的業務所對應的複雜度不同,不能一概而論。

我自己用的評判標準是,當一個執行正常的系統在沒有刻意最佳化效能的情況下,出現了效能問題,說明他開始進入到高併發的範圍了。

對,就是這麼簡單粗暴,沒有具體的數字。

但是在高併發是常態的場景中,一般都會有一個監控系統,會持續觀察至少以下這幾個指標是否正常。

TPS。每秒事務處理量,這裡的事務是指一個客戶機向伺服器傳送請求然後伺服器做出反應的過程。(以客戶端為視角)

QPS。每秒查詢率,可以透過併發量 / 平均響應時間計算得出。(以服務端為視角,一個TPS可能會對應多個QPS)

併發使用者數。系統可以同時承載的正常使用系統功能的使用者的數量。

響應時間。很多時候也叫RT,是指系統對請求作出響應的時間。

頻寬。如果是一個數據傳輸量比較大的業務,還需要考慮頻寬問題,比如影片、音訊類應用程式。

如果你連這些指標的含義都不是很清楚,那就多讀幾遍,先搞清楚它們,否則你的高併發是閉著眼睛在做。

/02  應對高併發的思路/

很多缺乏經驗的小夥伴,遇到高併發問題,不管三七二十一,上來就懟快取。

用快取是沒錯的,但是快取也不是靈丹妙藥,在哪裡都適合。畢竟,快取是「有狀態」的,在軟體開發中,處理「有狀態」的東西總是比「無狀態」的麻煩得多,因為存在資料一致性的問題需要考慮。

我建議你按照以下三個步驟來考慮這個問題。

01

梳理請求流轉鏈路

梳理好了請求流轉的鏈路,就好比你手裡有了一張“作戰地圖”,你可以更加直觀、準確地進行排兵佈陣。

畢竟軟體開發本身就是一個工程的問題,我們不能憑感覺,拍腦袋去做事。解決高併發問題自然也不例外。

02

確定目標

有一句話說得好,“沒有最好,只有更好”。如果不先確定目標,這件事將會變成對效能無止境地追求。而過度的高效能,不但不會產生額外的收益,反而是對投入成本的浪費,畢竟資源是有代價的,而且是有限的。

我建議你將具體的數值目標細化到每一個 API 上。比如我一般會先確定整個業務線的 TPS 要達到多少。例如,下單的 TPS 要達到100。

接下來,我會根據前面梳理好的鏈路圖,得到其中會涉及到哪些 Service ,

哪些

API ,有哪些 API 還會被不同的 Service 多次呼叫的。

然後會根據鏈路中 API 被呼叫的先後順序(一般越上游的 API 定的數值要適當放大)以及會被重複呼叫的次數,得到每個 API 的 QPS 目標。比如,

獲取購物車列表,存在兩次呼叫,QPS = 200。

批次獲取商品庫存,存在三次呼叫,QPS = 300。

獲取會員資訊,存在一次呼叫,QPS = 100。

……

照著這個思路,把所有業務線中會涉及到的 API 的 QPS 都確定好,然後將相同 API 的 QPS 數值相加,就能得到在整個系統層面每個 API 的理想 QPS 數值(相當於是在各條業務線都達到預估峰值的情況下)。比如,

獲取購物車列表,出現在三個業務線,QPS = 200 + 500 + 100 = 800。

批次獲取商品庫存,出現在兩個業務線,QPS = 300 + 200 = 500。

獲取會員資訊,出現在兩個業務線,QPS = 100 + 200 = 300。

……

當然了,實際制定的目標除了 QPS 以外,還有其它指標,上面只是舉個例子。

另外,需要額外注意的是,要關注 TP90、TP99 的「響應時間」。因為就算平均響應時間達標了,也不代表整個系統很穩定。因為可能存在80%的請求響應速度特別快,把平均值拉低了,但是同時還存在大量的耗時特別嚴重的請求。我慣用的經驗是,如果TP99超過了平均值的一倍,就需要引起重視了,因為這意味著某個地方存在著明顯的效能瓶頸。

03

制定具體的最佳化方案

其實具體的最佳化方案有很多種,要根據實際的情況來選擇。不過具體怎麼選擇,還是需要你有全域性視野。因為整個系統是一體的,透過相互配合形成合力,可以大大降低最佳化的難度。比如,選擇某個最佳化方案後,上游的 API 能扛掉90%的流量不往下游請求,那麼下游 API 的 QPS 目標也可以適當降低了。

以下就是我綜合複雜度和成本所排列的具體方案,從簡單到複雜。

先在程式碼層面做最佳化,比如程式碼效能,多執行緒,請求合併,池化(連線池、執行緒池、物件池等等)。

能升級硬體的就升級硬體。

能用快取解決的堅決不做系統拆分。並且,快取儘可能做到上游。比如, cdn >頁面> api > service 。

如果在資料處理上產生瓶頸,那麼優先考慮業務上是否接受非同步,如果接受,那麼用 MQ 來削峰填谷。或者限流降級。

如果 MQ 也不頂用,是整體吞吐量上的瓶頸的話,只要不是寫資料的瓶頸,儘量透過程式拆分而不是資料庫拆分來解決問題。(此時需要引入服務治理,另外,會存在一致性問題,資料合併問題要解決)

非得動到資料層面的話,優先考慮資料庫讀寫分離,而不是直接拆分資料。

實在迫不得已需要拆分資料,優先考慮根據業務垂直拆分,而不是水平拆分。(水平拆分的資料合併代價會比垂直拆分更大)

最後才是資料庫水平拆分,支援無限擴容,一勞永逸。

你看,雖然方案有很多,但是你也能觀察到一些規律:越在上游解決問題,成本越低。所以,以漏斗思維來做全域性的考慮是最適合不過的。從客戶端請求到接入層,到邏輯層,到 DB 層,層層遞減,過濾掉請求,哪怕是出現異常也要 Fail Fast(要失敗也要儘早返回)。

/03  落地/

真正落地的時候,涉及到的具體技術細節就多了,負載均衡啊、快取啊、訊息佇列啊、分庫分表啊等等。我這裡就不展開了,每一點展開都要寫好多。有興趣的小夥伴可以移步我之前寫的分散式系統系列文章——《

8個月打磨,一份送給程式設計師的「分散式系統」合集

其實,真正要把高併發當作一個系統化的事情來看待,視野不能僅僅侷限在「效能」這一個維度上,還至少需要考慮「可用性」和「擴充套件性」這兩個方面。

可用性就是系統可以正常服務的時間。一個雖然訪問速度沒那麼快,但是全年不停機、無故障;另一個雖然訪問速度很快,但是隔三差五出現上事故、宕機,使用者肯定選擇前者。

擴充套件性表示系統的快速擴充套件能力,當遇到突發的大流量衝擊時,能否在短時間內完成擴容,以承接這部分流量。比如,雙11活動、明星熱搜等場景。畢竟,我們不可能準確地預測未來的流量一定在什麼範圍內,也更加不可能隨時為它準備著大量冗餘的資源。

所以,這三個目標是需要通盤考慮的,因為它們互相關聯、甚至相互影響。

比如,當你考慮系統擴充套件能力的時候,你會將服務設計成無狀態的,這種設計其實不但提高了可擴充套件性,其實也間接提升了系統的效能和可用性,因為你可以隨時橫向擴充套件。

再比如,為了提高可用性,通常會對服務介面進行超時設定,以防大量執行緒阻塞在慢請求上造成系統雪崩。具體超時時間的設定成多少,參考的就是 API 的效能表現。

好了,總結一下。

這篇呢Z哥和你分享了我對高併發問題的處理思路。

按照以下三個步驟進行。

梳理請求流轉鏈路

確定目標

制定具體的最佳化方案

其中的第2和3點在文中給了很多實操細節,這裡就不一一羅列了。

希望對你有所幫助。

業務都是從0到1做起來的,在業務量逐漸變成原來的10倍、100倍的過程中,你是否用到了高併發的處理思路去演進你的系統,從架構設計、編碼實現、甚至產品方案等維度去預防和解決高併發的問題?

推薦閱讀:

8個月打磨,一份送給程式設計師的「分散式系統」合集

我在職場經歷的四個階段

也可以「關注」我,帶你以技術思維看世界~

內容包括:架構設計丨分散式系統丨產品丨運營丨個人深度思考。

相關文章