不懂高併發,薪資涼半截!

“高併發,幾乎是每個程序員都想擁有的經驗。原因很簡單:隨着流量變大,會遇到各種各樣的技術問題。

圖片來自 Pexels

比如接口響應超時、CPU load 升高、GC 頻繁、死鎖、大數據量存儲等等,這些問題能推動我們在技術深度上不斷精進。

在過往的面試中,如果候選人做過高併發的項目,我通常會讓對方談談對於高併發的理解。

但是能系統性地回答好此問題的人並不多,大概分成這樣幾類:

  • 對數據化的指標沒有概念:不清楚選擇什麼樣的指標來衡量高併發系統?分不清併發量和 QPS,甚至不知道自己系統的總用戶量、活躍用戶量,平峯和高峯時的 QPS 和 TPS 等關鍵數據。

  • 設計了一些方案,但是細節掌握不透徹:講不出該方案要關注的技術點和可能帶來的副作用。比如讀性能有瓶頸會引入緩存,但是忽視了緩存命中率、熱點 Key、數據一致性等問題。

  • 理解片面,把高併發設計等同於性能優化:大談併發編程、多級緩存、異步化、水平擴容,卻忽視高可用設計、服務治理和運維保障。

  • 掌握大方案,卻忽視最基本的東西:能講清楚垂直分層、水平分區、緩存等大思路,卻沒意識去分析數據結構是否合理,算法是否高效,沒想過從最根本的 IO 和計算兩個維度去做細節優化。

這篇文章,我想結合自己的高併發項目經驗,系統性地總結下高併發需要掌握的知識和實踐思路,希望對你有所幫助。

內容分成以下三個部分:

  • 如何理解高併發?

  • 高併發系統設計的目標是什麼?

  • 高併發的實踐方案有哪些?

 

如何理解高併發?

高併發意味着大流量,需要運用技術手段抵抗流量的衝擊,這些手段好比操作流量,能讓流量更平穩地被系統所處理,帶給用戶更好的體驗。

我們常見的高併發場景有:淘寶的雙 11、春運時的搶票、微博大 V 的熱點新聞等。

除了這些典型事情,每秒幾十萬請求的秒殺系統、每天千萬級的訂單系統、每天億級日活的信息流系統等,都可以歸爲高併發。

很顯然,上面談到的高併發場景,併發量各不相同,那到底多大併發纔算高併發呢?

不能只看數字,要看具體的業務場景。不能說 10W QPS 的秒殺是高併發,而 1W QPS 的信息流就不是高併發。

信息流場景涉及複雜的推薦模型和各種人工策略,它的業務邏輯可能比秒殺場景複雜 10 倍不止。因此,不在同一個維度,沒有任何比較意義。

業務都是從 0 到 1 做起來的,併發量和 QPS 只是參考指標,最重要的是:在業務量逐漸變成原來的 10 倍、100 倍的過程中,你是否用到了高併發的處理方法去演進你的系統。

從架構設計、編碼實現、甚至產品方案等維度去預防和解決高併發引起的問題?而不是一味的升級硬件、加機器做水平擴展。

此外,各個高併發場景的業務特點完全不同:有讀多寫少的信息流場景、有讀多寫多的交易場景,那是否有通用的技術方案解決不同場景的高併發問題呢?

我覺得大的思路可以借鑑,別人的方案也可以參考,但是真正落地過程中,細節上還會有無數的坑。

另外,由於軟硬件環境、技術棧、以及產品邏輯都沒法做到完全一致,這些都會導致同樣的業務場景,就算用相同的技術方案也會面臨不同的問題,這些坑還得一個個趟。

因此,這篇文章我會將重點放在基礎知識、通用思路、和我曾經實踐過的有效經驗上,希望讓你對高併發有更深的理解。

 

高併發系統設計的目標是什麼?

