工程實踐#
推薦系統的落地需要解決許多工程問題。本章涵蓋資料採集、即時推薦、實驗平台、服務化等實踐經驗。
資料採集#
採集要素#
| 要素 | 說明 | 範例 |
|---|---|---|
| 用戶標識 | 唯一識別用戶 | user_id、device_id、cookie |
| 行為類型 | 用戶做了什麼 | 曝光、點擊、購買、收藏 |
| 行為對象 | 作用於什麼物品 | item_id |
| 時間戳記 | 什麼時候發生 | timestamp |
| 上下文 | 發生的環境 | 頁面、位置、設備 |
客戶端 vs 服務端埋點#
| 類型 | 優點 | 缺點 |
|---|---|---|
| 客戶端埋點 | 能記錄前端事件(滑動、停留、曝光) | 上報易丟失、版本碎片化 |
| 服務端埋點 | 穩定可靠、不需發版 | 看不到前端純客戶端行為 |
實務上兩者並用:曝光 / 滑動 / 停留 用客戶端,下單 / 支付 用服務端。
埋點規範#
{
"event_type": "click",
"user_id": "u123456",
"item_id": "i789",
"timestamp": 1706313600000,
"page_id": "home",
"position": 3,
"recommend_id": "rec_xxx",
"device": {
"os": "iOS",
"version": "17.0"
}
}recommend_id 非常重要,用於關聯曝光與後續行為,是追蹤推薦效果與訓練樣本的串接鍵。沒有 recommend_id 等於沒有訓練資料。
資料品質保障#
| 問題 | 影響 | 解決方案 |
|---|---|---|
| 資料丟失 | 訓練資料不完整 | 多端上報、本地快取重傳 |
| 資料延遲 | 特徵更新不及時 | 流批一體架構 |
| 資料錯誤 | 模型效果下降 | 資料校驗、異常監控 |
| 重複資料 | 統計偏差 | 去重機制 |
| Position Bias | 高位天然點擊率高,污染訓練 | 位置特徵建模、IPS 校正、用戶側截斷 |
Position Bias 是新手最常踩的坑:用戶點擊第一個位置不代表喜歡,可能只是因為它在最上面。如果不處理,模型會學到「越靠前越好」這種無意義的規律。常見處理方式是把 position 當特徵餵給模型,預測時固定 position=0。
資料流:批次、流式與 CDC#
採集到的資料要怎麼流進特徵與模型?這決定了系統的新鮮度與成本。
批次 vs 流式#
| 維度 | 批次處理(Batch) | 流式處理(Stream) |
|---|---|---|
| 延遲 | 高(小時 / 天級) | 低(秒級) |
| 成本 | 低(離峰算、可重跑) | 高(常駐、狀態管理) |
| 資料範圍 | 全量歷史 | 最近事件 |
| 典型用途 | 模型訓練、全量特徵 | 即時特徵、即時計數 |
選型看延遲需求:能等就用批次(便宜又能重跑),要秒級反應才上流式。這也呼應 第 8 章 的 Lambda / Kappa——兩者其實是同一個取捨在不同層的展現。
變更資料捕捉(CDC)#
要把生產資料庫的變更同步進特徵管線,最穩健的做法是 CDC(Change Data Capture):
- 日誌式 CDC:直接解析資料庫的 WAL / binlog,捕捉每一筆 insert/update/delete,對來源庫零侵入。
- 通常先做一次初始快照,再持續追日誌;下游用 日誌壓縮(log compaction) 保留每個鍵的最新狀態。
事件日誌作為單一真相來源#
CDC 捕捉的是「資料庫狀態的變化」,而事件溯源(Event Sourcing) 記錄的是「用戶意圖的不可變事實」(曝光、點擊本身)。在推薦系統,這串不可變事件日誌就是訓練資料與特徵的源頭:
用戶行為事件 → Kafka(不可變日誌) → ┬→ 流式:即時特徵
├→ 批次:訓練樣本
└→ 回放:重建任意時點的特徵事件日誌「只追加、可重播」帶來兩個關鍵能力:(1) 出問題能重播重建歷史狀態;(2) 同一份事實能同時餵給批次與流式,天然保證兩條路徑的一致性——這正是解決下文「訓練 / 服務偏移」的基礎。
去重策略#
內容去重:SimHash#
處理相似內容,避免推薦重複新聞 / 影片:
1. 對文本分詞,計算每個詞的權重
2. 每個詞雜湊成二進位向量,0 變成 -1
3. 加權求和,正數變 1、負數變 0
4. 得到 SimHash 指紋特點:
- 相似內容的 SimHash 接近
- 用漢明距離判斷相似度
- 計算高效
用戶去重:Bloom Filter#
防止重複推薦用戶已看過的內容:
1. 準備長度為 m 的二進位向量
2. 使用 k 個獨立雜湊函數
3. 每個已推薦物品雜湊到 k 個位置,設為 1
4. 查詢時,k 個位置都為 1 則認為已推薦過Bloom Filter 有偽陽性(可能把未推薦的判為已推薦),但可控。實務上用 Redis Bloom Filter 即可。
即時推薦#
三個層次#
| 層次 | 說明 | 難度 |
|---|---|---|
| 給得及時 | 服務回應快 | 基本要求 |
| 用得及時 | 特徵即時更新 | 中等 |
| 改得及時 | 模型即時更新 | 困難 |
即時架構#
用戶行為 → Kafka → 流計算 → 特徵更新 → 線上服務
↓
模型增量更新
↓
推薦結果更新增量更新 Item-CF#
當用戶產生新行為時:
增量更新邏輯
輸入:用戶 u 對物品 i 產生行為
步驟:
1. 取出用戶歷史行為物品列表 [j1, j2, ...]
2. 對每個 j:
- 增加 (i, j) 的共現次數
- 增加 i 的評分用戶數
3. 重算所有相關物品對的相似度
4. 更新推薦結果效率優化#
| 方法 | 說明 |
|---|---|
| 剪枝 | 相似度收斂後不再更新(Hoeffding 不等式) |
| 滑窗 | 只保留最近 K 條行為 |
| 採樣 | 對高頻行為降採樣 |
| 合併 | 批次處理多條行為再更新 |
| 快取 | 熱門資料放入快取 |
實驗平台#
為什麼需要實驗平台#
資料驅動的核心是做對比實驗。沒有實驗平台,所有「我覺得這樣比較好」都只是猜測。
A/B 測試三要素#
| 要素 | 說明 |
|---|---|
| 流量 | 用戶造訪,實驗樣本來源 |
| 參數 | 不同策略或模型的配置 |
| 結果 | 日誌記錄,分析實驗效果 |
Google 分層實驗架構#
全部流量
↓
┌───────┴───────┐
域 1 域 2
↓ ↓
層 1 → 桶 1-5 獨立實驗
↓
層 2 → 桶 1-5
↓
層 3 → 桶 1-5核心概念:
| 概念 | 說明 |
|---|---|
| 域(Domain) | 流量的劃分 |
| 層(Layer) | 參數子集的測試 |
| 桶(Bucket) | 實驗組與對照組 |
為什麼要分層? 同時跑多個獨立實驗時,分層能讓不同層的流量正交(同一用戶可能在 A 層落入實驗組、B 層落入對照組),大幅提升並行實驗能力。
流量分配#
def get_bucket(user_id, layer_id, n_buckets=1000):
"""
計算用戶落入哪個桶
- 同一用戶在同一層始終落入同一桶(體驗一致)
- 不同層的分配相互獨立(無偏置)
"""
hash_key = f"{user_id}_{layer_id}"
hash_value = md5(hash_key.encode()).hexdigest()
return int(hash_value, 16) % n_buckets實驗規模估算#
Google 公式:
N >= 10.5 × (s/θ)²s:指標標準差θ:希望檢測的敏感度(如 2% 的 CTR 變化)
這個規模保證 90% 的統計檢力(power)。
不要看到 CTR 漲了 0.1% 就上線。先看:(1) 樣本量是否足夠 (2) 統計顯著性 (3) 多目標一起看(CTR 漲但時長跌就不一定是進步)。
服務化#
線上服務要求#
| 要求 | 指標 |
|---|---|
| 延遲 | P99 < 100ms |
| 可用性 | 99.99% |
| QPS | 視業務量級 |
微服務架構#
API Gateway
↓
┌───┴───┐
召回服務 排序服務
↓ ↓
Redis 模型服務模型部署#
| 方式 | 適用場景 |
|---|---|
| 嵌入式 | 小模型,直接載入到服務行程 |
| RPC 服務 | 大模型,獨立部署 |
| TensorFlow Serving | TF 模型標準化部署 |
| TorchServe | PyTorch 模型部署 |
| Triton | NVIDIA GPU 推理服務、多框架支援 |
線上監控#
線上推薦系統至少要監控四類指標:
- 服務指標:QPS、延遲(P50/P99)、錯誤率
- 業務指標:CTR、轉換率、人均時長
- 資料指標:曝光數、特徵覆蓋率、空召回率
- 模型指標:分數分佈、預測 / 實際 CTR 偏差(calibration)
任何一類異常都可能在用戶感知前先報警。
訓練 / 服務偏移#
「線下評估漲,線上指標跌」最常見的元兇是 訓練 / 服務偏移(training-serving skew):訓練時與服務時的特徵不一致。
| 偏移來源 | 例子 |
|---|---|
| 計算邏輯不同 | 訓練用 Python 算特徵,線上用 Java 重寫一份 |
| 時間點不同 | 訓練不小心用到「未來」資訊(特徵穿越) |
| 分佈漂移 | 新用戶、季節變化讓線上分佈偏離訓練分佈 |
緩解的核心是 Feature Store:離線訓練與線上推理讀同一份特徵定義與計算邏輯,並對特徵做版本管理;線上再加 schema 與數值範圍校驗,異常即時攔截。偵測上,持續比對「訓練 vs 服務」的特徵分佈、監控 calibration 漂移。
可重現性#
要能重現一次訓練結果,必須把這些一起釘版本:訓練程式碼、資料切分、特徵定義、超參數、依賴環境。配合不可變事件日誌,就能「從某個時間點回放重建特徵」,讓實驗可追溯、可複現——這也是 A/B 實驗能拿來做嚴謹決策的前提。
開源工具#
演算法層#
| 任務 | 工具 |
|---|---|
| 文本處理 | FastText、Gensim、jieba |
| 協同過濾 | Surprise、Implicit |
| 矩陣分解 | Spark ALS、Implicit |
| 深度學習 | TensorFlow、PyTorch |
| 向量檢索 | Faiss、HNSW、ScaNN、Milvus |
平台層#
| 任務 | 工具 |
|---|---|
| 訊息佇列 | Kafka、Pulsar |
| 流計算 | Flink(主流)、Spark Streaming |
| 批次處理 | Spark |
| 儲存 | Redis、Cassandra、HBase |
| 搜尋引擎 | ElasticSearch、OpenSearch |
| Feature Store | Feast、Tecton |
完整推薦框架(學習用)#
| 專案 | 語言 | 特點 |
|---|---|---|
| Surprise | Python | 簡單易用,適合入門 |
| RecBole | Python | 深度學習推薦框架 |
| LensKit | Java | 學術界常用 |
工業界很少用「大而全」的框架,建議按模組選工具自己組合,更容易診斷問題與優化。
團隊組建#
最小配置#
| 角色 | 數量 | 職責 |
|---|---|---|
| 演算法工程師 | 1-2 | 資料分析、模型訓練、效果評估 |
| 後端工程師 | 2 | 推薦服務、API 開發 |
| 資料工程師 | 1-2 | 資料採集、特徵管線、日誌管理 |
工程師核心素質#
| 素質 | 說明 |
|---|---|
| 工程能力 | 快速交付高效、低 bug 的程式碼 |
| 理論基礎 | 能看懂論文、理解演算法原理 |
| 視覺化思維 | 把資料規律直觀呈現出來 |
| 業務 sense | 把模型指標連回真實業務 KPI |
總結#
| 要點 | 說明 |
|---|---|
| 資料採集 | 規範埋點、客戶 / 服務端互補、處理 Position Bias |
| 資料流 | 批次 vs 流式、CDC、事件日誌作為單一真相來源 |
| 去重策略 | SimHash 內容去重、Bloom Filter 用戶去重 |
| 即時推薦 | 三個層次:服務即時、特徵即時、模型即時 |
| 實驗平台 | 分層實驗、無偏流量分配、足夠樣本量 |
| 服務化 | 低延遲、高可用、四類指標監控、防訓練/服務偏移 |
| 開源工具 | 按模組選擇、自己組合 |
下一章:品質與安全