系统设计面试009:设计 Facebook 新闻动态(News Feed)

🚀 系统设计面试:设计 Facebook 新闻动态(News Feed)

摘要:Facebook News Feed 是全球最大规模的个性化推荐系统之一,日活 20 亿用户,每人关注数百个对象。本文深入剖析 Feed 系统的核心架构,包括 EdgeRank 到深度学习排序的演进、TAO 图存储引擎、推拉混合模型、实时特征工程,并提供完整的 Java 实现。


🎯 场景引入

你打开 Facebook,首页 News Feed 里混合着朋友动态、广告、推荐内容。这个 Feed 是怎么从数千条候选中筛选出来的?

核心挑战

  1. 多源聚合:朋友动态、群组帖子、广告、推荐,如何统一排序?
  2. EdgeRank 算法:亲密度 × 权重 × 时效性,如何实时计算?
  3. 隐私控制:不同帖子对不同人可见,如何高效过滤?

一、问题背景

1.1 核心挑战

复制代码
用户打开 Facebook:
  → 关注了 500 个好友/Page/Group
  → 每个对象平均每天产生 2 条内容
  → 候选内容池 = 500 × 2 = 1000 条/天
  → 用户只看 20-50 条
  → 如何从 1000 条中选出最有价值的 20 条?

核心矛盾:
  信息过载 vs 用户注意力有限
  实时性要求 vs 计算复杂度高
  个性化需求 vs 海量用户规模

1.2 核心需求

需求 说明
个性化排序 千人千面,每个用户看到不同的 Feed
实时性 新内容要有机会曝光,不能只推旧内容
多样性 文本、图片、视频、直播混合展示
低延迟 Feed 加载时间 < 500ms
高可用 20 亿 DAU,不能宕机
可解释性 用户能理解为什么看到某条内容

1.3 容量估算

指标 数值
DAU 20 亿
每用户关注对象 ~500
每日新增内容 ~100 亿条
Feed 请求 QPS ~500 万
峰值 QPS ~1500 万
单次 Feed 候选集 ~1000 条
单次返回条数 20-50 条
排序模型推理延迟 < 50ms

二、整体架构

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                         客户端 (App/Web)                         │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────────┐    │
│  │ Feed 流  │  │ 发布内容 │  │ 互动操作 │  │ 预加载管理器 │    │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘  └──────┬───────┘    │
└───────┼──────────────┼─────────────┼───────────────┼────────────┘
        │              │             │               │
        ▼              ▼             ▼               ▼
┌───────────────────────────────────────────────────────────────┐
│                        API Gateway                             │
│              (限流 / 认证 / 路由 / 负载均衡)                    │
└───────┬──────────────┬─────────────┬───────────────┬──────────┘
        │              │             │               │
        ▼              ▼             ▼               ▼
┌──────────────┐ ┌──────────┐ ┌──────────┐ ┌───────────────┐
│ Feed Service │ │ Post Svc │ │Action Svc│ │ Prefetch Svc  │
│  (聚合排序)  │ │ (发布)   │ │ (互动)   │ │  (预计算)     │
└──────┬───────┘ └────┬─────┘ └────┬─────┘ └───────┬───────┘
       │              │            │                │
       ▼              ▼            ▼                ▼
┌─────────────────────────────────────────────────────────────┐
│                      核心服务层                               │
│  ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌──────────┐ │
│  │ Retrieval  │ │  Ranking   │ │  Feature   │ │ Social   │ │
│  │  (召回)    │ │  (排序)    │ │  Store     │ │ Graph    │ │
│  └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └────┬─────┘ │
└────────┼──────────────┼──────────────┼──────────────┼───────┘
         │              │              │              │
         ▼              ▼              ▼              ▼
┌─────────────────────────────────────────────────────────────┐
│                       存储层                                  │
│  ┌──────┐ ┌──────┐ ┌────────┐ ┌───────┐ ┌───────────────┐  │
│  │ TAO  │ │MySQL │ │ Redis  │ │ HBase │ │ Feature Store │  │
│  │(图DB)│ │(元数据)│ │(计数器)│ │(行为) │ │  (特征仓库)   │  │
│  └──────┘ └──────┘ └────────┘ └───────┘ └───────────────┘  │
└─────────────────────────────────────────────────────────────┘

