高效能架構設計#

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

高效能的複雜度來源#

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

複雜度維度單機複雜度集群複雜度
核心問題進程/線程模型、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」,有三種典型實作:

1. 單 Reactor 單進程/線程

              ┌─────────────────────────────────┐
   連接請求   │           Reactor               │
  ─────────→ │  ┌─────────┐    ┌───────────┐   │
              │  │Acceptor │    │  Handler  │   │
   I/O 請求   │  └────┬────┘    └─────┬─────┘   │
  ─────────→ │       │                │         │
              │       └────────┬──────┘         │
              │                ↓                │
              │         ┌──────────┐            │
              │         │  處理邏輯 │            │
              │         └──────────┘            │
              └─────────────────────────────────┘
  • 代表:Redis
  • 優點:簡單,無需考慮並發問題
  • 缺點:無法利用多核 CPU

2. 單 Reactor 多線程

              ┌──────────────────────────────────────────┐
              │              Reactor                      │
   請求       │  ┌─────────┐    ┌─────────────────────┐  │
  ─────────→ │  │Acceptor │    │    Handler Pool      │  │
              │  └────┬────┘    │  ┌───┐ ┌───┐ ┌───┐  │  │
              │       │         │  │ H │ │ H │ │ H │  │  │
              │       │         │  └───┘ └───┘ └───┘  │  │
              │       └────────→│                     │  │
              │                 └─────────────────────┘  │
              └──────────────────────────────────────────┘
  • 優點:可以利用多核 CPU
  • 缺點:Reactor 可能成為瓶頸

3. 多 Reactor 多進程/線程

              ┌──────────────────────────────────────────┐
              │            Main Reactor                   │
   連接請求   │  ┌─────────┐                             │
  ─────────→ │  │Acceptor │                             │
              │  └────┬────┘                             │
              │       │ 分發連接                          │
              │       ↓                                   │
              │  ┌──────────────────────────────────┐    │
              │  │         Sub Reactors              │    │
              │  │  ┌──────┐  ┌──────┐  ┌──────┐   │    │
  I/O 請求    │  │  │  R1  │  │  R2  │  │  R3  │   │    │
  ─────────→ │  │  │ + H  │  │ + H  │  │ + H  │   │    │
              │  │  └──────┘  └──────┘  └──────┘   │    │
              │  └──────────────────────────────────┘    │
              └──────────────────────────────────────────┘
  • 代表:Nginx, Memcached
  • 優點:充分利用多核,Reactor 不會成為瓶頸
  • 缺點:實作複雜

快取架構#

快取是高效能架構中最常用的技術,基本原理是將可能重複使用的資料放到記憶體中,一次生成、多次使用。

快取帶來的問題#

雖然快取能大幅提升效能,但也引入了架構複雜性:

問題描述解決方案
快取穿透查詢不存在的資料,每次都打到 DB空值快取、布隆過濾器
快取雪崩大量快取同時過期更新鎖、後台更新、隨機過期時間
快取熱點熱門資料集中在少數快取節點多副本、本地快取

快取穿透詳解#

場景一:資料確實不存在

用戶 ────→ 快取(無) ────→ DB(無) ────→ 返回空
  │                                      │
  └──────────── 每次都穿透 ←─────────────┘

解決方案:

// 查詢資料
data := cache.Get(key)
if data == nil {
    data = db.Query(key)
    if data == nil {
        // 關鍵:緩存空值,設置較短過期時間
        cache.Set(key, EMPTY_VALUE, 60)
    } else {
        cache.Set(key, data, 3600)
    }
}

場景二:快取生成耗時長

例如電商分頁,爬蟲遍歷所有頁面時,大量冷門分頁的快取已過期,需要重新從 DB 生成。

這種情況沒有完美解決方案。常見做法是識別爬蟲並限流,或做好監控及時處理。

快取雪崩詳解#

                 快取大量過期
                      │
                      ▼
    ┌─────────────────────────────────┐
    │        大量請求湧入              │
    │  ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │
    │  │ R │ │ R │ │ R │ │ R │ │ R │ │
    │  └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ │
    │    │     │     │     │     │   │
    │    └──┬──┴──┬──┴──┬──┴──┬──┘   │
    │       │     │     │     │       │
    │       ▼     ▼     ▼     ▼       │
    │  ┌─────────────────────────┐    │
    │  │      全部打到 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³²
                ●
               /|\
              / | \
             /  |  \
            /   |   \
           ●────●────●
          /     |     \
         /      |      \
        ●       ●       ●
       Node1   Node2   Node3

資料 key 順時針找到第一個節點

虛擬節點:為每個物理節點創建多個虛擬節點,解決資料分布不均的問題。

type ConsistentHash struct {
    ring     map[uint32]string  // hash -> 節點
    sortedKeys []uint32          // 排序的 hash 值
    virtualReplicas int          // 虛擬節點數
}

func (ch *ConsistentHash) Get(key string) string {
    hash := ch.hash(key)
    idx := ch.search(hash)  // 二分查找
    return ch.ring[ch.sortedKeys[idx]]
}

非同步處理架構#

非同步處理是提升系統吞吐量的重要手段,核心思想是將非關鍵路徑的操作延後處理。

非同步架構模式#

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

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