系統架構#

推薦系統不只是演算法,更是一個完整的工程系統。本章介紹推薦系統的架構設計。

三層架構#

Netflix 三層架構#

Netflix 的推薦系統架構是業界標竿,分為三層:

flowchart TB
    subgraph 線上層["🟢 線上層 Online"]
        direction LR
        O1[即時回應請求]
        O2[使用即時資料]
        O3[毫秒級延遲]
    end

    subgraph 近線層["🟡 近線層 Nearline"]
        direction LR
        N1[使用即時資料]
        N2[非同步處理]
        N3[特徵更新]
    end

    subgraph 離線層["🔵 離線層 Offline"]
        direction LR
        F1[批次處理]
        F2[全量資料]
        F3[模型訓練]
    end

    離線層 --> 近線層 --> 線上層

    style 線上層 fill:#e8f5e9
    style 近線層 fill:#fff3e0
    style 離線層 fill:#e3f2fd

各層特點#

階層資料服務典型任務
線上即時即時召回、排序、過濾、業務規則
近線即時非同步特徵更新、模型增量更新
離線全量批次模型訓練、全量計算

三層架構的本質:把「成本高但結果穩定」的計算放離線,「需要新鮮但能等」的放近線,「必須毫秒級回應」的放線上。整個架構就是一場 時效性 vs 成本 的取捨。

非功能性需求:可靠、可擴展、可維護#

評估一個推薦架構是否「企業級」,看的不是用了哪些花俏技術,而是這三個支柱:

支柱關鍵問題推薦系統的具體做法
可靠性 Reliability出錯時還能正常運作嗎?容錯優於預防、降級策略、故障演練(Chaos)
可擴展性 Scalability負載成長時能撐住嗎?量化負載參數與 p50/p99 延遲,設計水平擴展
可維護性 Maintainability新人能安心改、能無痛升級嗎?監控 / 日誌 / 文件、滾動升級而非停機升級

三者要分清楚 Fault(故障)Failure(失效):單一元件故障(某台特徵服務掛了)不該演變成整體失效(推薦不出來)。所以推薦系統寧可容錯——特徵服務掛了就退回預設特徵,也不要讓使用者看到空白頁。這正是下面「降級策略」的設計哲學。

可擴展性要量化,不能憑感覺:先定義負載參數(日活用戶數、每秒請求數、特徵維度、候選集大小),再定義效能指標(吞吐量、p99 延遲),最後才談「加機器」還是「換架構」。沒有數字的「高併發」是空話。

線上層設計#

核心要求#

線上層必須保證:

  1. 低延遲(端到端 < 100ms)
  2. 高可用(99.99%)
  3. 可降級

服務流程#

flowchart LR
    A[用戶請求] --> B[召回服務]
    B --> C[排序服務]
    C --> D[重排服務]
    D --> E[回傳結果]

    B -.-> B1[多路召回]
    C -.-> C1[CTR 預估]
    D -.-> D1[業務規則]

    style A fill:#e3f2fd
    style E fill:#e8f5e9

延遲預算範例#

100ms 端到端延遲怎麼分配:

階段延遲預算工作
特徵讀取5–10ms從 Redis / Feature Store 取用戶 / item 特徵
召回20–30ms多路召回平行執行
粗排10–20ms輕量模型過濾候選
精排20–40ms深度模型打分
重排5–10ms多樣性、業務規則
網路 I/O預留 ~10ms各服務間 RPC

任何一個階段超預算,整體就拉警報。實務上會對每個服務各自設 SLA,超時就降級。

降級策略#

當上游服務異常時要有 Plan B:

異常情況降級策略
個人化召回失敗回傳熱門排行榜
排序模型超時用簡單規則排序
特徵服務異常用預設特徵值

API 設計#

推薦 API 範例
GET /recommend
參數:
  - user_id: 用戶 ID(必需)
  - page_id: 頁面 ID
  - position_id: 推薦位 ID
  - size: 請求數量
  - offset: 偏移量(翻頁)

