彈性設計的核心目標是讓系統在故障發生時仍能維持運作,並具備自我恢復的能力。

彈性 (Resiliency) 的含義:系統在不健康、不順甚至出錯的情況下,仍有能力 hold 住、挺住,甚至力挽狂瀾。

隔離設計 (Bulkheads)#

靈感來自船艙的水密隔板設計:當船艙某處進水時,隔板能防止水蔓延到其他艙室。

按服務種類隔離#

flowchart TD
    LB[負載均衡器] --> U[用戶服務]
    LB --> P[商品服務]
    LB --> O[訂單服務]

    U --> UDB[(用戶 DB)]
    P --> PDB[(商品 DB)]
    O --> ODB[(訂單 DB)]

    style U fill:#e3f2fd
    style P fill:#fff9c4
    style O fill:#f3e5f5

優點:一個版塊的故障不會影響其他版塊

挑戰

  • 跨版塊資料獲取需要多次呼叫
  • 跨版塊事務處理變得複雜
  • 需要 Pub/Sub 機制打通資料交換

按用戶隔離(多租戶)#

flowchart LR
    subgraph VIP[VIP 用戶]
        V1[專用實體]
        V1 --> VDB[(專用 DB)]
    end

    subgraph Normal[普通用戶]
        A[用戶 A] --> S[共享實體]
        B[用戶 B] --> S
        S --> SDB[(共享 DB)]
    end

    style VIP fill:#fff9c4
    style Normal fill:#e8f5e9

三種多租戶模式

模式隔離度成本複雜度
完全獨立
共享服務,獨立資料
完全共享

熔斷設計 (Circuit Breaker)#

借鑒電路保險絲的概念:當電流過大時自動斷開,保護電路不被燒毀。

三種狀態#

stateDiagram-v2
    [*] --> Closed
    Closed --> Open: 失敗次數超過閾值
    Open --> HalfOpen: 逾時時間到達
    HalfOpen --> Closed: 探測請求成功
    HalfOpen --> Open: 探測請求失敗

    Closed: 正常狀態<br/>請求正常通過
    Open: 熔斷狀態<br/>請求直接失敗
    HalfOpen: 半開狀態<br/>允許部分請求探測

狀態說明#

狀態說明
Closed(關閉)正常狀態,請求正常通過,統計失敗次數
Open(打開)熔斷狀態,請求直接回傳錯誤或快取資料
Half-Open(半開)允許部分請求通過,探測服務是否恢復

熔斷設計要點#

  • 區分錯誤類型:有些錯誤需要重試後再熔斷,有些應直接熔斷
  • 記錄日誌便於監控和診斷
  • 提供手動重置功能
  • 考慮資源分區:不同分區應有獨立的熔斷器
熔斷器實現邏輯(參考 Netflix Hystrix)
// 1. 判斷是否允許請求
if (!allowRequest()) {
    return fallback();
}

// 2. 執行實際呼叫
try {
    result = doActualCall();
    markSuccess(duration);
    return result;
} catch (Exception e) {
    markFailure(duration);
    return fallback();
}

// 3. 判斷是否需要熔斷
boolean isOpen() {
    // 計算錯誤率: failure / (success + failure)
    if (errorRate > threshold) {
        return true;
    }
    return false;
}

限流設計 (Throttle)#

保護系統不會因過載而崩潰。

限流策略#

策略說明
拒絕服務超過閾值直接拒絕,優先拒絕例外高並行的用戶端
服務降級關閉非核心功能,或回傳簡化版資料
特權請求VIP 用戶優先處理
延時處理使用佇列緩衝請求
彈性伸縮自動擴容後端服務

限流演演算法#

計數器#

最簡單的方式,維護一個 Counter:

if (counter > threshold) {
    reject();
}

漏斗演演算法 (Leaky Bucket)#

請求進入漏斗,以固定速率處理:

flowchart TD
    A[請求進入] --> B{佇列是否已滿?}
    B -->|否| C[加入佇列]
    B -->|是| D[拒絕請求]
    C --> E[固定速率處理]

    style D fill:#ffcdd2
    style E fill:#c8e6c9

權杖桶演演算法 (Token Bucket)#

按固定速率產生權杖,請求需要獲取權杖才能執行:

flowchart TD
    subgraph Bucket[權杖桶]
        T[○ ○ ○ ○ ○ ○ ○]
    end

    R[固定速率補充] --> Bucket
    A[請求到達] --> B{有可用權杖?}
    B -->|是| C[獲取權杖]
    C --> D[處理請求]
    B -->|否| E[等待或拒絕]

    style Bucket fill:#fff9c4
    style D fill:#c8e6c9
    style E fill:#ffcdd2

