🎯 设计一个推荐系统架构:读心术的秘密!

33 阅读9分钟

📖 开场:书店导购员

想象你在书店 📚:

没有推荐(迷茫)

你:进入书店
    ↓
看到10万本书 😵
    ↓
不知道看什么 🤷
    ↓
逛了2小时,一本没买 ❌

结果:
- 选择困难 ❌
- 浪费时间 ❌
- 体验差 ❌

有推荐(精准)

导购员:
"看您之前买过《三体》,
 推荐您看《流浪地球》,
 同类型科幻小说!"
    ↓
你:哇,正是我想看的!✅
    ↓
立即下单 💰

结果:
- 精准推荐 ✅
- 节省时间 ✅
- 体验好 ✅
- 提高销量 ✅

这就是推荐系统:帮你找到想要的东西!


🤔 推荐算法

算法1:协同过滤(Collaborative Filtering)⭐⭐⭐

用户协同过滤(User-Based)

原理:
"和你相似的人喜欢什么,就推荐给你"

例子:
用户A喜欢:《三体》《流浪地球》
用户B喜欢:《三体》《流浪地球》《球状闪电》
    ↓
用户A和用户B相似度高
    ↓
推荐《球状闪电》给用户A

相似度计算(余弦相似度)

用户A的评分向量:[5, 4, 0, 0, 3]
用户B的评分向量:[5, 5, 4, 0, 0]
    ↓
余弦相似度 = cos(A, B) = (A·B) / (|A| × |B|)
           = (5×5 + 4×5 + 0×4) / (√50 × √66)
           = 45 / 57.4
           = 0.78

相似度0.78(很高)✅

物品协同过滤(Item-Based)⭐⭐⭐

原理:
"你喜欢A商品,推荐和A相似的B商品"

例子:
用户看了《三体》
    ↓
计算和《三体》相似的商品
    ↓
《流浪地球》相似度:0.85
《球状闪电》相似度:0.80
    ↓
推荐这两本书 ✅

优点:
- 物品数量稳定,相似度可以预计算 ✅
- 性能好 ✅

代码实现

@Service
public class ItemBasedCF {
    
    @Autowired
    private UserBehaviorMapper userBehaviorMapper;
    
    // 物品相似度矩阵(提前计算好)
    private Map<Long, Map<Long, Double>> itemSimilarityMatrix = new ConcurrentHashMap<>();
    
    /**
     * ⭐ 推荐商品(物品协同过滤)
     */
    public List<Long> recommend(Long userId, int topN) {
        // 1. 获取用户历史行为(浏览/购买过的商品)
        List<UserBehavior> behaviors = userBehaviorMapper.selectByUserId(userId);
        
        // 2. 计算推荐分数
        Map<Long, Double> recommendScores = new HashMap<>();
        
        for (UserBehavior behavior : behaviors) {
            Long itemId = behavior.getItemId();
            
            // 获取和该商品相似的商品
            Map<Long, Double> similarItems = itemSimilarityMatrix.get(itemId);
            
            if (similarItems != null) {
                for (Map.Entry<Long, Double> entry : similarItems.entrySet()) {
                    Long similarItemId = entry.getKey();
                    Double similarity = entry.getValue();
                    
                    // 累加推荐分数
                    recommendScores.merge(similarItemId, similarity, Double::sum);
                }
            }
        }
        
        // 3. 排序,取TopN
        return recommendScores.entrySet().stream()
            .sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
            .limit(topN)
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());
    }
    
    /**
     * ⭐ 计算物品相似度矩阵(离线计算)
     */
    @Scheduled(cron = "0 0 2 * * ?")  // 每天凌晨2点
    public void calculateItemSimilarity() {
        // 1. 获取所有用户行为数据
        List<UserBehavior> behaviors = userBehaviorMapper.selectAll();
        
        // 2. 构建 用户-物品 评分矩阵
        Map<Long, Map<Long, Integer>> userItemMatrix = new HashMap<>();
        for (UserBehavior behavior : behaviors) {
            userItemMatrix.computeIfAbsent(behavior.getUserId(), k -> new HashMap<>())
                         .put(behavior.getItemId(), 1);  // 1表示有交互
        }
        
        // 3. 计算物品相似度
        List<Long> itemIds = userBehaviorMapper.selectAllItemIds();
        
        for (Long itemId1 : itemIds) {
            Map<Long, Double> similarities = new HashMap<>();
            
            for (Long itemId2 : itemIds) {
                if (itemId1.equals(itemId2)) {
                    continue;
                }
                
                // 计算itemId1和itemId2的相似度
                double similarity = calculateSimilarity(itemId1, itemId2, userItemMatrix);
                
                if (similarity > 0) {
                    similarities.put(itemId2, similarity);
                }
            }
            
            // 保存相似度
            itemSimilarityMatrix.put(itemId1, similarities);
        }
        
        System.out.println("⭐ 物品相似度矩阵计算完成");
    }
    
    /**
     * 计算两个物品的相似度(余弦相似度)
     */
    private double calculateSimilarity(Long itemId1, Long itemId2, 
                                      Map<Long, Map<Long, Integer>> userItemMatrix) {
        Set<Long> users1 = new HashSet<>();
        Set<Long> users2 = new HashSet<>();
        
        // 找出交互过itemId1和itemId2的用户
        for (Map.Entry<Long, Map<Long, Integer>> entry : userItemMatrix.entrySet()) {
            Map<Long, Integer> items = entry.getValue();
            if (items.containsKey(itemId1)) {
                users1.add(entry.getKey());
            }
            if (items.containsKey(itemId2)) {
                users2.add(entry.getKey());
            }
        }
        
        // 计算交集(同时交互过两个物品的用户数)
        Set<Long> intersection = new HashSet<>(users1);
        intersection.retainAll(users2);
        
        if (intersection.isEmpty()) {
            return 0;
        }
        
        // 余弦相似度
        return (double) intersection.size() / 
               Math.sqrt(users1.size() * users2.size());
    }
}