三、数据模型设计

3.1 核心实体

java 复制代码
/**
 * 内容帖子
 */
@Entity
@Table(name = "posts")
public class Post {
    @Id
    private Long postId;
    private Long authorId;
    private Integer contentType;       // 1=文本 2=图片 3=视频 4=直播
    private String textContent;
    private String mediaUrls;           // JSON 数组
    private Integer privacyLevel;       // 0=公开 1=好友 2=仅自己
    private Long createTime;
    private Long updateTime;
}

/**
 * 社交关系(TAO 边)
 */
@Entity
@Table(name = "social_edges")
public class SocialEdge {
    @Id
    private Long edgeId;
    private Long fromUserId;
    private Long toUserId;
    private Integer edgeType;           // 1=关注 2=好友 3=屏蔽
    private Double affinityScore;       // 亲密度分数
    private Long createTime;
}

/**
 * 用户行为记录
 */
@Entity
@Table(name = "user_actions")
public class UserAction {
    @Id
    private Long actionId;
    private Long userId;
    private Long postId;
    private Integer actionType;         // 1=浏览 2=点赞 3=评论 4=分享 5=隐藏
    private Long dwellTime;             // 停留时长(ms)
    private Long createTime;
}

/**
 * Feed 候选项(排序用)
 */
public class FeedCandidate {
    private Long postId;
    private Long authorId;
    private Double relevanceScore;      // 相关性分数
    private Double recencyScore;        // 时效性分数
    private Double diversityScore;      // 多样性分数
    private Double finalScore;          // 最终排序分数
    private Map<String, Double> featureMap; // 特征向量
}

3.2 数据库选型

数据类型 存储方案 说明
社交关系图 TAO (自研图存储) 读优化,99% 缓存命中
帖子元数据 MySQL + 分库分表 按 authorId 分片
用户行为 HBase / Cassandra 海量写入,按时间范围查询
实时计数 Redis Cluster 点赞数、评论数、分享数
特征数据 Feature Store 离线+实时特征
搜索索引 Elasticsearch 内容搜索

四、核心模块实现

4.1 Feed 聚合服务(召回 + 排序 + 过滤)

java 复制代码
/**
 * Feed 聚合服务 - 核心入口
 * 
 * 流程:召回 → 粗排 → 精排 → 过滤 → 多样性重排 → 返回
 */
@Service
public class FeedAggregatorService {

    @Autowired
    private RetrievalService retrievalService;
    @Autowired
    private RankingService rankingService;
    @Autowired
    private FilterService filterService;
    @Autowired
    private DiversityReranker diversityReranker;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 获取用户的个性化 Feed
     */
    public FeedResponse getFeed(Long userId, String cursor, int pageSize) {
        // 1. 多路召回(并行执行)
        CompletableFuture<List<FeedCandidate>> friendFuture =
            CompletableFuture.supplyAsync(() -> retrievalService.recallFromFriends(userId, 500));
        CompletableFuture<List<FeedCandidate>> followFuture =
            CompletableFuture.supplyAsync(() -> retrievalService.recallFromFollowing(userId, 300));
        CompletableFuture<List<FeedCandidate>> trendingFuture =
            CompletableFuture.supplyAsync(() -> retrievalService.recallTrending(userId, 200));

        // 合并候选集
        List<FeedCandidate> candidates = new ArrayList<>();
        try {
            candidates.addAll(friendFuture.get(100, TimeUnit.MILLISECONDS));
            candidates.addAll(followFuture.get(100, TimeUnit.MILLISECONDS));
            candidates.addAll(trendingFuture.get(100, TimeUnit.MILLISECONDS));
        } catch (Exception e) {
            log.warn("部分召回超时,使用已获取的候选集", e);
        }

        // 2. 去重
        candidates = dedup(candidates);

        // 3. 过滤(已读、违规、屏蔽)
        candidates = filterService.filter(userId, candidates);

        // 4. 粗排(轻量模型,快速筛选 Top-200)
        candidates = rankingService.coarseRank(userId, candidates, 200);

        // 5. 精排(深度模型,精确打分)
        candidates = rankingService.fineRank(userId, candidates);

        // 6. 多样性重排(避免连续出现同类型内容)
        candidates = diversityReranker.rerank(candidates);

        // 7. 分页
        List<FeedCandidate> page = paginate(candidates, cursor, pageSize);

        // 8. 记录已曝光(防止重复推荐)
        markAsExposed(userId, page);

        return buildResponse(page);
    }

