每天一道面试题|微博千万级推送架构设计揭秘

59 阅读5分钟

面试官:"如果让你设计微博的feed流推送系统,支持千万粉丝大V的实时推送,你会如何设计架构?"

微博、抖音等社交平台的feed流推送是系统架构中最具挑战性的场景之一,既要保证实时性,又要应对海量数据和高并发访问。今天我们就来深入解析这个经典面试题。

一、核心问题:为什么这么难?

1. 数据规模爆炸式增长

  • 一个大V可能有5000万粉丝,一条微博需要推送给5000万人
  • 每日新增内容量可达亿级别,存储和推送压力巨大
  • 用户关系网络复杂,关注关系变化频繁

2. 实时性要求极高

  • 用户期望发布后秒级看到新内容
  • 热点事件时流量瞬间暴涨,系统要有弹性
  • 移动端刷新频繁,API响应必须在毫秒级

3. 个性化与一致性平衡

  • 不同用户看到不同的内容流
  • 需要支持智能排序、广告插入等业务需求
  • 保证跨设备、跨平台的内容一致性

二、架构设计方案:推拉结合的精妙平衡

2.1 三种基础模式对比

推模式(Push Model)

// 推模式核心实现:异步推送任务
@Service
public class PushService {
    
    @Autowired
    private UserRelationService userRelationService;
    
    @Autowired
    private FeedStorage feedStorage;
    
    @Async("pushTaskExecutor")
    public void pushPostToFollowers(Long authorId, Post post) {
        // 获取作者的所有粉丝
        List<Long> followerIds = userRelationService.getFollowers(authorId);
        
        // 分批异步推送到粉丝的收件箱
        int batchSize = 1000;
        for (int i = 0; i < followerIds.size(); i += batchSize) {
            List<Long> batch = followerIds.subList(i, 
                Math.min(i + batchSize, followerIds.size()));
            
            // 异步处理每个批次
            processBatch(batch, post);
        }
    }
    
    @Async
    public void processBatch(List<Long> followerIds, Post post) {
        for (Long followerId : followerIds) {
            // 推送到每个用户的个人时间线
            feedStorage.pushToTimeline(followerId, post);
        }
    }
}

优点

  • 读取性能极佳:用户直接读取本地时间线
  • 实时性好:发布后立即推送
  • 适合活跃用户:在线用户能及时收到更新

缺点

  • 写入压力大:大V发布时产生海量写入操作
  • 存储成本高:每个粉丝都需要存储副本
  • 数据冗余:浪费存储空间

拉模式(Pull Model)

// 拉模式核心实现:动态聚合查询
@Service
public class PullService {
    
    @Autowired
    private UserRelationService userRelationService;
    
    @Autowired
    private PostService postService;
    
    public List<Post> getUserFeed(Long userId, int page, int size) {
        // 获取用户关注列表
        List<Long> followingIds = userRelationService.getFollowings(userId);
        
        // 聚合查询关注用户的最新动态
        return postService.getLatestPosts(followingIds, page, size);
    }
}

优点

  • 写入简单:发布时只需写入一次
  • 存储节省:没有数据冗余
  • 适合不活跃用户:冷数据不会占用资源

缺点

  • 读取性能差:需要实时聚合多个数据源
  • 扩展性差:关注数多时查询缓慢
  • 实时性难保证:聚合需要时间

2.2 混合模式:智能推拉结合

// 智能推拉结合实现
@Service
public class HybridFeedService {
    
    @Autowired
    private PushService pushService;
    
    @Autowired
    private PullService pullService;
    
    @Autowired
    private UserStatusService userStatusService;
    
    /**
     * 处理新内容发布
     */
    public void handleNewPost(Long authorId, Post post) {
        // 1. 获取粉丝列表
        List<Long> followerIds = userRelationService.getFollowers(authorId);
        
        // 2. 分离在线和离线用户
        Map<Boolean, List<Long>> partitioned = followerIds.stream()
            .collect(Collectors.partitioningBy(
                userId -> userStatusService.isUserOnline(userId)
            ));
        
        List<Long> onlineUsers = partitioned.get(true);
        List<Long> offlineUsers = partitioned.get(false);
        
        // 3. 对在线用户使用推模式
        if (!onlineUsers.isEmpty()) {
            pushService.pushToUsers(onlineUsers, post);
        }
        
        // 4. 对离线用户使用拉模式(标记需要更新)
        offlineUsers.forEach(userId -> 
            userService.markNeedUpdate(userId)
        );
    }
    
    /**
     * 用户获取feed流
     */
    public List<Post> getHybridFeed(Long userId, int page, int size) {
        // 1. 先获取推模式存储的内容
        List<Post> pushedPosts = feedStorage.getPushedPosts(userId, page, size);
        
        // 2. 如果推模式内容不足,补充拉取内容
        if (pushedPosts.size() < size) {
            List<Post> pulledPosts = pullService.getLatestPosts(
                userRelationService.getFollowings(userId), 
                0, size - pushedPosts.size()
            );
            pushedPosts.addAll(pulledPosts);
        }
        
        // 3. 智能排序、去重、广告插入等
        return smartSort(pushedPosts);
    }
}

2.3 冷热数据分离策略

// 冷热数据分离实现
@Service
public class TieredStorageService {
    
    // 热数据:最近7天活跃用户的数据
    @Cacheable(value = "hotFeed", key = "#userId")
    public List<Post> getHotFeed(Long userId) {
        return getFeedFromRedis(userId);
    }
    
    // 冷数据:7天前的数据
    public List<Post> getColdFeed(Long userId, Date startDate, Date endDate) {
        return getFeedFromHBase(userId, startDate, endDate);
    }
    
    // 用户活跃度判断
    public boolean isActiveUser(Long userId) {
        Long lastLogin = userStatusService.getLastLoginTime(userId);
        return System.currentTimeMillis() - lastLogin < 7 * 24 * 3600 * 1000;
    }
}

三、架构优化与扩展

1. 分级推送策略

  • 明星用户:异步队列+批量推送
  • 普通用户:实时推送
  • 僵尸粉丝:延迟推送或仅标记

2. 智能缓存设计

// 多级缓存策略
public class MultiLevelCache {
    // 本地缓存:Guava Cache,存储热点数据
    // Redis集群:存储用户时间线数据
    // HBase/数据库:存储历史数据
}

3. 流量削峰设计

  • 消息队列缓冲写入压力
  • 限流降级保护核心服务
  • 自动扩缩容应对流量高峰

四、总结回顾

微博推送架构的核心设计哲学:

内容发布
→ 用户分组:[在线用户] vs [离线用户] vs [僵尸用户]
→ 推送策略:推模式(实时) + 拉模式(延迟) + 混合模式(智能)
→ 数据存储:热数据(内存) + 冷数据(磁盘) + 分层存储(成本优化)
→ 性能优化:缓存策略 + 异步处理 + 批量操作

五、面试建议

回答技巧:

  1. 先分析问题本质:数据量大、实时性要求高、读写比例失衡
  2. 对比方案优劣:详细说明推、拉模式的适用场景和限制
  3. 提出混合方案:推拉结合,根据用户状态智能选择
  4. 强调优化措施:冷热分离、缓存策略、异步处理等
  5. 考虑扩展性:如何支持更大规模、更多业务场景

加分回答点:

  • 提到具体的技术选型:Redis、Kafka、HBase等
  • 讨论数据一致性和可靠性保障
  • 考虑监控、告警、降级等运维层面
  • 提出AB测试和灰度发布方案

本文由微信公众号"程序员小胖"整理发布,转载请注明出处

明日面试题预告:大文件有限内存排序?