高并發、高性能、高可用系統設計經驗
軟件開發通常會提到一個名詞 “三高”,即高并發、高性能、高可用。
具體的指標定義,如:高并發方面要求QPS 大于 10萬;高性能方面要求請求延遲小于 100 ms;高可用方面要高于 99.99%。
接下來,我們重點來介紹這 三高
高并發
我們使用 QPS(Queries Per Second,每秒查詢率)來衡量系統承載能力。 架構策略有哪些?
1、負載均衡
正所謂雙拳難敵四手,高并發撐場面的首選方案就是集群化部署,一臺服務器承載的QPS有限,多臺服務器疊加效果就不一樣了。
如何將流量轉發到服務器集群,這里面就要用到負載均衡,比如:LVS 和 Nginx。
常用的負載算法有輪詢法、隨機法、源地址哈希法、加權輪詢法、加權隨機法、最小連接數法等
業務實戰:對于千萬級流量的秒殺業務,一臺LVS扛不住流量洪峰,通常需要 10 臺左右,其上面用DDNS(Dynamic DNS)做域名解析負載均衡。搭配高性能網卡,單臺LVS能夠提供百萬以上并發能力。
注意, LVS 負責網絡四層協議轉發,無法按 HTTP 協議中的請求路徑做負載均衡,所以還需要 Nginx
2、池化技術
復用單個連接無法承載高并發,如果每次請求都新建連接、關閉連接,考慮到TCP的三次握手、四次揮手,有時間開銷浪費。池化技術的核心是資源的“預分配”和“循環使用”,常用的池化技術有線程池、進程池、對象池、內存池、連接池、協程池。
連接池的幾個重要參數:最小連接數、空閑連接數、最大連接數
Linux 內核中是以 進程 為單元來調度資源的,線程也是輕量級進程。所以說,進程、線程都是由內核來創建并調度。協程是由應用程序創建出來的任務執行單元,比如 Go 語言中的協程“goroutine”。 協程本身是運行在線程上,由應用程序自己調度,它是比線程更輕量的執行單元。
在 Go 語言中,一個協程初始內存空間是 2KB(Linux 下線程棧大小默認是 8MB),相比線程和進程來說要小很多。協程的創建和銷毀完全是在用戶態執行的,不涉及用戶態和內核態的切換。另外,協程完全由應用程序在用戶態下調用,不涉及內核態的上下文切換。協程切換時由于不需要處理線程狀態,需要保存的上下文也很少,速度很快。
Go語言中協程池的實現方法有兩種:搶占式和調度式。
-
搶占式協程池,所有任務存放到一個共享的 channel 中,多個協程同時去消費 channel 中的任務,存在鎖競爭。
-
調度式協程池,每個協程都有自己的 channel,每個協程只消費自己的 channel。下發任務的時候,采用負載均衡算法選擇合適的協程來執行任務。比如選擇排隊中任務最少的協程,或者簡單輪詢。
3、流量漏斗
上面講的是正向方式提升系統QPS,我們也可以逆向思維,做減法,攔截非法請求,將核心能力留給正常業務!
互聯網高并發流量并不都是純凈的,也有很多惡意流量(比如黑客攻擊、惡意爬蟲、黃牛、秒殺器等),我們需要設計流量攔截器,將那些非法的、無資格的、優先級低的流量過濾掉,減輕系統的并發壓力。
攔截器分層:
-
網關和 WAF(Web Application Firewall,Web 應用防火墻)
采用封禁攻擊者來源 IP、拒絕帶有非法參數的請求、按來源 IP 限流、按用戶 ID 限流等方法
-
風控分析。借助大數據能力分析訂單等歷史業務數據,對同ip多個賬號下單、或者下單后支付時間過快等行為有效識別,并給賬號打標記,提供給業務團隊使用。
-
下游的每個tomcat實例應用本地內存緩存化,將一些庫存存儲在本地一份,做前置校驗。當然,為了盡量保持數據的一致性,有定時任務,從 Redis 中定時拉取最新的庫存數據,并更新到本地內存緩存中。
高性能
性能直接影響用戶的感官體驗,訪問一個系統,如果超過5秒沒有響應,絕大數用戶會選擇離開。
那么有哪些因素會影響系統的性能呢?
-
用戶網絡環境
-
請求/響應的數據包大小
-
業務系統 CPU、內存、磁盤等性能
-
業務鏈路的長度
-
下游系統的性能
-
算法實現是否高效
當然,隨著并發數的提升,系統壓力增大,平均請求延遲也會增大。
1、高性能緩存
對一些熱點數據每次都從 DB 中讀取,會給 DB 帶來較大的壓力,導致性能大幅下降。所以,我們需要用緩存來提升熱點數據的訪問性能,比如將活動信息數據在瀏覽器的緩存中保存一段時間。
緩存根據性能由高到低分為:寄存器、L1緩存、L2緩存、L3緩存、本地內存、分布式緩存
上層的寄存器、L1 緩存、L2 緩存是位于 CPU 核內的高速緩存,訪問延遲通常在 10 納秒以下。L3 緩存是位于 CPU 核外部但在芯片內部的共享高速緩存,訪問延遲通常在十納秒左右。高速緩存具有成本高、容量小的特點,容量最大的 L3 緩存通常也只有幾十MB。
本地內存是計算機內的主存儲器,相比 CPU 芯片內部的高速緩存,內存的成本要低很多,容量通常是 GB 級別,訪問延遲通常在幾十到幾百納秒。
內存和高速緩存都屬于掉電易失的存儲器,如果機器斷電了,這類存儲器中的數據就丟失了。
特別說明:在使用緩存時,要注意緩存穿透、緩存雪崩、緩存熱點問題、緩存數據一致性問題。當然為了提升整體性能通常會采用多級緩存組合方案(瀏覽器緩存+服務端本地內存緩存+服務端網絡內存緩存)
2、日志優化,避免IO瓶頸
當系統處理大量磁盤 IO 操作的時候,由于 CPU 和內存的速度遠高于磁盤,可能導致 CPU 耗費太多時間等待磁盤返回處理的結果。對于這部分 CPU 在 IO 上的開銷,我們稱為 “iowait”。
在IO中斷過程中,如果此時有其他任務線程可調度,系統會直接調度其他線程,這樣 CPU 就相應顯示為 Usr 或 Sys;但是如果此時系統較空閑,無其他任務可以調度,CPU 就會顯示為 iowait(實際上與 idle 無本質區別)。
磁盤有個性能指標:IOPS,即每秒讀寫次數,性能較好的固態硬盤,IOPS 大概在 3 萬左右。對于秒殺系統,如果單節點QPS在10萬,每次請求產生3條日志,那么日志的寫入QPS在 30W/s,磁盤根本扛不住。
Linux 有一種特殊的文件系統:tmpfs(臨時文件系統),它是一種基于內存的文件系統,由操作系統管理。當我們寫磁盤的時候實際是寫到內存中,當日志文件達到我們的設置閾值,操作系統會將日志寫到磁盤中,并將tmpfs中的日志文件刪除。
這種批量化、順序寫,大大提升了磁盤的吞吐性能!
高可用
高可用指標是指用來衡量一個系統可用性有多高。
-
MTBF(Mean Time Between Failure),系統可用時長
-
MTTR(Mean Time To Repair),系統從故障后到恢復正常所耗費的時間
- SLA(Service-Level Agreement),服務等級協議,用于評估服務可用性等級。計算公式是
MTBF/(MTBF+MTTR)
一般我們所說的可用性高于 99.99%,是指 SLA 高于 99.99%。
技術架構,高可用有哪些策略?
-
多云架構、異地多活、異地備份
-
主備切換,如redis緩存、mysql數據庫,主備節點會實時數據同步、備份。如果主節點不可用,自動切換到備用節點
-
微服務,無狀態化架構,業務集群化部署,有心跳檢測,能最短時間檢測到不可用的服務。
-
通過熔斷、限流,解決流量過載問題,提供過載保護
-
重視web安全,解決攻擊和XSS問題
1、主備切換,縮減故障時間
當系統出現故障時,首要任務不是立馬查找原因,考慮到故障的復雜樣,定位排查要花些時間,等問題修復好,SLA也降了好幾個檔。有沒有更快的方式解決這個問題? 那就是故障轉移。
當發現故障節點的時候,不是嘗試修復它,而是立即把它隔離,同時將流量轉移到正常節點上。這樣通過故障轉移,不僅減少了 MTTR 提升了 SLA,還為修復故障節點贏得了足夠的時間。
主備切換大致分為三步:
-
第一步故障自動偵測(Auto-detect),采用健康檢查、心跳等技術手段自動偵測故障節點;
-
第二步自動轉移(FailOver),當偵測到故障節點后,采用摘除流量、脫離集群等方式隔離故障節點,將流量轉移到正常節點;
-
第三步自動恢復(FailBack),當故障節點恢復正常后,自動將其加入集群中,確保集群資源與故障前一致。
2、熔斷,提供過載保護
所謂過載保護,是指負載超過系統的承載能力時,系統會自動采取保護措施,確保自身不被壓垮。
熔斷就是在系統瀕臨崩潰的時候,立即中斷服務,從而保障系統穩定避免崩潰。它類似于電器中的“保險絲”,當電流過大的時候,“保險絲”會先被燒掉,斷開電流,以免電路過熱燒毀電器引起火災。
例子:熔斷觸發條件往往跟系統節點的承載能力和服務質量有關,比如 CPU 的使用率超過 90%,請求錯誤率超過 5%,請求延遲超過 500ms, 它們中的任意一個滿足條件就會出現熔斷。
3、限流,提供過載保護
限流的原理跟熔斷有點類似,都是通過判斷某個條件來確定是否執行某個策略。但是又有所區別,熔斷觸發過載保護,該節點會暫停服務,直到恢復。限流,則是只處理自己能力范圍之內的請求,超量的請求會被限流。
限流算法主要有:計數器限流、滑動窗口限流、令牌桶限流、漏桶限流。網上的資料很多,這里就不多贅述。
4、降級
比如電商大促,業務在峰值時刻,系統抵擋不住全部的流量時,系統的負載、CPU 的使用率都超過了預警水位,可以對一些 非核心的功能進行降級 ,降低系統壓力,比如把 商品評價
、 成交記錄
等功能臨時關掉。 棄車保帥
, 保證 創建訂單、支付 等核心功能的正常使用 。
當然不同業務、不同公司處理方式也各不相同,需要結合實際場景,和業務方一塊討論,最后達成一個統一認可的降級方案。
總結下來:降級是通過暫時關閉某些非核心服務或者組件從而保護核心系統的可用性。