算法2:内容推荐(Content-Based)📝

原理:
"根据物品的内容特征推荐"

例子:
用户喜欢:《三体》
特征:科幻、刘慈欣、获奖作品
    ↓
推荐具有相似特征的商品:
《流浪地球》:科幻、刘慈欣 ✅
《球状闪电》:科幻、刘慈欣 ✅

代码实现

@Service
public class ContentBasedRecommender {
    
    /**
     * ⭐ 基于内容推荐
     */
    public List<Long> recommend(Long userId, int topN) {
        // 1. 获取用户历史喜欢的商品
        List<Long> likedItemIds = userBehaviorMapper.selectLikedItems(userId);
        
        // 2. 提取这些商品的特征标签
        Set<String> userInterestTags = new HashSet<>();
        for (Long itemId : likedItemIds) {
            Item item = itemMapper.selectById(itemId);
            userInterestTags.addAll(item.getTags());
        }
        
        // 3. 查找具有相似标签的商品
        List<Item> candidateItems = itemMapper.selectAll();
        
        Map<Long, Double> scores = new HashMap<>();
        for (Item item : candidateItems) {
            // 计算标签匹配度
            double score = calculateTagSimilarity(userInterestTags, item.getTags());
            scores.put(item.getId(), score);
        }
        
        // 4. 排序,取TopN
        return scores.entrySet().stream()
            .sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
            .limit(topN)
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());
    }
    
    /**
     * 计算标签相似度
     */
    private double calculateTagSimilarity(Set<String> tags1, Set<String> tags2) {
        Set<String> intersection = new HashSet<>(tags1);
        intersection.retainAll(tags2);
        
        Set<String> union = new HashSet<>(tags1);
        union.addAll(tags2);
        
        return union.isEmpty() ? 0 : (double) intersection.size() / union.size();
    }
}

算法3:混合推荐(Hybrid)⭐⭐⭐

混合策略:
1. 协同过滤 权重0.5
2. 内容推荐 权重0.3
3. 热门推荐 权重0.2
    ↓
加权求和,综合排序 ✅

优点:
- 结合多种算法优点 ✅
- 推荐更准确 ✅

🎯 系统架构

        推荐系统架构

┌────────────────────────────────────┐
│         客户端(App/Web)           │
└──────────────┬─────────────────────┘
               │
               ↓
┌────────────────────────────────────┐
│        推荐服务(实时)             │
│  - 召回层                          │
│  - 排序层                          │
│  - 重排层                          │
└──────────────┬─────────────────────┘
               │
       ┌───────┼───────┐
       ↓       ↓       ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 协同过滤 │ │ 内容推荐 │ │ 热门推荐 │
