在如間的網路環境下,高併發的場景無處不在,特別在面試如何解決高併發是一個躲不過的問題,即使生產環境達不到那麼高的qps但是也應該給自己留條後路來應對日後可能發生的高併發場景,不用匆忙的加班加點的進行重構。
在應對日常高併發場景常常會有這麼幾個方法:
- 叢集&負載均衡SLB
- 讀寫分離&分庫分表
- 快取
- 非同步佇列(RebbitMQ)
- 分散式系統、微服務
接下來就由淺入深分別來介紹下這幾個方法是怎麼應用到伺服器並且解決高併發的,首先我們先來看下最原始的也是最簡單的伺服器與應用程式關係。
圖1
如圖1所示在一臺伺服器上承載了資料庫、檔案系統、應用程式的所有功能,這就導致即使低qps的情況下伺服器的記憶體或者cpu佔比都非常高,用過sqlserver的同僚們都知道為了達到最高效快速的資料查詢、儲存及運算支援sql server預設會盡可能的佔用記憶體及CPU來達到自己的目的,從而導致我們的應用程式在處理一些運算或者請求量相對升高時應用程式就會變得非常慢,這時候我們就該考慮升級我們現有的伺服器了,當然最高效也是最便捷的方式是升級硬體(cpu、記憶體、硬碟),這也是最容易達到瓶頸的畢竟一臺服務的硬體也是有瓶頸的而且費用也是相當相當高昂的,一般情況下我們會選擇我們最開始提到解決高併發方法中分散式來升級我們圖1的單一伺服器系統架構。
圖2
如圖2所示我們由一臺伺服器轉為三臺伺服器互相協作的方式來處理每次請求,這也是簡單的分散式系統每臺伺服器各司其職再也不會發生單一應用佔用大量cpu或記憶體的情況導致請求變得緩慢,但是就圖2而言的伺服器架構的承載能力也是非常有限的,當請求量上升後可能就扛不住當機了。
一般這時候我們就要分析發生當機的原因,從圖2便知只有伺服器A或者伺服器B最有可能出現問題,根據以往的經驗在請求量升高時資料庫會承載絕大部分的壓力,如果資料庫崩了那麼整個應用就會處於不可用的狀態,那麼為了緩解資料庫的壓力,我們很自然的就會想到利用快取,這也是高併發場景下最常用也是最有效最簡單的方案,利用好快取能讓你的系統的承載能力提示幾倍甚至十幾倍幾十倍。熟悉二八原則的同僚們都知道80%請求的資料都集中在20%的資料上,雖然有些誇張但是意思就是這麼個意思。快取又分為本地快取和分散式快取,本著分散式的原則,我們一般都會選用分散式快取同時也是為後期做分散式叢集打下基礎。
圖3
如圖3所示在圖2的基礎上增加了一臺快取伺服器D來儲存我們的快取資料,一般我們會採用redis來存放快取資料,至於memcache現在應用的頻率是非常低的。現在當請求到達應用程式時會優先訪問快取伺服器D,若存在快取資料就直接返回給客戶端如果不存在快取資料才會去資料庫獲取資料返回給客戶端同時將資料儲存到快取伺服器D設定快取失效時間這樣下次請求時就不用到資料庫查詢資料了從而達到減輕資料庫壓力的目的。雖然快取能抵擋大部分的請求,但是我們也要做好防止快取擊穿、穿透和雪崩的問題來提升系統的穩定性。
隨著業務量的增多和繁多的業務種類圖3的系統架構也會慢慢達到瓶頸支撐不住多樣化的業務需求,這時候我們就應該採用叢集的方式來達到負載均衡的目的,將請求平均的分散到多臺伺服器來擴充應用程式的承載能力。
圖4
如圖示4所示由伺服器A-1、伺服器A-2共同承載使用者的請求來提高系統的承載能力也就是我們最開始說到叢集,出現叢集的地方必然少不了負載均衡,圖4我們由nginx來實現請求的分發來達到負載均衡的目的。在設計圖3的架構的時候我們有說到本地快取,如果是採用本地快取而不是分散式快取那麼系統架構就存在一個比較大的缺陷,因為一個請求過來是由nginx區分發的如果我們再用本地快取那麼在在伺服器A-1和伺服器A-2上可能存在大量相同的本地快取這樣就得不償失了容易造成伺服器資源的浪費嚴重的還會拖累伺服器的效能,利用分散式快取的好處在於我們不管有多少個應用伺服器所有的快取都是共享的。
圖4的伺服器架構應該是目前中小型應用中最常用的,而且系統的整體承載能力也相當不錯,不過隨著業務的發展流量與日俱增,圖3的伺服器架構也很難保證系統的穩定,特別是日常流量峰值的一些時段圖3的系統可能時常會面臨奔潰的危險,這時候就要重新分析各伺服器的壓力承載情況了,顯而易見最可能出現問題的就是資料庫伺服器,終於要對資料庫下手了,當下最有效的方法就是就寫分離,還是遵循二八原則80%的資料操作都是查詢操作。
圖5
如圖5所示在圖4的基礎上由單一的資料庫變為主從資料庫從庫負責資料的查詢操作主庫負責資料增刪改操作,但請求操作主庫後主庫將操作日誌執行到從庫達到主從資料一致的目的,但是主從分離後不可能避免的一個問題就是主從資料一致性會有延遲,資料同步延遲的問題只能儘可能的減小資料延遲的時間,但對一些時效性非常高或者不能容忍資料延遲的請求只能做一些妥協,這類操作的crud都在主庫上操作這樣就避免資料延遲的問題,對一些對於資料時效性不那麼嚴格的請求可以將這部分的查詢操作由從庫去承載,對於主從資料庫個人以為和應用叢集是一樣的可以理解為叢集資料庫只不過在請求的分發上制定了規則(主庫處理更新、從庫處理查詢)。
佇列下回補充。。。個人對佇列的理解不夠透徹需要好好組織下再發表。。。