權杖桶 vs 漏斗

  • 漏斗:以恆定速率處理
  • 權杖桶:允許突發流量(消耗累積的權杖)

動態限流#

根據後端服務的回應時間動態調整:

1. 計算過去 N 秒請求的 P99 回應時間
2. 如果 P99 超過閾值,降低 QPS(如減半)
3. 慢啟動恢復,類似 TCP 擁塞控制
4. 在最佳值附近小幅震盪

動態限流需要使用蓄水池抽樣 (Reservoir Sampling) 等近似演演算法來高效計算 P99。

降級設計 (Degradation)#

當資源不足或訪問量過大時,暫時犧牲部分功能保障核心功能。

降級策略#

1. 降低一致性#

強一致性  →  最終一致性

同步處理  →  非同步處理

例:電商下單
  - 正常:同步扣庫存 + 同步支付
  - 降級:非同步處理 + 貨到付款

2. 停止次要功能#

優先級:核心功能 > 次要功能

例:電商系統
  - 必須保留:下單、支付、物流
  - 可停止:評論、推薦、搜尋

3. 簡化功能#

完整版  →  精簡版

例:商品詳情頁
  - 正常:商品資訊 + 評論 + 推薦
  - 降級:只回傳商品資訊

降級設計要點#

  1. 預先設計:降級邏輯要事先設計好,不能臨時寫
  2. 開關控制:通過組態開關快速切換
  3. 友好提示:告知用戶系統繁忙,請稍後再試
  4. 定期演練:降級功能平時不用,可能會有 bug

重試設計 (Retry)#

重試的語義是「認為這是暫時性故障」。如果是永久性錯誤,重試沒有意義。

適合重試的場景#

  • 呼叫逾時
  • 被呼叫端回傳可重試的錯誤(繁忙中、流控中、維護中)

不適合重試的場景#

  • 業務級錯誤(無權限、非法資料)
  • 技術性錯誤(HTTP 503 等)

重試策略#

指數退避 (Exponential Backoff)#

long getWaitTime(int retryCount) {
    return (long) Math.pow(2, retryCount) * 100; // ms
}

// 第 1 次重試: 200ms
// 第 2 次重試: 400ms
// 第 3 次重試: 800ms
// ...

重試策略類型#

策略說明
NeverRetry不重試
AlwaysRetry無限重試(危險!可能死循環)
SimpleRetry固定次數重試
TimeoutRetry逾時時間內重試
CircuitBreakerRetry帶熔斷的重試

重試設計要點#

  1. 設置最大重試次數:避免無限重試
  2. 使用退避策略:避免加重負載
  3. 確保冪等性:重複執行結果相同
  4. 結合熔斷:超過重試次數後觸發熔斷

冪等性設計 (Idempotency)#

f(x) = f(f(x))

一次和多次請求產生的副作用相同。

為什麼需要冪等性#

分散式呼叫有三種結果:

  • 成功 (Success)
  • 失敗 (Failed)
  • 逾時 (Timeout) ← 這個狀態不確定!

逾時時,完全不知道請求是否被處理過,重試可能導致重複處理。

實現方式#

全局唯一 ID#

使用 Snowflake 等演演算法生成全局唯一的交易 ID。

處理流程#

-- 方式一:插入檢查衝突
INSERT INTO transactions (id, ...) VALUES (...)
ON DUPLICATE KEY UPDATE ...

-- 方式二:更新時檢查狀態
UPDATE orders
SET status = 'paid'
WHERE id = ? AND status = 'unpaid'

HTTP 方法的冪等性#

方法冪等性說明
GET獲取資源,無副作用
HEAD同 GET,只回傳標頭
OPTIONS獲取支援的方法
PUT創建或更新資源
DELETE刪除資源
POST創建資源,多次呼叫會創建多個

彈力設計總結#

mindmap
  root((彈力設計))
    冗餘服務
      負載均衡 + 健康檢查
      服務發現 + 動態路由
      Kubernetes 調度
    服務解耦
      Bulkheads 隔離
      非同步通訊
      補償事務
    容錯設計
      重試 + 熔斷 + 冪等
      一致性處理
      限流 + 降級

彈力設計需要自動化運維工具的配合。沒有監控和自動化,再好的設計也難以發揮作用。建議使用:

  • APM 服務監控
  • Docker + Kubernetes 進行服務調度
  • Spring Cloud 開發框架