高效能架構設計#
高效能是架構設計中最常見的需求之一。本章介紹高效能架構的核心模式,包括快取、非同步處理和負載均衡。
高效能的複雜度來源#
高效能帶來的複雜度主要體現在兩個層面:
| 複雜度維度 | 單機複雜度 | 叢集複雜度 |
|---|---|---|
| 核心問題 | 行程/執行緒模型、I/O 模型、並發控制、資源管理 | 任務分配(負載均衡)、任務分解(服務拆分)、狀態管理、資料一致性 |
架構設計決定了系統效能的上限,實作細節決定了系統效能的下限。如果架構設計沒有做到高效能,後面的最佳化空間是有限的。
單機高效能模式#
並發模型#
並發模型有兩個關鍵設計點:
- I/O 模型:阻塞、非阻塞、同步、非同步
- 行程模型:單行程、多行程、多執行緒
| 模式 | 特點 | 適用場景 | 代表產品 |
|---|---|---|---|
| PPC (Process Per Connection) | 每連接一個行程 | 連接數少,處理邏輯重 | 傳統 CGI |
| TPC (Thread Per Connection) | 每連接一個執行緒 | 連接數中等 | 傳統 Servlet |
| Reactor | 非阻塞 I/O + 多路復用 | 高並發連接 | Nginx, Redis |
| Proactor | 非同步 I/O | 高並發 + 重計算 | Windows IOCP |
Reactor 模式三種子模式#
Reactor 的核心是「I/O 多路復用 + 非阻塞 I/O」。常見有三種子模式,差別在於 Acceptor / Handler 是否分離、是否多執行緒:
| 子模式 | 結構 | 優缺點 | 代表 |
|---|---|---|---|
| 單 Reactor 單執行緒 | 一個 Reactor 同時處理 Accept 與 I/O | 簡單、無並發問題;無法利用多核 | Redis |
| 單 Reactor 多執行緒 | Reactor 收事件,Handler 交執行緒池 | 可用多核;Reactor 仍是單點瓶頸 | - |
| 多 Reactor 多執行緒/多行程 | Main Reactor 分發連接給 Sub Reactor | 充分利用多核,Reactor 不再瓶頸;實作較複雜 | Nginx, Memcached |
在現代高效能伺服器中,多 Reactor 已是主流選擇;Redis 則因為自身單執行緒模型的設計取捨而採用單 Reactor。
快取架構#
快取是高效能架構中最常用的技術,基本原理是將可能重複使用的資料放到記憶體中,一次生成、多次使用。
快取帶來的問題#
雖然快取能大幅提升效能,但也引入了架構複雜性:
| 問題 | 描述 | 解決方案 |
|---|---|---|
| 快取穿透 | 查詢不存在的資料,每次都打到 DB | 空值快取、布隆過濾器 |
| 快取雪崩 | 大量快取同時過期 | 更新鎖、後台更新、隨機過期時間 |
| 快取熱點 | 熱門資料集中在少數快取節點 | 多副本、本地快取 |
快取穿透詳解#
場景一:資料確實不存在
每次查詢都繞過快取直打資料庫,相當於快取形同虛設。常見對策是對空結果也快取(設較短的 TTL),讓後續同樣的查詢由快取吸收。
data := cache.Get(key)
if data == nil {
data = db.Query(key)
if data == nil {
cache.Set(key, EMPTY_VALUE, 60) // 關鍵:對空值也設較短 TTL
} else {
cache.Set(key, data, 3600)
}
}場景二:快取生成耗時長
例如電商分頁,爬蟲遍歷所有頁面時,大量冷門分頁的快取已過期,需要重新從 DB 生成。
這種情況沒有完美解決方案。常見做法是識別爬蟲並限流,或做好監控及時處理。
快取雪崩詳解#
當大量快取在同一時間點失效,原本由快取吸收的流量會瞬間全部打到資料庫,造成 DB 崩潰。常見對策有兩種:
方案一:更新鎖
只允許一個請求去查 DB 並回寫快取,其他請求等待或返回預設值。
def get_data(key):
data = cache.get(key)
if data is None:
if lock.acquire(key): # 嘗試取得分散式鎖
try:
data = db.query(key)
cache.set(key, data)
finally:
lock.release(key)
else:
time.sleep(0.1)
return get_data(key)
return data方案二:後台更新
- 快取永不過期
- 後台執行緒定時更新
- 業務執行緒只讀快取
後台更新的優勢:既適用於單機多執行緒,也適用於分散式叢集,實作比更新鎖簡單。
快取熱點詳解#
某明星發微博,瞬間上千萬用戶湧入查看。即使是 Redis,單 key 的 QPS 也有上限。
解決方案:多副本 + 隨機讀取
原始:key = "weibo_12345"
副本:key = "weibo_12345_1", "weibo_12345_2", ..., "weibo_12345_100"
讀取時隨機選擇一個副本不同副本的過期時間要隨機化,避免同時失效。
負載均衡架構#
當單機效能無法滿足需求時,需要通過叢集來擴展效能。
負載均衡分類#
| 類型 | 範圍 | 特點 | 代表產品 |
|---|---|---|---|
| DNS 負載 | 全局 | 地理位置、運營商分流 | DNS 服務商 |
| 硬體負載 | 本地 | 高效能、高可靠、價格昂貴 | F5, A10 |
| 軟體負載 | 本地 | 靈活、成本低 | LVS, Nginx |
負載均衡演算法#
| 演算法 | 描述 | 適用場景 |
|---|---|---|
| 輪詢 | 依次分配 | 伺服器效能相近 |
| 加權輪詢 | 按權重分配 | 伺服器效能差異明顯 |
| 隨機 | 隨機選擇 | 大量請求時效果接近輪詢 |
| 最少連接 | 選擇連接數最少的 | 長連接場景 |
| 來源位址 Hash | 同一來源固定到同一伺服器 | 需要會話保持 |
| 一致性 Hash | 節點變化時影響範圍小 | 分散式快取 |
一致性 Hash 重點
問題背景:傳統 Hash(如 hash(key) % N)在節點增減時,幾乎所有資料的映射都會改變,造成快取大面積失效。
核心步驟:
- 將 hash 空間視為一個 0 ~ 2³² 的環
- 節點與資料都對應到環上的某個位置
- 資料 key 順時針找到的第一個節點即為其歸屬節點
- 節點增減只影響相鄰一小段範圍,其他資料映射不變
虛擬節點:為每個物理節點建立多個虛擬節點打散到環上,解決資料分布不均的問題。Go 中的典型結構就是一個 map[uint32]string(hash → 節點)加上排序後的 hash 切片,查詢時對 hash 做二分搜尋。
非同步處理架構#
非同步處理是提升系統吞吐量的重要手段,核心思想是將非關鍵路徑的操作延後處理。
非同步架構模式#
flowchart LR
subgraph 同步處理
A1[用戶] --> B1[業務邏輯]
B1 --> C1[發短信<br/>100ms]
C1 --> D1[記日誌<br/>50ms]
D1 --> E1[返回<br/>總耗時 150ms+]
end
subgraph 非同步處理
A2[用戶] --> B2[業務邏輯]
B2 --> E2[返回<br/>立即]
B2 -.-> Q[訊息佇列]
Q -.-> S1[簡訊服務]
Q -.-> S2[日誌服務]
end訊息佇列選型#
| 產品 | 特點 | 適用場景 |
|---|---|---|
| RabbitMQ | 功能完善,AMQP 協議 | 業務訊息 |
| Kafka | 高吞吐,持久化 | 日誌收集、流處理 |
| RocketMQ | 事務訊息,順序訊息 | 交易系統 |
| Redis | 簡單,速度快 | 輕量級任務佇列 |
非同步設計要點#
非同步不是銀彈,引入訊息佇列會帶來以下問題:
- 訊息遺失:需要持久化和確認機制
- 訊息重複:消費端需要冪等處理
- 訊息積壓:需要監控和擴容機制
- 順序問題:可能需要特殊處理
效能最佳化方法論#
最佳化層次#
flowchart TB
A["業務最佳化<br/>(效果最大)"] --> B["架構最佳化"]
B --> C["程式碼最佳化"]
C --> D["設定最佳化<br/>(效果最小)"]
style A fill:#c8e6c9
style B fill:#e8f5e9
style C fill:#fff3e0
style D fill:#ffecb3常見最佳化手段#
| 層面 | 最佳化手段 | 效果 |
|---|---|---|
| 業務 | 減少不必要的功能 | 最好的效能最佳化是不做 |
| 架構 | 快取、非同步、拆分 | 數量級提升 |
| 程式碼 | 演算法最佳化、減少 I/O | 倍數級提升 |
| 設定 | JVM 參數、連線池 | 百分比提升 |
本章小結#
| 模式 | 核心思想 | 典型應用 |
|---|---|---|
| 單機並發 | 選擇合適的 I/O 和行程模型 | Reactor (Nginx, Redis) |
| 快取 | 空間換時間,減少重複計算 | Redis, Memcached |
| 負載均衡 | 任務分配,水平擴展 | Nginx, LVS |
| 非同步 | 削峰填谷,提升吞吐量 | Kafka, RabbitMQ |
記住這個公式:高效能 = 合適的並發模型 + 快取 + 負載均衡 + 非同步處理