回傳:
{
  "recommend_id": "xxx",   // 曝光 ID,用於日誌追蹤
  "items": [
    {
      "id": "item_123",
      "title": "...",
      "score": 0.95
    }
  ],
  "size": 10
}

recommend_id 是日後追蹤該次曝光對應點擊 / 轉換的關鍵,不能省。

離線層設計#

典型任務#

資料清洗 → 特徵工程 → 模型訓練 → 離線評估 → 推送上線

資料管理#

元件作用工具
資料湖儲存原始日誌HDFS、S3
資料倉庫結構化儲存Hive、Spark SQL
特徵平台特徵計算與儲存Feast、自研

Feature Store(特徵平台)是近年業界趨勢。它解決的是「離線訓練特徵」與「線上推理特徵」的一致性問題——兩者用同一份計算邏輯與儲存,避免「線下漲線上跌」的訓練 / 服務偏移(training-serving skew)。

模型訓練#

def train_model():
    # 1. 讀取訓練資料
    df = spark.read.parquet("hdfs://data/training")

    # 2. 特徵工程
    features = feature_engineering(df)

    # 3. 模型訓練
    model = train(features)

    # 4. 離線評估
    metrics = evaluate(model, test_data)

    # 5. 模型推送
    if metrics['auc'] > threshold:
        push_to_online(model)

近線層設計#

流計算架構#

用戶行為 → Kafka → Flink → 特徵更新 → Redis / Cassandra
                         → 模型增量更新
                         → 推薦結果更新

早年常用 Storm,現代主流是 Flink(更好的狀態管理、Exactly-Once 語意、SQL 介面)。Spark Streaming 在批微批場景也常見。

即時更新範例#

以 Item-CF 為例,用戶產生新行為時:

  1. 取出用戶歷史行為物品列表
  2. 新物品與歷史物品構成「物品對」
  3. 更新這些物品對的共現次數
  4. 重算相似度

用戶連點兩個影片,系統能在秒級反映進相似度矩陣。

流批一體:Lambda vs Kappa#

離線批次與近線流計算如何統一?業界有兩種典型取捨:

flowchart LR
    subgraph Lambda["Lambda 架構(雙路)"]
        direction TB
        B["離線批次<br/>Spark 日度全量"] --> M1["合併視圖"]
        S["近線流式<br/>Flink 即時增量"] --> M1
    end
    subgraph Kappa["Kappa 架構(流優先)"]
        direction TB
        F["統一流處理<br/>Flink / Kafka Streams"] --> M2["服務視圖"]
        L["日誌重播<br/>需重算時回放"] --> F
    end
架構思路優點代價
Lambda批次 + 流式雙路,最後合併準確 + 即時兩套邏輯,維護成本高
Kappa只有流式,重算就回放日誌邏輯單一重算大歷史時延遲高

選型規則:特徵變化快、值域大(如即時點擊率)傾向 Lambda;特徵相對穩定、計算邏輯能用日誌重建(如長期興趣)可用 Kappa 省下一套程式碼。

資訊流架構#

Feed 系統特點#

特點說明
海量內容每天新增百萬級內容
即時性要求高新聞、短影片有強時效性
多路召回興趣標籤、CF、熱門、向量召回
多目標融合排序點擊、時長、互動、多樣性同時優化

典型架構#

flowchart LR
    subgraph 內容處理
        A1[內容入庫] --> A2[內容分析] --> A3[建立索引]
    end

    subgraph 多路召回
        B1[標籤召回]
        B2[CF 召回]
        B3[熱門召回]
    end

    subgraph 排序展示
        C1[融合] --> C2[排序] --> C3[重排] --> C4[展示]
    end

    A3 --> B1
    A3 --> B2
    A3 --> B3
    U[用戶請求] --> B1
    U --> B2
    U --> B3
    B1 --> C1
    B2 --> C1
    B3 --> C1

    style U fill:#e3f2fd
    style C4 fill:#e8f5e9

搜尋、推薦、廣告的架構統一#

