談談高並發系統的一些解決方案
本文結合項目經驗,整理一份大綱,供參考。
常用指標
-
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。
-
搭建壓測集群,平時抓取服務真實流量,節日或大促前進行必要的壓測,以暴露性能瓶頸。
日常巡檢/故障演練
用於提前發現問題,如接口掃描、混沌工程就是做這些事情的。
大綱就寫到這裡,你還有其他解決方案嗎?歡迎評論區討論。