推荐系统是消费互联网的「增长引擎」——YouTube 70% 的观看、Netflix 80% 的播放、抖音的沉浸式信息流都来自推荐。推荐系统的架构难点不在单个模型,而在「召回候选 → 特征拼接 → 排序 → 近实时更新 → 实验验证」这一整套工程链路。本文按面试答题结构设计一个内容/商品通用推荐系统。
一句话:推荐系统 = 海量候选里「召回 + 精排」两步漏斗 + 实时的特征管道 + 持续的 AB 实验飞轮,三者缺一不可。
一、需求澄清与量级估算
1.1 需求澄清
- 推荐什么:信息流(短视频/图文)还是商品?我们做「短视频信息流推荐」为主,可推广到商品。
- 推荐位:首页信息流下拉无限流,还是固定槽位 + 相关推荐?
- 实时性要求:用户刚看完一个视频,下一个是否马上受其影响(近实时反馈)?
- 个性化程度:新老用户策略差异(冷启动)?
- 实验能力:是否要做 AB 实验平台、离线评估?
明确假设:
| 需求项 | 假设 |
|---|---|
| 内容规模 | 1000 万活跃视频,日新增 10 万 |
| 用户规模 | 1 亿注册,5000 万 DAU |
| 请求 QPS | 峰值 5 万次推荐请求/秒 |
| 实时性 | 用户行为 1 分钟内在下次刷新生效 |
| 指标 | 时长(watch time)、完播率、互动率、多样性 |
1.2 量级估算
| 指标 | 估算值 | 推导 |
|---|---|---|
| 每日推荐请求 | 50 亿次 | 5000 万 DAU × 平均 100 次刷新 |
| 每次请求返回 | 20 条 | 一屏内容 |
| 召回候选池 | 每请求 1 万条 | 粗筛后进入精排 |
| 精排打分 | 5000 万 次/天 | 5 万 QPS × 1 万候选 × 24h 峰值系数 |
| 特征写入 | 千万级事件/秒 | 播放、点赞、评论、完播等行为流 |
一句话:QPS 上十万、特征万亿级,推荐系统必须「分阶段漏斗」——先低成本召回砍到千级候选,再对候选精排,绝不让大模型处理全部候选。
1.3 评价体系与离线指标
推荐系统的目标必须先定义清楚,否则「好不好」无从谈起:
| 层面 | 指标 | 说明 |
|---|---|---|
| 北极星 | 用户时长 / GMV / 留存 | 业务最终目标 |
| 核心体验 | 点击率、完播率、互动率 | 内容质量与相关性 |
| 多样与健康 | 类目覆盖率、长尾曝光、信息茧房度 | 防同质化 |
| 系统质量 | 延迟、资源成本 | 工程约束 |
离线指标(AUC/GAUC/NDCG)评估「模型好不好」,在线指标评估「对业务有没有用」,两者都要。面试能说出「北极星 → 体验 → 多样 → 成本」的指标分层,就展示了业务 sense。
二、高层架构设计
用户行为(App / Web 埋点) 内容库(视频/商品)
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ 数据与特征平台 │
│ 行为日志Kafka ─▶ Flink 实时特征 / 离线数仓特征 ─▶ 特征存储 │
│ 内容属性、用户画像、社交关系、序列特征 │
└───────────────┬─────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 推荐服务(在线链路) │
│ 请求 ─▶ 召回(多路) ─▶ 粗排 ─▶ 精排 ─▶ 重排(多样性/打散) ─▶ 响应│
│ 召回路: 向量相似(ANN) / 协同过滤 / 热门 / 语义 / 运营池 │
└───────────────┬─────────────────────────────────────────────┘
│ (精排打分需要模型)
┌───────────────▼─────────────────────────────────────────────┐
│ 模型训练与实验平台 │
│ 样本日志 ─▶ 特征拼接 ─▶ 离线训练(GBDT/DeepFM/Transformer) │
│ ─▶ 模型发布/灰度 ─▶ AB 实验系统 ─▶ 指标看板(时长/互动/多样性) │
└─────────────────────────────────────────────────────────────┘
三大子系统:
- 数据与特征平台:负责行为埋点、特征计算与存储,支撑在线和离线。
- 在线推荐服务:请求漏斗(召回→粗排→精排→重排),低延迟返回。
- 训练与实验平台:离线训练、模型上线、AB 验证,形成「数据飞轮」。
2.1 为什么召回和精排必须分开
- 候选量大:1000 万内容不可能逐个跑大模型,先召回砍到 1000-10000。
- 计算成本差异:召回可以用向量索引、倒排、协同过滤等轻量方法;精排用复杂模型(DeepFM、双塔 + 交叉)。
- 各司其职:召回负责「找对方向」(recall 高),精排负责「排准顺序」(precision 高)。
一句话:召回保证「不漏」,精排保证「不错」,两段漏斗让「亿级候选」变成「可被模型逐条打分」的百级列表。
三、核心组件设计
3.1 召回层(多路召回)
召回不是单一方法,而是多路并行、结果合并去重:
| 召回路 | 原理 | 适合 | 成本 |
|---|---|---|---|
| 向量召回(ANN) | 用户/物品 Embedding 做近似最近邻 | 大规模内容,主召回 | 高(需建索引) |
| 协同过滤(ItemCF/UserCF) | 行为相似物品/用户 | 中长尾、关系网 | 中 |
| 语义召回 | 文本 Embedding(标题/标签) | 冷启动、跨域 | 中 |
| 热门召回 | 全局热门/区域热门 | 兜底、新用户 | 低 |
| 运营池召回 | 人工精选内容 | 活动、调性 | 低 |
| 序列召回(Next-It-Net) | 用户行为序列预测下一个 | 时效性强内容 | 高 |
向量召回架构:
物品侧 Embedding(离线产出) ─▶ 向量索引(Faiss / HNSW) ─▶ ANN Top-K
用户侧 Embedding(在线拼接实时特征) ─▶ 查询向量
→ 返回 K 个候选(如 200 路 × 30 个)
3.2 粗排与精排
- 粗排:轻量模型(双塔打分、LR、GBDT 小模型)在 1000-10000 候选上快速打分,筛出 100-300 条进精排。目标是控制精排计算量。
- 精排:复杂模型(DeepFM / DIN / 多任务 MMoE)对 100-300 条精确打分。每条候选要拼接特征、过模型,成本高但准。
def rank_pipeline(user_id, candidates):
# 1. 召回合并去重
recalls = merge(multi_recall(user_id)) # ~10000
# 2. 粗排: 轻量模型过滤
mid = coarse_rank(user_id, recalls, limit=300) # ~300
# 3. 精排: 复杂模型打分
scored = [(c, fine_model.score(user_feat, item_feat(c))) for c in mid]
# 4. 重排: 多样性/打散/去重同质内容
return rerank(sorted(scored, reverse=True), limit=20)
3.3 特征工程与特征服务
特征分三类:
- 用户特征:性别年龄、城市、注册时长、历史兴趣向量、近 7 天行为聚合。
- 物品特征:类目、标签、发布时间、完播率/点击率统计、物品 Embedding。
- 上下文特征:时间(早中晚)、设备、网络、场景(首页/搜索/相关推荐)。
特征服务(在线拼接):特征计算后统一写入特征存储(Redis / FeatHub / TeraSort 风格),在线推荐服务一次取回全量特征向量,避免逐特征 RPC。
实时特征流: 行为事件 Kafka ─▶ Flink 窗口聚合 ─▶ 特征存储(Redis, TTL 7天)
离线特征: 数仓 T+1 ─▶ 特征表 ─▶ 同步到在线存储
一句话:特征服务是「在线模型的地基」,要把特征做成「预计算 + 存储 + 一次性取回」,而不是在线实时算每个特征。
3.4 重排与多样性控制
精排给出的是「单条候选的预测价值」,但用户要看的是一整屏,必须做重排:
| 重排手段 | 解决的问题 | 实现 |
|---|---|---|
| 同源打散 | 连续 N 条来自同一召回源/同一作者 | 贪心插入:选价值最高且与前 K 条不冲突的 |
| 类目均衡 | 避免信息茧房、类目比例失衡 | 按用户兴趣分布的类目配额 |
| 时效加权 | 新内容/热点优先 | 精排分数上叠加时间衰减 |
| 运营混排 | 置顶活动、品牌合作 | 插槽规则,保底位置 |
重排本质是「带约束的序列优化」,贪心即可(近似最优),面试能讲清「精排看单条、重排看整屏」的分工就够了。
一句话:精排负责「每条值多少分」,重排负责「整屏怎么摆才不腻」,两者目标不同,缺一不可。
3.5 特征拼接与推理
在线推理把「用户特征 + 物品特征 + 上下文」拼成模型输入,注意三点:
- 一次性取回:按 user_id + 候选 item_id 批量从特征服务取回,避免 N 次 RPC。
- 特征版本一致性:离线训练与在线推理用同一版本的特征 schema,否则「训练偏差」让模型离线好、线上崩。
- 缓存热点:高热度物品的特征可长时间缓存,用户特征按活跃度分层缓存(活跃用户 TTL 短、普通用户 TTL 长)。
def assemble_features(user_id, items, ctx):
user_feat = feat_store.get_user(user_id, ctx)
item_feats = feat_store.batch_get_items(items) # 批量
return [concat(user_feat, item_feats[i], ctx) for i in items]
四、数据模型
样本与特征的核心表(离线):
CREATE TABLE train_sample (
sample_id BIGINT,
user_id BIGINT,
item_id BIGINT,
label TINYINT, -- 是否点击/完播/时长分桶
context VARCHAR(64), -- 场景+位置
features MAP<STRING,FLOAT>, -- 特征快照(命中时拼接)
date STRING, -- 分区
KEY (user_id, date), KEY (item_id)
) PARTITIONED BY (date);
CREATE TABLE user_embedding (
user_id BIGINT PRIMARY KEY,
embedding ARRAY<FLOAT>, -- 128/256 维
version INT,
updated_at DATETIME
);
CREATE TABLE item_embedding (
item_id BIGINT PRIMARY KEY,
embedding ARRAY<FLOAT>,
category INT,
hot_score DOUBLE,
status TINYINT
);
在线特征存储用 Redis/KV:rec:user:{uid} → 用户特征 JSON;rec:item:{iid} → 物品特征。KV 大表用一致性哈希分片。
五、关键流程
5.1 一次推荐请求时序(在线链路)
用户刷新 → 网关鉴权 → 推荐服务
1. 组装上下文特征(设备/时间/场景)
2. 取用户特征(Redis) + 用户实时行为序列(最近100条)
3. 多路召回并行 → 合并去重(约1万)
4. 粗排(轻量模型) → 300
5. 精排(复杂模型逐条打分) → 排序
6. 重排(打散同源/避免连刷/运营混排) → 20条
7. 拼装物品摘要/封面 → 返回(延迟要求 < 200ms)
延迟预算:召回 40ms + 特征拼接 30ms + 粗排 20ms + 精排 80ms + 重排 10ms ≈ 180ms,各环节并行化。
5.2 近实时特征更新(数据飞轮)
用户点击视频 A(1s内) → 埋点 → Kafka → Flink 实时特征
→ 更新用户序列特征(Redis) / 实时 CTR 计数
→ 1分钟后用户再次刷新: 新特征已生效,推荐结果紧跟行为
→ 行为日志离线落数仓 → T+1 样本 → 训练新模型 → 发布 → 影响后续推荐
一句话:近实时更新靠「Flink 实时特征 + Redis 缓存生效」支撑分钟级反馈,离线再训练支撑天级模型演进,这就是数据飞轮。
5.3 模型训练与在线推理
离线训练流水线:
行为日志 → 特征拼接(join 特征表) → 样本采样/负样本构造 → 训练(DeepFM/DIN/MMoE)
→ 模型评估(离线指标) → 模型注册中心 → 发布
在线推理两种形态:
| 形态 | 适用 | 说明 |
|---|---|---|
| 近线批量打分 | 粗排、热门榜 | 定期(分钟/小时)对候选池打分写入 KV |
| 在线实时打分 | 精排 | 请求到达时对 100-300 候选逐条打分 |
模型发布用「灰度 + 回滚」:新模型先小流量(如 1%),AB 指标正向再逐步放量;异常自动回滚到旧版本。
一句话:训练与推理要「特征同构、版本可控、灰度发布」,否则模型迭代就是在线上裸奔。
5.4 用户画像构建
用户画像是「特征工程 + 知识沉淀」的结合体:
用户画像 = 静态属性(注册/设备/地域)
+ 短期行为(最近N次点击/观看序列, 实时更新)
+ 长期兴趣(类目/标签权重衰减, 半衰期模型)
+ 关系图谱(社交关注、相似用户)
+ 向量化(行为序列 → 用户 Embedding, 周期性重算)
画像的存储与更新:
- 静态画像:MySQL / 数仓 T+1。
- 实时画像:Redis(KV,TTL 控制);序列特征用 Redis List(保留最近 100 条行为)。
- 用户 Embedding:离线周期性计算 + 增量更新,向量库存。
一句话:画像分「静态 + 短期 + 长期」三桶,短期走实时、长期走离线,各有各的存储与更新频率。
5.5 场景化推荐差异
不同推荐位对链路的要求不同:
| 场景 | 候选池 | 延迟要求 | 主要链路 |
|---|---|---|---|
| 首页信息流 | 全量 | 200ms | 多路召回 + 粗排 + 精排 + 重排 |
| 相关推荐(详情页) | 当前物品邻居 | 50ms | 向量召回 + 轻量排序 |
| 搜索后推荐 | 当前 query 相关 | 100ms | 语义召回 + 排序 |
| 运营 banner | 运营池 | 即时 | 规则/权重排序 |
面试答出「场景决定链路深浅」比「一套方案打天下」更有经验含量。
六、冷启动与 AB 实验
6.1 冷启动问题
- 新用户:无行为 → 用热门召回 + 地域/设备画像 + 引导式选择兴趣标签;逐步积累行为后过渡到个性化。
- 新物品:无曝光 → 用语义召回(文本/封面 Embedding)补充曝光;探索机制(bandit / 一定比例探索流量)让新内容有机会被看见。
| 冷启动对象 | 数据来源 | 策略 |
|---|---|---|
| 新用户 | 注册信息、设备、地域 | 热门 + 热门类目 + 兴趣引导 |
| 新物品 | 内容属性、标题、标签 | 语义 Embedding + 探索曝光 |
| 新场景/活动 | 运营池 | 强运营干预 + 规则召回 |
6.2 评估与 AB 实验
离线评估:GAUC、AUC、NDCG@K、召回率/精确率;防止离线指标与线上不一致(偏差)。
在线 AB:流量分桶(按 user_id hash),对比时长、完播率、互动率、多样性,做显著性检验(t 检验、AA 回归)。
实验平台: 实验配置中心 → 分流(SDK) → 埋点回流 → 指标看板 → 决策(放量/回滚)
七、性能与扩展
- 向量索引:HNSW/IVF-PQ 在内存(每亿向量约几十 GB),分片多副本承载高 QPS。
- 缓存:热门结果/热门用户特征缓存;用户请求结果缓存 TTL 数秒,防抖刷。
- 降级:精排模型超时降级到粗排结果;召回超时丢路;兜底热门列表。
- 弹性:大促/晚高峰扩容,模型版本灰度。
成本与扩展权衡
| 手段 | 收益 | 代价 |
|---|---|---|
| 增加召回路 | recall 提升 | 合并去重成本、延迟增加 |
| 精排模型更复杂 | 排序精度 | 延迟、GPU/CPU 成本翻倍 |
| 特征更实时 | 反馈更及时 | Flink 资源、存储成本 |
| 更大候选池 | 长尾覆盖 | 精排计算量线性上涨 |
八、权衡与备选
| 决策点 | 本文选型 | 备选 | 权衡 |
|---|---|---|---|
| 向量索引 | Faiss(HNSW) | Milvus / Vespa | Faiss 自建可控;Milvus 托管省心 |
| 特征流 | Kafka + Flink | Spark Streaming / 自研 | Flink 低延迟窗口;Spark 批处理更省资源 |
| 精排模型 | DeepFM + MMoE | 双塔、Transformer、重排 GNN | 精度 vs 延迟/成本折中 |
| 召回 | 多路并行合并 | 单一向量召回 | 多路更稳,但需合并逻辑 |
| 实时性 | 分钟级 Flink | 秒级 lambda 架构 | 秒级成本高,收益边际递减 |
取舍原则
- 延迟 > 极致个性化:信息流 200ms 内出结果,模型不能太慢。
- 多样性与相关性平衡:高相关但同质会让用户腻,加打散和多样性指标。
- 离线与在线指标一致:做不好评估,模型演进就是盲人摸象。
九、扩展场景与面试追问
9.1 相关推荐 / 详情页推荐
详情页「看了又看」「买了又买」是独立场景:不依赖用户全局画像,只看「当前物品 → 相似物品」(ItemCF 或物品向量最近邻),延迟要求更高(毫秒级),候选池小(单物品邻居),可以走纯向量召回 + 规则重排。
9.2 商品推荐 vs 内容推荐差异
| 维度 | 内容(视频/图文) | 商品 |
|---|---|---|
| 反馈信号 | 完播、时长、点赞 | 点击、加购、转化、GMV |
| 目标 | 时长/留存 | 转化率/客单价 |
| 候选池变化 | 日增 10 万新内容 | 相对静态,长尾多 |
| 冷启动 | 新内容靠语义曝光 | 新商品靠类目 + 相似品 |
9.3 多目标优化与算力约束
推荐要同时优化「时长、互动、多样性」,常见做法:
- 多目标模型(MMoE/PLE):共享底层表示、各目标独立 tower,用 gate 加权融合。
- 目标权重调参:线上用「时长/互动/多样性」加权分排序,权重靠实验调。
- 算力约束:精排 GPU 批推理、粗排用轻量模型、候选池上限设置——把算力花在「最可能被点」的候选上。
9.4 面试常见追问
| 追问 | 关键回答 |
|---|---|
| 用户画像怎么做? | 注册属性 + 行为聚合(类目/标签加权)+ 向量化,离线 T+1 + 实时增量 |
| 负样本怎么构造? | 曝光未点击(in-batch 负采样)+ 随机采样,注意防「曝光偏差」 |
| 推荐结果总是同一类怎么办? | 重排打散 + 多样性指标进 AB 评估,必要时限制类目占比 |
| 新内容没人看怎么办? | 语义召回 + 探索流量(一定比例随机/bandit 曝光)+ 冷启动扶持池 |
| 模型在线延迟太高? | 特征预计算、批量取回、近线打分、小模型蒸馏、GPU 批推理 |
十、总结
| 模块 | 关键点 | 一句话记忆 |
|---|---|---|
| 召回 | 多路并行 + 向量 ANN | 先找对方向,成本低 |
| 粗排/精排 | 两段漏斗 | 粗排砍量、精排保质 |
| 特征服务 | 预计算 + 存储 + 一次性取回 | 特征地基决定模型上限 |
| 近实时 | Kafka + Flink + Redis | 分钟级反馈生效 |
| 冷启动 | 热门 + 语义 + 探索 | 无数据也能推荐 |
| 实验 | AB 分流 + 指标评估 | 数据飞轮验证每次迭代 |
一句话:推荐系统面试先讲「召回→精排→重排」漏斗和「离线/在线一致性」,再深入特征服务与近实时更新,最后用冷启动与 AB 实验展示工程闭环——这比背一个模型更有区分度。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。