🚀 系统设计面试:设计 Facebook 新闻动态(News Feed)
摘要:Facebook News Feed 是全球最大规模的个性化推荐系统之一,日活 20 亿用户,每人关注数百个对象。本文深入剖析 Feed 系统的核心架构,包括 EdgeRank 到深度学习排序的演进、TAO 图存储引擎、推拉混合模型、实时特征工程,并提供完整的 Java 实现。
🎯 场景引入
你打开 Facebook,首页 News Feed 里混合着朋友动态、广告、推荐内容。这个 Feed 是怎么从数千条候选中筛选出来的?
核心挑战:
- 多源聚合:朋友动态、群组帖子、广告、推荐,如何统一排序?
- EdgeRank 算法:亲密度 × 权重 × 时效性,如何实时计算?
- 隐私控制:不同帖子对不同人可见,如何高效过滤?
一、问题背景
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问题) | 无 | 低 |
| 读延迟 | 极低 | 高 | 中(预计算优化) |
| 排序灵活性 | 低(预排序) | 高(实时排序) | 高 |
| 实时性 | 高 | 中 | 高 |
| 存储成本 | 高(每人一份) | 低 | 中 |
| 适用场景 | 搜索引擎 | 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 策略:先用单机版验证核心功能,再逐步演进到分布式生产级架构。