三者的底層流程都可以抽象為:過濾候選 ➡️ 排序候選 ➡️ 個人化輸出

                 ┌─────────────────────────────────────┐
                 │            統一架構                  │
                 │                                     │
Query / User ───→│  召回層 → 排序層 → 重排層     │──→ 結果
                 │                                     │
                 └─────────────────────────────────────┘

目標、約束、特徵不同,但工程基礎設施(特徵平台、模型訓練、線上推理)高度共用。許多大公司已經在嘗試統一搜尋與推薦的架構,共享底層技術棧。

儲存選型#

不同資料的儲存方案#

資料類型特點推薦儲存
用戶 / 物品特徵點查為主Redis、Cassandra、HBase
模型參數K-VRedis
推薦結果排序列表Redis Sorted Set
Embedding 向量稠密向量檢索Faiss / HNSW(記憶體)+ MMAP 檔案
倒排索引高效檢索ElasticSearch

OLTP 與 OLAP 的分工#

工具表只列了「用什麼」,背後的選型哲學是儲存引擎的兩種定位

定位工作負載引擎特性推薦系統用途
OLTP點查、線上服務LSM-Tree,高寫入吞吐、低延遲特徵存儲、模型參數(毫秒級)
OLAP批次聚合、分析列式儲存,掃描少 I/O訓練資料倉儲、報表(秒級)

同一份資料用錯引擎,效能可能差 1~2 個數量級。特徵服務要的是「給一個 user_id 毫秒內回特徵」(OLTP);離線訓練要的是「掃全表算統計量」(OLAP)。不要拿分析型倉儲扛線上點查。

列式資料庫選擇#

Redis vs Cassandra vs HBase:

  • Redis:強一致(單 leader)、亞毫秒延遲、吃記憶體——適合排序結果、即時計數。
  • Cassandra:去中心化、可調一致性(w + r > n 的 quorum)、讀寫效能好——適合海量用戶特徵點查。
  • HBase:強一致、與 Hadoop 生態整合佳——適合事件日誌與審計。

特徵平台的分散式設計#

Feature Store 本身就是一個分散式系統,要扛 1M+ QPS 必須分片與複製:

  • 分片(Sharding):按 user_id / item_id 雜湊水平切分,攤平讀寫熱點。
  • 複製(Replication):多副本提升可用性,用 quorum(w + r > n)在一致性與延遲間取捨——推薦場景多半容忍分鐘級最終一致。
  • 讀修復 / 反熵:背景程序持續對齊副本,避免長期分歧。

模型儲存格式#

格式適用場景
PMML小規模模型、跨語言部署
Pickle / JoblibPython 生態內部使用
SavedModelTensorFlow 模型
ONNX跨框架通用格式
TorchScriptPyTorch 模型

實踐建議#

從簡單開始#

不要一開始就追求完美架構:

  • 中小團隊:先單機,充分發揮單機效能
  • 先跑通流程,再優化效能
  • 依實際瓶頸逐步升級

架構演進路線#

timeline
    title 推薦系統架構演進

    階段 1 單機版本
        : 協同過濾 + 熱門
        : MySQL / Redis
        : 定時批次更新

    階段 2 線上 / 離線分離
        : 離線:Spark 計算
        : 線上:微服務架構

    階段 3 加入近線層
        : Kafka + Flink
        : 即時特徵更新

    階段 4 完整架構
        : 多路召回
        : 深度學習排序
        : Feature Store
        : 完善監控體系

總結#

要點說明
三層架構線上 / 近線 / 離線,分工的本質是時效 vs 成本
非功能需求可靠(容錯)、可擴展(量化負載)、可維護
流批架構Lambda 雙路求準,Kappa 流優先求簡
線上層低延遲、高可用、可降級、有延遲預算
離線層批次計算、模型訓練、Feature Store
近線層Flink 流計算、即時特徵 / 模型更新
儲存選型依資料特性選工具
演進路線從簡單開始,按需升級

下一章:工程實踐