談談高並發系統的一些解決方案

本文結合項目經驗,整理一份大綱,供參考。

常用指標

  • RT(Response Time):響應時間。可能會衍生出 TP999、TP99、TP95、TP90等指標。一般在幾毫秒到幾百毫秒之間。

  • QPS(Query Per Second):每秒查詢量。這是我們最常說的一個指標了。視業務複雜度不同而不同,輕量級的可能單機上萬,重量級的可能就幾百,這是主要依靠水平擴容來解決。

  • TPS(Transaction per second):每秒事務量。主要衡量數據庫性能,一般比 QPS 低 1~3 個數量級。

  • 吞吐量(Throughput):單位時間內處理的請求數量。該指標概念比較寬泛:

    • 從業務角度看,吞吐量可以用QPS、TPS等單位來衡量。

    • 從網絡角度看,吞吐量可以用bps來衡量。一般用於排查網絡抖動等問題,尤其是弱網環境下的RPC請求會更加關注。

  • PV(Page View):頁面瀏覽量。一般用於統計頁面訪問頻次,每次刷新頁面即被計算一次。高並發系統的單日 PV 基本上會超過千萬。

  • UV(Unique Visitor):獨立訪客量。用於統計單位時間內頁面訪問的用戶數,同一個用戶多次刷新頁面只會被算做一次。高並發系統的單日 UV 基本上會過萬。

 

提升系統性能的兩個維度

  • 垂直擴展:提升單機性能。包括硬件配置和軟件編寫方式兩種維度。

  • 水平擴展:集群整體性能。無狀態服務加機器可解決;有狀態服務還要額外考慮狀態存儲及遷移。實踐中盡量做成無狀態服務。

  

災備管理

多區域部署機房,通常存在冷備、熱備、雙活等幾種形式。主要用於流量分攤和故障轉移。

 

故障處理

  • Failover 失敗自動切換:當出現失敗,重試其它服務器,通常用於讀操作。一般核心服務會使用該策略。

  • Failfast 快速失敗:只發起一次調用,失敗立即報錯,通常用於非冪等性的讀寫操作。多數場景均有重試機制。

  • Failsafe 失敗安全:出現異常時,直接忽略。通常用於寫入日誌等操作。

  • Failback 失敗自動恢復:後台記錄失敗請求,定時重發。通常用於消息通知等操作。

  • Forking 並行調用多個服務方:其中一個成功即可返回,通常用於實時性要求較高的讀操作。

  • Broadcast廣播調用:所有提供方逐個調用,任意一台報錯則報錯。主要用於RPC框架註冊節點使用(更新提供方本地狀態),應用型服務基本不會使用。

  

負載均衡

  • 監控機器性能,並配置機器的權重(靜態或動態)。

    • 處理能力越強,分到的流量越多。

    • 某台機器故障時自動摘除流量。

    • 服務剛啟動時的小流量預熱,防止瞬間高流量把機器打崩。主要是各類中間件資源初始化可能會很耗時,導致請求響應慢,此時如果大量流量湧入會導致 Cpu Load急劇升高,甚至可能打崩。

  • 自動擴縮容。業務高峰時段自動擴容,低峰時段自動縮容,節約成本。

 

區分服務等級

  • 核心服務:一般都存在Backup,出錯時自動切換,同時觸發中高級別告警。

  • 非核心服務:出錯時可執行(手動或自動)降級甚至熔斷,同時觸發中低級別告警。

  

使用緩存

主要針對不易變化的數據,可能是多級緩存,可能橫跨客戶端和多個服務端。

  • 本地緩存。如 ConcurrentMap、Guava Cache、Caffeine。

  • 分佈式緩存。如 Redis、GemFire/Geode。

  

異步操作

  • 架構層面:

    • 如使用MQ消息隊列進行削峰、解耦處理,日誌處理也用得比較多。

    • 若追求 Cpu 的穩定性,可使用 Spring WebFlux 等全鏈路異步化技術,需要上下游服務都改造,才能有顯著效果。

  • 代碼層面:如 Java 中的 Future 機制(常用 CompletableFuture),同時發起多個微服務的調用,隔一段時間後統一 get 結果。

  

批量執行

  • 框架層面:可參考 Hystrix 的請求合併機制 HystrixCollapser。

  • 代碼層面:服務接口批量調用數據,拿到批量結果後再分派結果。

  

池化技術

  • 線程池:常用如 ThreadPoolExecutor。

  • 連接池:常用如 HikariCP、Druid、c3p0、DBCP。

  • 對象池:常用如 Apache Commons Pool2。另外,如 Integer 等包裝類針對(-127~128)的對象緩存,其實也是一種對象池的體現。

  

限流處理

  • 服務入口:監控近實時統計QPS,達到閾值時拒絕請求。

  • 常見的幾種限流框架:

    • 單機版(JDK自帶的鎖、信號量、Guava Limiter)

    • 分佈式(基於 Redis 的 redis-cell 模塊和 Redisson、重量級的 Sentinel,以及老牌框架Hystrix)

  • 常見的幾種限流算法:

    • 計數器法。存在臨界流量問題,基本不會使用。

    • 滑動窗口。時間片劃分精度不好控制,基本不會使用。

    • 漏桶算法。難以應對突發流量,使用較少。

    • 令牌桶算法。常用。

  • 常見的線程池拒絕策略:

    • CallerRunsPolicy 由調用者運行。

    • AbortPolicy 拋棄並拋異常。這是默認策略,也是最常用的策略,可以讓應用層快速發現失敗,進而介入處理。

    • DiscardPolicy 靜靜地拋棄,應用層無法感知到。

    • DiscardOldestPolicy 拋棄最老的請求。

  

防刷分流

搭建兩套服務集群,將存在爬蟲標記(依賴於專業的爬蟲識別算法)的流量分流到另一套集群,甚至可以返回假數據,做蜜罐處理。

 

靜態資源分發

主要依賴 CDN 技術進行資源的就近部署,可提前預熱。常見如html、js、css、image等資源。

 

數據庫並發

  • 單機:MVCC、事務隔離、做好索引優化。

  • 集群:分庫分表、讀寫分離。

  • 結合其他中間件:如簡單的查詢、統計,或者文本搜索等場景,可使用 ElasticSearch,必要時進行二級檢索( ElasticSearch 檢索出 id,再到 SQL 中查詢)。

 

壓力測試 /性能測試

  • Apache JMeter。

  • 搭建壓測集群,平時抓取服務真實流量,節日或大促前進行必要的壓測,以暴露性能瓶頸。

  

日常巡檢/故障演練

用於提前發現問題,如接口掃描、混沌工程就是做這些事情的。

 

大綱就寫到這裡,你還有其他解決方案嗎?歡迎評論區討論。