先搞清楚高併發系統設計的目標,在此基礎上再討論設計方案和實踐經驗纔有意義和針對性。

宏觀目標

高併發絕不意味着只追求高性能,這是很多人片面的理解。從宏觀角度看,高併發系統設計的目標有三個:高性能、高可用,以及高可擴展。

①高性能:性能體現了系統的並行處理能力,在有限的硬件投入下,提高性能意味着節省成本。

同時,性能也反映了用戶體驗,響應時間分別是 100 毫秒和 1 秒,給用戶的感受是完全不同的。

②高可用:表示系統可以正常服務的時間。一個全年不停機、無故障;另一個隔三差五出線上事故、宕機,用戶肯定選擇前者。另外,如果系統只能做到 90% 可用,也會大大拖累業務。

③高擴展:表示系統的擴展能力,流量高峯時能否在短時間內完成擴容,更平穩地承接峯值流量,比如雙 11 活動、明星離婚等熱點事件。

這 3 個目標是需要通盤考慮的,因爲它們互相關聯、甚至也會相互影響。

比如說:考慮系統的擴展能力,你會將服務設計成無狀態的,這種集羣設計保證了高擴展性,其實也間接提升了系統的性能和可用性。

再比如說:爲了保證可用性,通常會對服務接口進行超時設置,以防大量線程阻塞在慢請求上造成系統雪崩,那超時時間設置成多少合理呢?一般,我們會參考依賴服務的性能表現進行設置。

微觀目標

再從微觀角度來看,高性能、高可用和高擴展又有哪些具體的指標來衡量?爲什麼會選擇這些指標呢?

性能指標:通過性能指標可以度量目前存在的性能問題,同時作爲性能優化的評估依據。一般來說,會採用一段時間內的接口響應時間作爲指標。

①平均響應時間:最常用,但是缺陷很明顯,對於慢請求不敏感。比如 1 萬次請求,其中 9900 次是 1ms,100 次是 100ms,則平均響應時間爲 1.99ms,雖然平均耗時僅增加了 0.99ms,但是 1% 請求的響應時間已經增加了 100 倍。

②TP90、TP99 等分位值:將響應時間按照從小到大排序,TP90 表示排在第 90 分位的響應時間, 分位值越大,對慢請求越敏感。

③吞吐量:和響應時間呈反比,比如響應時間是 1ms,則吞吐量爲每秒 1000 次。

通常,設定性能目標時會兼顧吞吐量和響應時間,比如這樣表述:在每秒 1 萬次請求下,AVG 控制在 50ms 以下,TP99 控制在 100ms 以下。對於高併發系統,AVG 和 TP 分位值必須同時要考慮。

另外,從用戶體驗角度來看,200 毫秒被認爲是第一個分界點,用戶感覺不到延遲,1 秒是第二個分界點,用戶能感受到延遲,但是可以接受。

因此,對於一個健康的高併發系統,TP99 應該控制在 200 毫秒以內,TP999 或者 TP9999 應該控制在 1 秒以內。

可用性指標:高可用性是指系統具有較高的無故障運行能力,可用性=平均故障時間/系統總運行時間,一般使用幾個 9 來描述系統的可用性。

對於高併發系統來說,最基本的要求是:保證 3 個 9 或者 4 個 9。原因很簡單,如果你只能做到 2 個 9,意味着有 1% 的故障時間,像一些大公司每年動輒千億以上的 GMV 或者收入,1% 就是 10 億級別的業務影響。

可擴展性指標:面對突發流量,不可能臨時改造架構,最快的方式就是增加機器來線性提高系統的處理能力。

對於業務集羣或者基礎組件來說,擴展性=性能提升比例/機器增加比例,理想的擴展能力是:資源增加幾倍,性能提升幾倍。通常來說,擴展能力要維持在 70% 以上。

但是從高併發系統的整體架構角度來看,擴展的目標不僅僅是把服務設計成無狀態就行了,因爲當流量增加 10 倍,業務服務可以快速擴容 10 倍,但是數據庫可能就成爲了新的瓶頸。

