跳到主要内容

推荐系统设计

问题

如何设计一个内容推荐系统?

答案

架构概览

推荐流程:召回 → 粗排 → 精排 → 重排

阶段目的候选量方法
召回从海量内容取候选百万→几千协同过滤、标签匹配、热门
粗排快速筛选几千→几百轻量模型打分
精排精确排序几百→几十深度学习模型
重排业务规则调整几十→最终去重、多样性、运营插入

协同过滤(基于用户/物品)

基于物品的协同过滤(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)或深度学习模型

相关链接