高效能架構設計#

高效能是架構設計中最常見的需求之一。本章介紹高效能架構的核心模式,包括快取、非同步處理和負載均衡。

高效能的複雜度來源#

高效能帶來的複雜度主要體現在兩個層面:

複雜度維度單機複雜度叢集複雜度
核心問題行程/執行緒模型、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)在節點增減時,幾乎所有資料的映射都會改變,造成快取大面積失效。

核心步驟

  1. 將 hash 空間視為一個 0 ~ 2³² 的環
  2. 節點與資料都對應到環上的某個位置
  3. 資料 key 順時針找到的第一個節點即為其歸屬節點
  4. 節點增減只影響相鄰一小段範圍,其他資料映射不變

虛擬節點:為每個物理節點建立多個虛擬節點打散到環上,解決資料分布不均的問題。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

記住這個公式:高效能 = 合適的並發模型 + 快取 + 負載均衡 + 非同步處理