像 MySQL 這種有狀態的存儲服務通常是擴展的技術難點,如果架構上沒提前做好規劃(垂直和水平拆分),就會涉及到大量數據的遷移。

因此,高擴展性需要考慮:服務集羣、數據庫、緩存和消息隊列等中間件、負載均衡、帶寬、依賴的第三方等,當併發達到某一個量級後,上述每個因素都可能成爲擴展的瓶頸點。

 

高併發的實踐方案有哪些?

瞭解了高併發設計的 3 大目標後,再系統性總結下高併發的設計方案,會從以下兩部分展開:先總結下通用的設計方法,然後再圍繞高性能、高可用、高擴展分別給出具體的實踐方案。

通用的設計方法

通用的設計方法主要是從「縱向」和「橫向」兩個維度出發,俗稱高併發處理的兩板斧:縱向擴展和橫向擴展。

縱向擴展(scale-up):它的目標是提升單機的處理能力。

方案包括如下兩種:

  • 提升單機的硬件性能:通過增加內存、CPU 核數、存儲容量、或者將磁盤升級成 SSD 等堆硬件的方式來提升。

  • 提升單機的軟件性能:使用緩存減少 IO 次數,使用併發或者異步的方式增加吞吐量。

橫向擴展(scale-out):因爲單機性能總會存在極限,所以最終還需要引入橫向擴展,通過集羣部署以進一步提高併發處理能力。

包括以下兩個方向:

①做好分層架構:這是橫向擴展的提前,因爲高併發系統往往業務複雜,通過分層處理可以簡化複雜問題,更容易做到橫向擴展。

上面這種圖是互聯網最常見的分層架構,當然真實的高併發系統架構會在此基礎上進一步完善。

比如會做動靜分離並引入 CDN,反向代理層可以是 LVS+Nginx,Web 層可以是統一的 API 網關,業務服務層可進一步按垂直業務做微服務化,存儲層可以是各種異構數據庫。

②各層進行水平擴展:無狀態水平擴容,有狀態做分片路由。業務集羣通常能設計成無狀態的,而數據庫和緩存往往是有狀態的,因此需要設計分區鍵做好存儲分片,當然也可以通過主從同步、讀寫分離的方案提升讀性能。

 

具體的實踐方案

下面再結合我的個人經驗,針對高性能、高可用、高擴展 3 個方面,總結下可落地的實踐方案。

高性能的實踐方案:

  • 集羣部署,通過負載均衡減輕單機壓力。

  • 多級緩存,包括靜態數據使用 CDN、本地緩存、分佈式緩存等,以及對緩存場景中的熱點 Key、緩存穿透、緩存併發、數據一致性等問題的處理。

  • 分庫分表和索引優化,以及藉助搜索引擎解決複雜查詢問題。

  • 考慮 NoSQL 數據庫的使用,比如 HBase、TiDB 等,但是團隊必須熟悉這些組件,且有較強的運維能力。

  • 異步化,將次要流程通過多線程、MQ、甚至延時任務進行異步處理。

  • 限流,需要先考慮業務是否允許限流(比如秒殺場景是允許的),包括前端限流、Nginx 接入層的限流、服務端的限流。

  • 對流量進行削峯填谷,通過 MQ 承接流量。

  • 併發處理,通過多線程將串行邏輯並行化。

  • 預計算,比如搶紅包場景,可以提前計算好紅包金額緩存起來,發紅包時直接使用即可。

  • 緩存預熱,通過異步任務提前預熱數據到本地緩存或者分佈式緩存中。

  • 減少 IO 次數,比如數據庫和緩存的批量讀寫、RPC 的批量接口支持、或者通過冗餘數據的方式幹掉 RPC 調用。

  • 減少 IO 時的數據包大小,包括採用輕量級的通信協議、合適的數據結構、去掉接口中的多餘字段、減少緩存 Key 的大小、壓縮緩存 Value 等。

  • 程序邏輯優化,比如將大概率阻斷執行流程的判斷邏輯前置、For 循環的計算邏輯優化,或者採用更高效的算法。

  • 各種池化技術的使用和池大小的設置,包括 HTTP 請求池、線程池(考慮 CPU 密集型還是 IO 密集型設置核心參數)、數據庫和 Redis 連接池等。

  • JVM 優化,包括新生代和老年代的大小、GC 算法的選擇等,儘可能減少 GC 頻率和耗時。

  • 鎖選擇,讀多寫少的場景用樂觀鎖,或者考慮通過分段鎖的方式減少鎖衝突。