    /**
     * 去重:同一帖子可能被多路召回
     */
    private List<FeedCandidate> dedup(List<FeedCandidate> candidates) {
        Map<Long, FeedCandidate> map = new LinkedHashMap<>();
        for (FeedCandidate c : candidates) {
            map.putIfAbsent(c.getPostId(), c);
        }
        return new ArrayList<>(map.values());
    }

    /**
     * 记录已曝光的帖子 ID
     */
    private void markAsExposed(Long userId, List<FeedCandidate> page) {
        String key = "feed:exposed:" + userId;
        for (FeedCandidate c : page) {
            redisTemplate.opsForSet().add(key, String.valueOf(c.getPostId()));
        }
        redisTemplate.expire(key, 24, TimeUnit.HOURS);
    }
}

4.2 排序服务(EdgeRank → 深度学习)

java 复制代码
/**
 * 排序服务 - 从 EdgeRank 到深度学习模型
 */
@Service
public class RankingService {

    @Autowired
    private FeatureStoreClient featureStore;
    @Autowired
    private ModelServingClient modelClient;

    /**
     * 粗排:使用轻量级双塔模型
     * 复杂度 O(N),快速筛选
     */
    public List<FeedCandidate> coarseRank(Long userId, List<FeedCandidate> candidates, int topK) {
        // 获取用户 Embedding
        float[] userEmbedding = featureStore.getUserEmbedding(userId);

        for (FeedCandidate candidate : candidates) {
            // 获取内容 Embedding
            float[] postEmbedding = featureStore.getPostEmbedding(candidate.getPostId());
            // 计算余弦相似度
            double similarity = cosineSimilarity(userEmbedding, postEmbedding);
            // 加入时间衰减因子
            double timeDecay = 1.0 / (1.0 + hoursAgo(candidate) * 0.1);
            candidate.setRelevanceScore(similarity * timeDecay);
        }

        // 取 Top-K
        candidates.sort((a, b) -> Double.compare(b.getRelevanceScore(), a.getRelevanceScore()));
        return candidates.subList(0, Math.min(topK, candidates.size()));
    }

    /**
     * 精排:使用深度学习多目标模型
     * 预测 P(like), P(comment), P(share), P(click)
     */
    public List<FeedCandidate> fineRank(Long userId, List<FeedCandidate> candidates) {
        // 批量提取特征
        List<Map<String, Double>> featureBatch = new ArrayList<>();
        for (FeedCandidate c : candidates) {
            Map<String, Double> features = extractFeatures(userId, c);
            c.setFeatureMap(features);
            featureBatch.add(features);
        }

        // 批量模型推理(调用 TensorFlow Serving)
        List<MultiObjectiveScore> scores = modelClient.batchPredict(featureBatch);

        // 加权融合多目标分数
        for (int i = 0; i < candidates.size(); i++) {
            MultiObjectiveScore s = scores.get(i);
            double finalScore = 
                0.3 * s.getPLike() +
                0.25 * s.getPComment() +
                0.25 * s.getPShare() +
                0.1 * s.getPClick() +
                0.1 * s.getPDwellTime();
            candidates.get(i).setFinalScore(finalScore);
        }

        candidates.sort((a, b) -> Double.compare(b.getFinalScore(), a.getFinalScore()));
        return candidates;
    }