└──────────┘ └──────────┘ └──────────┘
       ↓       ↓       ↓
┌────────────────────────────────────┐
│      离线计算(Spark/Flink)        │
│  - 相似度矩阵                      │
│  - 用户画像                        │
│  - 物品特征                        │
└────────────────────────────────────┘

召回层(Recall)

作用:
从百万商品中,快速召回1000个候选商品

召回策略:
1. 协同过滤召回:300个
2. 内容推荐召回:300个
3. 热门推荐召回:200个
4. 兴趣标签召回:200个
    ↓
合并去重 → 1000个候选商品 ✅

排序层(Ranking)

作用:
对1000个候选商品精排,取Top100

排序模型(CTR预估):
特征:
- 用户特征:年龄、性别、地域
- 物品特征:价格、类目、销量
- 交叉特征:用户-物品历史交互

模型:
- 逻辑回归(LR)
- GBDT
- 深度学习(DNN)

输出:
每个商品的点击率预估 → 排序 → Top100

代码实现

@Service
public class RankingService {
    
    @Autowired
    private ModelService modelService;
    
    /**
     * ⭐ 排序(CTR预估)
     */
    public List<Long> rank(Long userId, List<Long> candidateItemIds) {
        // 1. 特征工程
        List<RankingItem> rankingItems = new ArrayList<>();
        
        for (Long itemId : candidateItemIds) {
            // 提取特征
            Map<String, Double> features = extractFeatures(userId, itemId);
            
            // CTR预估
            double ctr = modelService.predictCTR(features);
            
            RankingItem rankingItem = new RankingItem();
            rankingItem.setItemId(itemId);
            rankingItem.setCtr(ctr);
            rankingItems.add(rankingItem);
        }
        
        // 2. 排序
        return rankingItems.stream()
            .sorted(Comparator.comparingDouble(RankingItem::getCtr).reversed())
            .map(RankingItem::getItemId)
            .collect(Collectors.toList());
    }
    
    /**
     * 提取特征
     */
    private Map<String, Double> extractFeatures(Long userId, Long itemId) {
        Map<String, Double> features = new HashMap<>();
        
        // 用户特征
        User user = userMapper.selectById(userId);
        features.put("user_age", (double) user.getAge());
        features.put("user_gender", user.getGender() == "M" ? 1.0 : 0.0);
        
        // 物品特征
        Item item = itemMapper.selectById(itemId);
        features.put("item_price", item.getPrice().doubleValue());
        features.put("item_sales", (double) item.getSales());
        
        // 交叉特征
        int historyCount = userBehaviorMapper.countInteraction(userId, itemId);
        features.put("history_interaction", (double) historyCount);
        
        return features;
    }
}

重排层(Re-Ranking)

作用:
对Top100商品进行业务规则调整

规则:
1. 去重(用户已购买的商品)
2. 多样性(不要全推荐同一类目)
3. 新品扶持(新品加权)
4. 广告插入(第3、8位插入广告)
    ↓
最终推荐列表 ✅

🎓 面试题速答

Q1: 推荐算法有哪些?

A: 三大算法

  1. 协同过滤(最常用)⭐⭐⭐:

    • 用户协同:和你相似的人喜欢什么
    • 物品协同:推荐相似的商品
  2. 内容推荐

    • 根据物品特征推荐
  3. 混合推荐

    • 结合多种算法

Q2: 物品协同过滤如何实现?

A: 相似度矩阵 + 离线计算

// 离线计算物品相似度矩阵
@Scheduled(cron = "0 0 2 * * ?")
public void calculateItemSimilarity() {
    // 计算物品之间的余弦相似度
    // 保存到相似度矩阵
}

// 在线推荐
public List<Long> recommend(Long userId) {
    // 获取用户历史行为
    List<Long> historyItems = getHistory(userId);
    
    // 从相似度矩阵查询相似商品
    Map<Long, Double> scores = new HashMap<>();
    for (Long itemId : historyItems) {
        Map<Long, Double> similarItems = similarityMatrix.get(itemId);
        // 累加分数
    }
    
    // 排序返回
}

Q3: 推荐系统架构分哪几层?

A: 三层架构

  1. 召回层

    • 从百万商品召回1000个候选
  2. 排序层

    • CTR预估精排,取Top100
  3. 重排层

    • 业务规则调整

Q4: 如何评估推荐效果?