上述方案無外乎從計算和 IO 兩個維度考慮所有可能的優化點,需要有配套的監控系統實時瞭解當前的性能表現,並支撐你進行性能瓶頸分析,然後再遵循二八原則,抓主要矛盾進行優化。

高可用的實踐方案:

  • 對等節點的故障轉移,Nginx 和服務治理框架均支持一個節點失敗後訪問另一個節點。

  • 非對等節點的故障轉移,通過心跳檢測並實施主備切換(比如redis的哨兵模式或者集羣模式、MySQL 的主從切換等)。

  • 接口層面的超時設置、重試策略和冪等設計。

  • 降級處理:保證核心服務,犧牲非核心服務,必要時進行熔斷;或者核心鏈路出問題時,有備選鏈路。

  • 限流處理:對超過系統處理能力的請求直接拒絕或者返回錯誤碼。

  • MQ 場景的消息可靠性保證,包括 Producer 端的重試機制、Broker 側的持久化、Consumer 端的 Ack 機制等。

  • 灰度發佈,能支持按機器維度進行小流量部署,觀察系統日誌和業務指標,等運行平穩後再推全量。

  • 監控報警:全方位的監控體系,包括最基礎的 CPU、內存、磁盤、網絡的監控,以及 Web 服務器、JVM、數據庫、各類中間件的監控和業務指標的監控。

  • 災備演練:類似當前的“混沌工程”,對系統進行一些破壞性手段,觀察局部故障是否會引起可用性問題。

高可用的方案主要從冗餘、取捨、系統運維 3 個方向考慮,同時需要有配套的值班機制和故障處理流程,當出現線上問題時,可及時跟進處理。

高擴展的實踐方案:

  • 合理的分層架構:比如上面談到的互聯網最常見的分層架構,另外還能進一步按照數據訪問層、業務邏輯層對微服務做更細粒度的分層(但是需要評估性能,會存在網絡多一跳的情況)。

  • 存儲層的拆分:按照業務維度做垂直拆分、按照數據特徵維度進一步做水平拆分(分庫分表)。

  • 業務層的拆分:最常見的是按照業務維度拆(比如電商場景的商品服務、訂單服務等),也可以按照核心接口和非核心接口拆,還可以按照請求源拆(比如 To C 和 To B,APP 和 H5)。

 

最後的話

高併發確實是一個複雜且系統性的問題,由於篇幅有限,諸如分佈式 Trace、全鏈路壓測、柔性事務都是要考慮的技術點。

另外,如果業務場景不同,高併發的落地方案也會存在差異,但是總體的設計思路和可借鑑的方案基本類似。

高併發設計同樣要秉承架構設計的 3 個原則:簡單、合適和演進。“過早的優化是萬惡之源”,不能脫離業務的實際情況,更不要過度設計,合適的方案就是最完美的。

希望這篇文章能帶給你關於高併發更全面的認識,如果你也有可借鑑的經驗和深入的思考,歡迎評論區留言討論。

 

作者:駱俊武

編輯:陶家龍

出處:轉載自微信公衆號 IT 人的職場進階(ID:BestITer)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章