    /**
     * 特征提取
     */
    private Map<String, Double> extractFeatures(Long userId, FeedCandidate candidate) {
        Map<String, Double> features = new HashMap<>();
        // 用户特征
        features.put("user_active_days", featureStore.getUserActiveDays(userId));
        features.put("user_avg_dwell_time", featureStore.getUserAvgDwellTime(userId));
        // 内容特征
        features.put("post_age_hours", (double) hoursAgo(candidate));
        features.put("post_like_count", featureStore.getPostLikeCount(candidate.getPostId()));
        features.put("post_comment_count", featureStore.getPostCommentCount(candidate.getPostId()));
        // 交叉特征
        features.put("affinity_score", featureStore.getAffinityScore(userId, candidate.getAuthorId()));
        features.put("content_type_pref", featureStore.getContentTypePref(userId, candidate.getContentType()));
        return features;
    }

    /**
     * 余弦相似度计算
     */
    private double cosineSimilarity(float[] a, float[] b) {
        double dotProduct = 0, normA = 0, normB = 0;
        for (int i = 0; i < a.length; i++) {
            dotProduct += a[i] * b[i];
            normA += a[i] * a[i];
            normB += b[i] * b[i];
        }
        return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB) + 1e-8);
    }
}

4.3 TAO 图存储引擎(社交关系查询)

java 复制代码
/**
 * TAO 客户端 - Facebook 自研图存储
 * 
 * 核心设计:
 * 1. Object(节点):User, Post, Comment, Page
 * 2. Association(边):Friend, Like, Comment, Follow
 * 3. 读优化:多级缓存,99% 缓存命中率
 */
@Service
public class TaoClient {

    private final Cache<String, List<Long>> associationCache;  // 本地缓存
    private final RedisTemplate<String, String> redisTemplate; // 分布式缓存
    private final TaoStorageClient storageClient;              // 底层存储

    public TaoClient(RedisTemplate<String, String> redisTemplate,
                     TaoStorageClient storageClient) {
        this.redisTemplate = redisTemplate;
        this.storageClient = storageClient;
        // 本地缓存:10 万条,5 分钟过期
        this.associationCache = Caffeine.newBuilder()
            .maximumSize(100_000)
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .build();
    }

    /**
     * 查询用户的关注列表
     * 三级缓存:本地 → Redis → 存储层
     */
    public List<Long> getFollowingIds(Long userId) {
        String cacheKey = "tao:following:" + userId;

        // L1: 本地缓存
        List<Long> result = associationCache.getIfPresent(cacheKey);
        if (result != null) return result;

        // L2: Redis 缓存
        String redisValue = redisTemplate.opsForValue().get(cacheKey);
        if (redisValue != null) {
            result = deserializeIds(redisValue);
            associationCache.put(cacheKey, result);
            return result;
        }

        // L3: 底层存储
        result = storageClient.queryAssociations(userId, EdgeType.FOLLOWING);
        redisTemplate.opsForValue().set(cacheKey, serializeIds(result), 30, TimeUnit.MINUTES);
        associationCache.put(cacheKey, result);
        return result;
    }

    /**
     * 查询两个用户之间的亲密度
     */
    public double getAffinityScore(Long userId1, Long userId2) {
        String key = "tao:affinity:" + Math.min(userId1, userId2) + ":" + Math.max(userId1, userId2);
        String cached = redisTemplate.opsForValue().get(key);
        if (cached != null) return Double.parseDouble(cached);

        // 基于互动频率计算亲密度
        long interactions = storageClient.countInteractions(userId1, userId2, 30); // 最近30天
        double score = Math.min(1.0, interactions / 100.0); // 归一化到 [0, 1]
        redisTemplate.opsForValue().set(key, String.valueOf(score), 1, TimeUnit.HOURS);
        return score;
    }
}

4.4 多样性重排算法

java 复制代码
/**
 * 多样性重排器
 * 
 * 问题:精排后可能连续出现同类型内容(如连续 5 个视频)
 * 方案:MMR (Maximal Marginal Relevance) 算法
 */
@Service
public class DiversityReranker {

    private static final double LAMBDA = 0.7; // 相关性 vs 多样性的权衡