A: 四大指标

  1. 点击率(CTR)

    • 推荐商品被点击的比例
  2. 转化率(CVR)

    • 推荐商品被购买的比例
  3. 准确率(Precision)

    • 推荐正确的比例
  4. 召回率(Recall)

    • 用户感兴趣的被推荐的比例

Q5: 冷启动问题如何解决?

A: 三种策略

  1. 新用户

    • 推荐热门商品
    • 引导用户填写兴趣标签
  2. 新商品

    • 内容推荐(根据特征)
    • 新品扶持(加权)
  3. 新用户+新商品

    • 基于人口统计学(年龄、性别)

Q6: A/B测试如何做?

A: 分流实验

// A/B测试分流
public List<Long> recommend(Long userId) {
    // 用户ID取模分流
    int group = (int) (userId % 100);
    
    if (group < 50) {
        // A组:老算法(50%流量)
        return oldAlgorithm.recommend(userId);
    } else {
        // B组:新算法(50%流量)
        return newAlgorithm.recommend(userId);
    }
}

// 统计对比指标
// A组CTR、CVR
// B组CTR、CVR
// 如果B组效果好,全量上线

🎬 总结

       推荐系统核心

┌────────────────────────────────────┐
│ 1. 协同过滤 ⭐⭐⭐                   │
│    - 物品协同(最常用)            │
│    - 相似度矩阵离线计算            │
└────────────────────────────────────┘

┌────────────────────────────────────┐
│ 2. 三层架构                        │
│    - 召回层(1000候选)            │
│    - 排序层(Top100)              │
│    - 重排层(业务规则)            │
└────────────────────────────────────┘

┌────────────────────────────────────┐
│ 3. CTR预估                         │
│    - 特征工程                      │
│    - 机器学习模型                  │
└────────────────────────────────────┘

┌────────────────────────────────────┐
│ 4. 冷启动                          │
│    - 热门推荐                      │
│    - 内容推荐                      │
└────────────────────────────────────┘

┌────────────────────────────────────┐
│ 5. A/B测试                         │
│    - 分流实验                      │
│    - 效果对比                      │
└────────────────────────────────────┘

🎉 恭喜你!

你已经完全掌握了推荐系统的设计!🎊

核心要点

  1. 协同过滤:物品协同最常用,离线计算相似度矩阵
  2. 三层架构:召回→排序→重排
  3. CTR预估:机器学习模型,特征工程
  4. 冷启动:热门推荐+内容推荐
  5. A/B测试:分流实验,效果对比

下次面试,这样回答

"推荐系统采用物品协同过滤算法。离线每天凌晨2点计算物品相似度矩阵,使用余弦相似度衡量两个商品的相似程度。在线推荐时,获取用户历史行为的商品ID列表,从相似度矩阵查询每个商品的相似商品,累加推荐分数后排序返回TopN。

系统架构分为召回、排序、重排三层。召回层从百万商品中快速召回1000个候选,使用协同过滤、内容推荐、热门推荐等多路召回策略。排序层对1000个候选商品进行CTR预估精排,提取用户特征、物品特征和交叉特征,输入机器学习模型预测点击率,排序后取Top100。重排层进行业务规则调整,包括去重、多样性、新品扶持和广告插入。

CTR预估模型使用深度学习DNN。特征包括用户年龄、性别、地域,商品价格、类目、销量,以及用户商品历史交互次数。特征经过Embedding后输入多层神经网络,最后一层Sigmoid输出点击概率。模型离线训练,在线预测毫秒级响应。

冷启动问题分三种情况。新用户推荐热门商品,同时引导填写兴趣标签。新商品使用内容推荐,根据商品特征匹配用户兴趣,并给予新品加权扶持。新用户加新商品使用人口统计学推荐,根据年龄性别等属性推荐相应商品。

A/B测试用于评估新算法效果。将用户按ID取模分成A组和B组各50%流量,A组使用老算法,B组使用新算法。统计对比CTR和CVR指标,如果B组效果显著优于A组,则新算法全量上线。"

面试官:👍 "很好!你对推荐系统的设计理解很深刻!"


本文完 🎬

上一篇: 218-设计一个视频网站系统.md
下一篇: 220-设计一个监控告警系统.md

作者注:写完这篇,我都想去当算法工程师了!🎯
如果这篇文章对你有帮助,请给我一个Star⭐!