系統架構#
推薦系統不只是演算法,更是一個完整的工程系統。本章介紹推薦系統的架構設計。
三層架構#
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 延遲),最後才談「加機器」還是「換架構」。沒有數字的「高併發」是空話。
線上層設計#
核心要求#
線上層必須保證:
- 低延遲(端到端 < 100ms)
- 高可用(99.99%)
- 可降級
服務流程#
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 為例,用戶產生新行為時:
- 取出用戶歷史行為物品列表
- 新物品與歷史物品構成「物品對」
- 更新這些物品對的共現次數
- 重算相似度
用戶連點兩個影片,系統能在秒級反映進相似度矩陣。
流批一體: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-V | Redis |
| 推薦結果 | 排序列表 | 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 / Joblib | Python 生態內部使用 |
| SavedModel | TensorFlow 模型 |
| ONNX | 跨框架通用格式 |
| TorchScript | PyTorch 模型 |
實踐建議#
從簡單開始#
不要一開始就追求完美架構:
- 中小團隊:先單機,充分發揮單機效能
- 先跑通流程,再優化效能
- 依實際瓶頸逐步升級
架構演進路線#
timeline
title 推薦系統架構演進
階段 1 單機版本
: 協同過濾 + 熱門
: MySQL / Redis
: 定時批次更新
階段 2 線上 / 離線分離
: 離線:Spark 計算
: 線上:微服務架構
階段 3 加入近線層
: Kafka + Flink
: 即時特徵更新
階段 4 完整架構
: 多路召回
: 深度學習排序
: Feature Store
: 完善監控體系總結#
| 要點 | 說明 |
|---|---|
| 三層架構 | 線上 / 近線 / 離線,分工的本質是時效 vs 成本 |
| 非功能需求 | 可靠(容錯)、可擴展(量化負載)、可維護 |
| 流批架構 | Lambda 雙路求準,Kappa 流優先求簡 |
| 線上層 | 低延遲、高可用、可降級、有延遲預算 |
| 離線層 | 批次計算、模型訓練、Feature Store |
| 近線層 | Flink 流計算、即時特徵 / 模型更新 |
| 儲存選型 | 依資料特性選工具 |
| 演進路線 | 從簡單開始,按需升級 |
下一章:工程實踐