    /**
     * MMR 重排:在相关性和多样性之间取平衡
     */
    public List<FeedCandidate> rerank(List<FeedCandidate> candidates) {
        if (candidates.size() <= 1) return candidates;

        List<FeedCandidate> result = new ArrayList<>();
        Set<Integer> selected = new HashSet<>();

        // 第一个选最高分的
        result.add(candidates.get(0));
        selected.add(0);

        while (result.size() < candidates.size()) {
            double bestMmrScore = Double.NEGATIVE_INFINITY;
            int bestIdx = -1;

            for (int i = 0; i < candidates.size(); i++) {
                if (selected.contains(i)) continue;

                FeedCandidate candidate = candidates.get(i);
                // 相关性分数
                double relevance = candidate.getFinalScore();
                // 与已选内容的最大相似度(惩罚重复)
                double maxSimilarity = 0;
                for (FeedCandidate sel : result) {
                    maxSimilarity = Math.max(maxSimilarity, contentSimilarity(candidate, sel));
                }
                // MMR 分数
                double mmrScore = LAMBDA * relevance - (1 - LAMBDA) * maxSimilarity;

                if (mmrScore > bestMmrScore) {
                    bestMmrScore = mmrScore;
                    bestIdx = i;
                }
            }

            if (bestIdx >= 0) {
                result.add(candidates.get(bestIdx));
                selected.add(bestIdx);
            }
        }
        return result;
    }

    /**
     * 内容相似度:基于类型、作者、话题
     */
    private double contentSimilarity(FeedCandidate a, FeedCandidate b) {
        double sim = 0;
        if (a.getContentType() == b.getContentType()) sim += 0.4;
        if (a.getAuthorId().equals(b.getAuthorId())) sim += 0.4;
        // 话题重叠度
        sim += 0.2 * topicOverlap(a, b);
        return sim;
    }
}

4.5 实时特征工程

java 复制代码
/**
 * 实时特征计算服务
 * 
 * 使用 Flink 实时流处理用户行为,更新特征
 */
@Service
public class RealtimeFeatureService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 处理用户行为事件(Flink 调用)
     */
    public void processUserAction(UserActionEvent event) {
        Long userId = event.getUserId();
        Long postId = event.getPostId();
        Long authorId = event.getAuthorId();

        switch (event.getActionType()) {
            case LIKE:
                // 更新帖子点赞计数
                redisTemplate.opsForValue().increment("feature:post:likes:" + postId);
                // 更新用户-作者亲密度
                redisTemplate.opsForValue().increment("feature:affinity:" + userId + ":" + authorId);
                // 更新用户兴趣标签
                updateUserInterestTags(userId, postId, 1.0);
                break;

            case VIEW:
                // 更新帖子浏览计数
                redisTemplate.opsForValue().increment("feature:post:views:" + postId);
                // 更新用户停留时长特征
                double dwellTime = event.getDwellTime();
                updateDwellTimeFeature(userId, dwellTime);
                break;

            case COMMENT:
                redisTemplate.opsForValue().increment("feature:post:comments:" + postId);
                redisTemplate.opsForValue().increment("feature:affinity:" + userId + ":" + authorId, 2);
                updateUserInterestTags(userId, postId, 2.0);
                break;

            case SHARE:
                redisTemplate.opsForValue().increment("feature:post:shares:" + postId);
                updateUserInterestTags(userId, postId, 3.0);
                break;

            case HIDE:
                // 负反馈:降低相关内容权重
                updateUserInterestTags(userId, postId, -5.0);
                break;
        }
    }

    /**
     * 更新用户兴趣标签向量
     */
    private void updateUserInterestTags(Long userId, Long postId, double weight) {
        String key = "feature:user:interests:" + userId;
        // 获取帖子标签
        List<String> tags = getPostTags(postId);
        for (String tag : tags) {
            redisTemplate.opsForZSet().incrementScore(key, tag, weight);
        }
        // 保留 Top-100 兴趣标签
        Long size = redisTemplate.opsForZSet().size(key);
        if (size != null && size > 100) {
            redisTemplate.opsForZSet().removeRange(key, 0, size - 101);
        }
    }
}

五、推拉混合模型

5.1 策略选择

