推荐系统设计
问题
如何设计一个内容推荐系统?
答案
架构概览
推荐流程:召回 → 粗排 → 精排 → 重排
| 阶段 | 目的 | 候选量 | 方法 |
|---|---|---|---|
| 召回 | 从海量内容取候选 | 百万→几千 | 协同过滤、标签匹配、热门 |
| 粗排 | 快速筛选 | 几千→几百 | 轻量模型打分 |
| 精排 | 精确排序 | 几百→几十 | 深度学习模型 |
| 重排 | 业务规则调整 | 几十→最终 | 去重、多样性、运营插入 |
协同过滤(基于用户/物品)
基于物品的协同过滤(Item-CF)简化实现
public List<Long> recommend(long userId, int topN) {
// 1. 获取用户历史行为(点击/收藏/购买)
Set<Long> userItems = userBehaviorService.getItems(userId);
// 2. 找到相似物品
Map<Long, Double> candidateScores = new HashMap<>();
for (Long itemId : userItems) {
// 获取与 itemId 相似的物品及相似度
List<ItemSimilarity> similars = similarityService.getSimilarItems(itemId, 20);
for (ItemSimilarity sim : similars) {
if (!userItems.contains(sim.getItemId())) {
candidateScores.merge(sim.getItemId(), sim.getScore(), Double::sum);
}
}
}
// 3. 按得分排序取 TopN
return candidateScores.entrySet().stream()
.sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
.limit(topN)
.map(Map.Entry::getKey)
.toList();
}
热门推荐(冷启动兜底)
Redis ZSet 维护热门内容
// 每次用户行为(浏览/点赞)增加热度分
public void incrHotScore(long itemId) {
redisTemplate.opsForZSet().incrementScore("hot:items",
String.valueOf(itemId), 1.0);
}
// 热度衰减(每小时执行)
@Scheduled(cron = "0 0 * * * ?")
public void decayHotScore() {
Set<ZSetOperations.TypedTuple<String>> items =
redisTemplate.opsForZSet().rangeWithScores("hot:items", 0, -1);
for (var item : items) {
redisTemplate.opsForZSet().add("hot:items",
item.getValue(), item.getScore() * 0.95); // 衰减 5%
}
}
常见面试问题
Q1: 新用户冷启动怎么办?
答案:
- 热门推荐兜底
- 注册时收集兴趣标签
- 基于人口统计学属性推荐(年龄、地区)
- 随机探索 + 快速收集行为数据
Q2: 推荐结果如何避免同质化?
答案:
在重排阶段加入多样性策略:
- 类目打散:同类目连续不超过 2 个
- 已读过滤:布隆过滤器过滤已曝光内容
- 探索机制:一定比例的随机推荐
Q3: 协同过滤的冷启动和数据稀疏问题?
答案:
- 冷启动:新物品无行为数据 → 用内容特征(标题、标签)做基于内容的推荐
- 数据稀疏:引入隐语义模型(矩阵分解 ALS)或深度学习模型