可擴展架構設計#

可擴展性是指系統為了應對將來需求變化而提供的擴展能力。當新需求出現時,系統不需要或只需少量修改就可以支援。

可擴展性的複雜度#

設計可擴展系統需要同時滿足兩個條件,而這兩個條件本身都很複雜:

維度預測變化應對變化
挑戰 1不能每個點都考慮封裝變化
挑戰 2不能完全不考慮隔離變化
挑戰 3預測可能出錯接口設計

軟件的本質是變化。對於建築來說,永恆是主題;對於軟件來說,變化才是主題。

預測變化:2 年法則#

預測的困境#

  • 過度預測:每個設計點都考慮擴展性,架構師不堪重負,系統過度設計
  • 不預測:剛上線就要重構,前期投入白費
  • 預測錯誤:期望的需求不來,做的擴展沒用

2 年法則#

只預測 2 年內的可能變化,不要試圖預測 5 年甚至 10 年後的變化。

原因:

  • 變化快的行業,能預測 2 年已經足夠
  • 變化慢的行業,預測 2 年和 5 年結果差不多
2 年法則實例

場景:設計一個電商系統

需要考慮的(2 年內可能發生)

  • 支援更多支付方式(微信、支付寶、銀聯)
  • 接入更多物流商
  • 商品分類增加

不需要考慮的(超出 2 年預測)

  • 元宇宙購物
  • 腦機接口下單
  • 支援外星貨幣

應對變化:兩種基本方案#

方案一:分層架構(隔離變化)#

將不變的部分封裝在「穩定層」,將變化封裝在「變化層」。

方式一:穩定層依賴變化層

flowchart TB
    A["業務邏輯<br/>(穩定層)"] -->|呼叫| B["資料訪問層<br/>(MySQL/Oracle)<br/>(變化層)"]

    style A fill:#c8e6c9
    style B fill:#fff3e0

方式二:變化層依賴穩定層

flowchart TB
    A["接口適配層<br/>(XML/JSON/PB)<br/>(變化層)"] -->|呼叫| B["業務邏輯<br/>(穩定層)"]

    style A fill:#fff3e0
    style B fill:#c8e6c9

分層的複雜性

  1. 如何劃分變化層和穩定層?不同人有不同理解
  2. 如何設計層間接口?接口要足夠穩定

方案二:抽象設計(封裝變化)#

提煉出「抽象層」和「實作層」,通過抽象接口封裝變化。

flowchart TB
    A["抽象層(規則)<br/>定義接口、處理流程、擴展點"]
    A --> I1["實作 A"]
    A --> I2["實作 B"]
    A --> I3["實作 C"]

    style A fill:#e3f2fd
    style I1 fill:#c8e6c9
    style I2 fill:#c8e6c9
    style I3 fill:#c8e6c9

典型應用:設計模式、規則引擎

設計模式範例:策略模式
// 抽象層:定義支付接口
type PaymentStrategy interface {
    Pay(amount float64) error
}

// 實作層:不同的支付實作
type WechatPayment struct{}
func (w *WechatPayment) Pay(amount float64) error {
    // 微信支付邏輯
}

type AlipayPayment struct{}
func (a *AlipayPayment) Pay(amount float64) error {
    // 支付寶支付邏輯
}

// 業務程式碼(穩定)
type Order struct {
    payment PaymentStrategy
}

func (o *Order) Checkout(amount float64) error {
    return o.payment.Pay(amount)  // 不關心具體實作
}

新增支付方式時,只需添加新的實作,不需要修改業務程式碼。

1 寫 2 抄 3 重構原則#

Martin Fowler 的「Rule of Three」:事不過三,三則重構

flowchart TD
    A["第 1 次需求<br/>直接實作,不考慮擴展"] --> B["第 2 次類似需求<br/>可以拷貝修改"]
    B --> C["第 3 次類似需求<br/>重構,抽取公共部分"]

    style A fill:#e3f2fd
    style B fill:#fff3e0
    style C fill:#c8e6c9

實例:對接第三方錢包#

階段做法原因
1 寫直接對接微信支付業務能否做起來還不確定
2 抄拷貝程式碼,改為支付寶快速上線更重要
3 重構抽象支付接口,統一封裝已有模式,值得抽象

不要一開始就過度設計。等到真正出現重複時再抽象,這時你對需求的理解也更深刻。

可擴展架構模式#

分層架構#

最常見的可擴展架構,通過分層隔離關注點。

flowchart TB
    subgraph Layers["分層架構"]
        L1["表現層 (Presentation)"]
        L2["業務層 (Business)"]
        L3["持久層 (Persistence)"]
        L4["資料層 (Database)"]
    end

    L1 --> L2 --> L3 --> L4

    style L1 fill:#e3f2fd
    style L2 fill:#fff3e0
    style L3 fill:#e8f5e9
    style L4 fill:#f3e5f5