复制代码
┌─────────────────────────────────────────────────────┐
│              推拉混合策略                              │
│                                                       │
│  普通用户(粉丝 < 1000):                            │
│    → 推模式:发布时写入粉丝的 Feed 收件箱              │
│    → 优点:读取快,直接取收件箱                        │
│                                                       │
│  大V用户(粉丝 > 10万):                             │
│    → 拉模式:读取时实时聚合                            │
│    → 原因:推模式写扩散太大(1条→100万次写入)         │
│                                                       │
│  Facebook 的选择:                                    │
│    → 主要用拉模式(因为有复杂排序算法)                │
│    → 预计算优化:用户打开 App 时预先触发计算           │
└─────────────────────────────────────────────────────┘

5.2 预计算服务

java 复制代码
/**
 * Feed 预计算服务
 * 
 * 在用户即将刷新 Feed 时提前计算
 * 触发时机:App 启动、推送唤醒、定时刷新
 */
@Service
public class FeedPrefetchService {

    @Autowired
    private FeedAggregatorService feedAggregator;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final String PREFETCH_KEY = "feed:prefetch:";
    private static final int PREFETCH_SIZE = 50;

    /**
     * 预计算用户 Feed 并缓存
     */
    public void prefetch(Long userId) {
        String key = PREFETCH_KEY + userId;
        // 检查是否已有有效缓存
        if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
            Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS);
            if (ttl != null && ttl > 120) return; // 缓存还有 2 分钟以上,跳过
        }

        // 异步计算 Feed
        CompletableFuture.runAsync(() -> {
            FeedResponse response = feedAggregator.getFeed(userId, null, PREFETCH_SIZE);
            // 缓存结果,5 分钟过期
            String json = JsonUtils.toJson(response);
            redisTemplate.opsForValue().set(key, json, 5, TimeUnit.MINUTES);
        });
    }

    /**
     * 获取预计算的 Feed(如果有)
     */
    public FeedResponse getPrefetchedFeed(Long userId) {
        String key = PREFETCH_KEY + userId;
        String json = redisTemplate.opsForValue().get(key);
        if (json != null) {
            redisTemplate.delete(key); // 使用后删除
            return JsonUtils.fromJson(json, FeedResponse.class);
        }
        return null; // 没有预计算结果,走实时计算
    }
}

六、性能优化

6.1 优化策略总览

优化点 方案 效果
召回延迟 多路并行召回 + 超时降级 召回 P99 < 50ms
排序延迟 粗排+精排两阶段 排序 P99 < 30ms
缓存命中 TAO 三级缓存 99% 命中率
预计算 App 启动时预加载 首屏 < 200ms
特征计算 Flink 实时流 + 离线批量 特征延迟 < 1s
模型推理 GPU 推理 + 批量请求 单次推理 < 10ms
多样性 MMR 重排 用户停留时长 +15%

6.2 降级策略

java 复制代码
/**
 * Feed 降级策略
 */
@Service
public class FeedDegradationService {

    /**
     * 根据系统负载选择降级策略
     */
    public FeedResponse getFeedWithDegradation(Long userId, int pageSize) {
        SystemLoad load = getSystemLoad();

        if (load == SystemLoad.NORMAL) {
            // 正常:完整的召回+精排流程
            return feedAggregator.getFeed(userId, null, pageSize);
        } else if (load == SystemLoad.HIGH) {
            // 高负载:跳过精排,只用粗排
            return feedAggregator.getFeedCoarseOnly(userId, null, pageSize);
        } else {
            // 极端:返回预计算缓存或热门内容
            FeedResponse cached = prefetchService.getPrefetchedFeed(userId);
            if (cached != null) return cached;
            return feedAggregator.getFallbackTrending(pageSize);
        }
    }
}

七、监控指标

指标类别 具体指标 告警阈值
业务指标 Feed CTR(点击率) < 5%
业务指标 用户平均停留时长 < 10 分钟
业务指标 互动率(点赞+评论+分享) < 3%
性能指标 Feed 加载 P99 延迟 > 500ms
性能指标 排序模型推理延迟 > 50ms
性能指标 召回服务 QPS > 500 万
系统指标 TAO 缓存命中率 < 95%
系统指标 Redis 内存使用率 > 80%
系统指标 特征更新延迟 > 5s

