Feed流推拉模式对比

三种 Feed 流架构的选择困境

2023年,我们产品经理提出了三个需求:

需求 1:微博式 Feed(用户看到关注的人的动态)
需求 2:朋友圈式 Feed(用户看到好友的动态)
需求 3:抖音式 Feed(推荐算法生成的个性化内容)

这三种 Feed 背后是三种完全不同的架构:推模式、拉模式、混合模式

选错了架构,轻则用户体验差,重则系统崩溃。


二、推模式(Push / Write Fanout)🔴

2.1 原理

用户 A 发了一条动态:

用户 A ──发布──▶ 数据库

       └───推送到──▶ 粉丝的收件箱

        ┌────────────┼────────────┐
        ▼            ▼            ▼
    粉丝1收件箱  粉丝2收件箱  粉丝3收件箱

2.2 代码实现

@Service
class PushFeedService {
    @Autowired
    private FeedDao feedDao;
    @Autowired
    private FollowService followService;
    @Autowired
    private RocketMQTemplate mqTemplate;

    public void publish(Post post) {
        // 1. 保存帖子
        postDao.save(post);

        // 2. 异步推送给粉丝(MQ 解耦)
        mqTemplate.asyncSend("feed:push:topic",
            new PushMessage(post.getUserId(), post.getId()),
            new SendCallback() {
                @Override
                public void onSuccess(SendResult result) { }

                @Override
                public void onException(Throwable e) {
                    // 重试逻辑
                }
            }
        );
    }
}

@RocketMQListener(topic = "feed:push:topic")
class PushConsumer {
    @Autowired
    private FeedDao feedDao;
    @Autowired
    private FollowService followService;

    public void handle(PushMessage message) {
        // 查询所有粉丝
        List<Long> followers = followService.getFollowers(message.getUserId());

        // 批量写入粉丝收件箱
        for (Long followerId : followers) {
            feedDao.pushToInbox(followerId, message.getPostId());
        }
    }
}

2.3 优缺点

优点缺点
读取极快(O(1))写入慢(大V发一条要写 millions 个收件箱)
用户体验好存储成本高
适合粉丝数少的情况大V问题

三、拉模式(Pull / Read Fanout)🔴

3.1 原理

用户 A 刷新 Feed:

请求 ──▶ 拉取关注列表(1万用户)
       ──▶ 分别拉取每个用户的最新动态
       ──▶ 合并排序
       ──▶ 返回

3.2 代码实现

@Service
class PullFeedService {
    @Autowired
    private FollowService followService;
    @Autowired
    private PostDao postDao;

    public List<Post> getFeed(Long userId, int offset, int limit) {
        // 1. 获取关注列表
        List<Long> following = followService.getFollowing(userId);

        // 2. 并行拉取关注的人的动态
        ExecutorService executor = Executors.newFixedThreadPool(10);
        List<Future<List<Post>>> futures = following.stream()
            .map(followerId ->
                executor.submit(() -> postDao.getRecentPosts(followerId, 100))
            )
            .collect(Collectors.toList());

        // 3. 合并结果
        List<Post> allPosts = futures.stream()
            .flatMap(f -> {
                try {
                    return f.get().stream();
                } catch (Exception e) {
                    return Stream.empty();
                }
            })
            .sorted(Comparator.comparing(Post::getCreatedAt).reversed())
            .skip(offset)
            .limit(limit)
            .collect(Collectors.toList());

        return allPosts;
    }
}

3.3 优缺点

优点缺点
写入极快(只写一条)读取慢(N 次查询)
存储成本低无法做到秒开
大V无压力用户列表大时查询慢

四、混合模式(Hybrid)🟡

4.1 核心思想

推拉结合:
- 普通用户:推模式(粉丝少,写入可接受)
- 大V用户:拉模式(粉丝多,写入成本高)

临界点:粉丝数 > 10000 → 切换为拉模式

4.2 实现

@Service
class HybridFeedService {
    private static final long PUSH_THRESHOLD = 10_000;

    public void publish(Post post) {
        // 1. 保存帖子
        postDao.save(post);

        // 2. 判断是否大V
        long followerCount = followService.getFollowerCount(post.getUserId());

        if (followerCount < PUSH_THRESHOLD) {
            // 普通用户:推模式
            pushToFollowers(post);
        } else {
            // 大V:只记录到帖子表,读取时拉取
            recordToPostTable(post);
        }
    }

    public List<Post> getFeed(Long userId, int offset, int limit) {
        // 1. 从收件箱获取(推模式的内容)
        List<Post> inboxPosts = feedDao.getInbox(userId, offset, limit);

        // 2. 拉取大V的最新动态
        List<Long> bigVs = followService.getBigVFollowing(userId);
        List<Post> bigVPosts = pullFromBigVs(bigVs, offset, limit);

        // 3. 合并去重
        return mergeAndDedupe(inboxPosts, bigVPosts);
    }
}

五、模式对比决策表🟡

场景推荐模式原因
粉丝数少(< 1000)推模式写入成本可接受,读取快
粉丝数中等(1000~10000)混合模式普通用户推,大V拉
粉丝数多(> 10000)混合模式或拉模式写放大严重
微博式(关注驱动)推/混合模式内容由关注决定
抖音式(推荐驱动)拉模式内容由算法决定,无法预推

【架构权衡】 没有最好的架构,只有最适合的架构。微博选择推模式是因为用户发帖频率低、但读取频率高;抖音选择推荐模式是因为内容由算法生成、无法预推。


六、面试总结

级别期望回答
P5能说出推拉模式的基本原理
P6能分析两种模式的优缺点
P7能根据场景选择合适的模式