優點

  • 層次清晰,職責明確
  • 各層可以獨立演進
  • 便於團隊分工

缺點

  • 層數過多時效能下降
  • 可能出現「瀑布式」修改(改一個功能要改所有層)

SOA 架構#

面向服務的架構,將系統拆分為獨立的服務。

flowchart TB
    ESB["ESB (企業服務總線)"]
    ESB --> S1["用戶服務"]
    ESB --> S2["訂單服務"]
    ESB --> S3["支付服務"]
    ESB --> S4["庫存服務"]
    ESB --> S5["物流服務"]

    style ESB fill:#fff3e0

特點

  • 服務獨立部署、獨立演進
  • 通過 ESB 進行服務編排和協議轉換
  • 適合大型企業應用

微服務架構#

SOA 的輕量化演進,去掉中心化的 ESB。

flowchart TB
    subgraph Services[微服務集群]
        U[用戶服務] <--> O[訂單服務]
        O <--> P[支付服務]
        P <--> I[庫存服務]
    end

    U --> SD[服務發現<br/>Consul]
    O --> SD
    P --> SD
    I --> SD

微服務 vs SOA

維度SOA微服務
服務粒度
通信方式ESB(重量級)輕量級協議(HTTP/gRPC)
資料管理共享資料庫每個服務獨立資料庫
治理方式集中式去中心化
微服務拆分原則

1. 基於業務拆分

  • 按業務領域劃分:用戶、商品、訂單、支付
  • 每個服務對應一個限界上下文

2. 基於穩定性拆分

  • 穩定的核心業務獨立
  • 變化頻繁的邊緣業務獨立

3. 基於可靠性拆分

  • 核心服務和非核心服務分離
  • 避免非核心服務故障影響核心服務

4. 基於效能拆分

  • 高頻訪問的功能獨立
  • 便於針對性最佳化和擴容

水平擴展 vs 垂直擴展#

垂直擴展(Scale Up)#

提升單機效能:加 CPU、加內存、換 SSD。

狀態組態
Before4 核 8G
After32 核 128G

優點:簡單,不需要修改架構 缺點:有上限,成本指數增長

水平擴展(Scale Out)#

增加更多機器。

狀態伺服器數量
Before1 台
After4 台(可無限擴展)

優點:理論上無上限 缺點:需要架構支援,引入分布式複雜性

如何選擇#

場景推薦方案原因
初創階段垂直擴展快速、簡單
效能瓶頸明確垂直擴展針對性解決
需要高可用水平擴展冗餘 + 負載均衡
長期發展水平擴展突破單機上限

資料擴展:分庫分表#

當單庫單表無法支撐時,需要進行資料層面的水平擴展。

分庫分表策略#

維度垂直拆分水平拆分
方式按業務拆分資料庫按規則分散資料
範例用戶庫、訂單庫Hash、Range、時間
效果降低單庫壓力突破單表資料量限制

分片策略#

策略方式優點缺點
Hashhash(key) % N分布均勻擴容麻煩
Range按範圍劃分擴容簡單可能不均勻
時間按月/年分表歷史資料歸檔方便熱點集中
映射表查映射關係靈活多一次查詢
分庫分表帶來的問題

問題一:跨庫 Join

-- 這個查詢在分庫後無法直接執行
SELECT * FROM orders o
JOIN users u ON o.user_id = u.id

解決方案:

  • 應用層組裝
  • 資料冗餘
  • 使用搜索引擎

問題二:分布式事務

  • 2PC:效能差,實作複雜
  • TCC:業務侵入性強
  • 最終一致性:通過消息隊列

問題三:全局 ID

  • 資料庫自增 ID 會衝突
  • 解決方案:UUID、Snowflake、資料庫號段

問題四:分頁查詢

-- 分頁變得複雜
SELECT * FROM orders ORDER BY create_time LIMIT 100 OFFSET 10000

解決方案:

  • 禁止深度分頁
  • 使用游標分頁

擴展性設計檢查清單#

在設計架構時,可以用以下問題檢查擴展性:

檢查項問題
容量擴展用戶量增長 10 倍,架構能否支撐?
功能擴展新增一個功能需要改動多少模塊?
技術演進如果要換資料庫/框架,改動範圍多大?
團隊擴展新人加入,是否能快速理解和開發?
運維擴展是否支援灰度發布、快速回滾?

本章小結#

主題核心思想實踐建議
預測變化只看 2 年內不過度設計,也不忽視擴展
應對變化隔離或封裝分層架構 + 抽象設計
重構時機1 寫 2 抄 3 重構等到重複出現再抽象
擴展模式分層/SOA/微服務根據規模選擇
資料擴展分庫分表慎重,引入複雜性

可擴展性設計的核心原則:

  • 預測要適度,不多也不少
  • 封裝變化,暴露穩定接口
  • 簡單優先,複雜度是代價