八、方案对比

维度 纯推模式 纯拉模式 推拉混合(Facebook)
写放大 高(大V问题)
读延迟 极低 中(预计算优化)
排序灵活性 低(预排序) 高(实时排序)
实时性
存储成本 高(每人一份)
适用场景 Twitter 搜索引擎 Facebook/Instagram

九、高频面试问题

Q1:为什么 Facebook 选择拉模式而不是推模式?

:因为 Facebook 的核心是排序(Ranking),不是简单的时间线。推模式预先计算好的顺序在用户刷新时可能已经过时(权重变化、新内容出现)。拉模式允许每次请求时实时计算最优排序。通过预计算和缓存来弥补读取延迟。

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

:新用户没有行为数据时:(1) 基于注册信息(年龄、地区、兴趣标签)推荐热门内容;(2) 引导用户关注好友和 Page;(3) 使用 Explore 推荐(基于全局热门);(4) 快速学习:前几次互动后立即更新推荐模型。

Q3:如何处理信息茧房?

:(1) 多样性重排(MMR 算法)确保内容类型多样;(2) 随机探索:一定比例的 Feed 位置留给非个性化内容;(3) 引入"值得信赖的来源"权重;(4) 用户可手动调整 Feed 偏好。

Q4:TAO 的缓存一致性如何保证?

:TAO 采用最终一致性模型。写操作先更新主库,然后异步失效缓存。对于社交关系这种读多写少的场景,短暂的不一致是可接受的。关键操作(如取消关注)会同步失效缓存。

Q5:排序模型如何在线更新?

:(1) 离线训练:每天用前一天的数据重新训练模型;(2) 在线学习:使用增量学习更新模型参数;(3) A/B 测试:新模型先在小流量上验证,指标提升后全量上线;(4) 特征实时更新:通过 Flink 流处理实时更新用户特征。


十、架构设计检查清单

检查项 状态
多路召回并行执行
粗排+精排两阶段排序
MMR 多样性重排
TAO 三级缓存(本地→Redis→存储)
实时特征工程(Flink)
Feed 预计算
推拉混合模型
降级策略(高负载/极端)
已曝光去重
冷启动处理
监控告警体系

核心权衡:在一致性 vs 可用性的 Trade-off 中,本系统选择核心路径强一致、非核心路径最终一致的混合策略。这是 CP vs AP 取舍的工程实践。
容灾策略:节点宕机时自动故障转移;下游超时触发熔断降级;数据不一致通过补偿任务回滚修复;定期备份保证灾难恢复。
💡 MVP 策略:先用单机版验证核心功能,再逐步演进到分布式生产级架构。

相关推荐
拾贰_C1 小时前
【OpenClaw | openai | QQ】 配置QQ qot机器人
运维·人工智能·ubuntu·面试·prompt
空中海1 小时前
Spring Boot 专家级面试题库
spring boot·后端·面试
AI人工智能+电脑小能手5 小时前
【大白话说Java面试题】【Java基础篇】第20题:HashMap在计算index的时候,为什么要对数组长度做减1操作
java·开发语言·数据结构·后端·面试·哈希算法·hash-index
逻辑驱动的ken5 小时前
Java高频面试考点场景题17
开发语言·jvm·面试·求职招聘·春招
Fuly10245 小时前
java面试知识点复习
java·开发语言·面试
小程故事多_805 小时前
[大模型面试系列] 破解 Agent 软故障困局,四层防御 + 可观测性,筑牢生产级稳健性防线
人工智能·面试·职场和发展·智能体
嵌入式小企鹅6 小时前
嵌入式面试宝典
学习·面试·嵌入式·嵌入式工程师·高薪offer
许彰午8 小时前
CacheSQL:一个面向政务系统的内存缓存数据库中间件
java·数据库·缓存·中间件·面试·开源软件·政务
不会敲代码18 小时前
从 URL 到页面展示,还有哪些你忽略的底层细节?(DNS 与传输篇)
前端·面试