Skip to content

信息流Feed系统架构设计

Feed流系统概述

Feed流是社交和内容平台的核心功能,用户看到的朋友动态、关注者更新、推荐内容等都属于Feed流的范畴。

Feed流的典型形态

mermaid
graph TD
    A["Feed流类型"] --> B["时间线Timeline"]
    A --> C["智能推荐"]
    A --> D["混合模式"]
    
    B --> B1["朋友圈动态"]
    B --> B2["关注者更新"]
    B --> B3["订阅频道"]
    
    C --> C1["首页推荐"]
    C --> C2["个性化内容"]
    C --> C3["热门排行"]
    
    D --> D1["推荐+关注结合"]
    D --> D2["时间+热度权重"]
    
    style A fill:#4A90E2,color:#fff,rx:10,ry:10
    style B fill:#27AE60,color:#fff,rx:10,ry:10
    style C fill:#E74C3C,color:#fff,rx:10,ry:10
    style D fill:#9B59B6,color:#fff,rx:10,ry:10

时间线模式:按时间顺序展示内容,实现简单,适用于好友社交场景,用户更关注"谁发的"而非"发了什么"。

智能推荐模式:基于用户兴趣和行为进行个性化推荐,需要依赖推荐算法,但存在"信息茧房"问题。

混合模式:结合时间线和推荐的优点,既保证时效性,又有一定的个性化,是目前主流的方案。

核心技术挑战

挑战维度具体要求技术难点
实时性发布后秒级可见数据同步延迟控制
高并发几十万QPS读写压力分离
性能100ms内响应多源数据聚合
准确性不丢不重分布式一致性

三种推送模式对比

推模式(Push)

当用户发布内容时,主动将内容推送给所有粉丝的收件箱。

mermaid
sequenceDiagram
    participant Author as 内容发布者
    participant Service as Feed服务
    participant Outbox as 发件箱
    participant Inbox as 粉丝收件箱
    
    Author->>Service: 发布新内容
    Service->>Outbox: 写入发件箱
    
    loop 遍历粉丝
        Service->>Inbox: 写入粉丝1收件箱
        Service->>Inbox: 写入粉丝2收件箱
        Service->>Inbox: 写入粉丝N收件箱
    end
    
    Note over Inbox: 粉丝读取时直接查收件箱

数据模型

sql
-- 发件箱:记录用户发布的内容
CREATE TABLE outbox (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    content_id BIGINT NOT NULL,
    create_time DATETIME NOT NULL,
    INDEX idx_user_time(user_id, create_time DESC)
);

-- 收件箱:每个用户的feed流
CREATE TABLE inbox (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL COMMENT '收件箱所属用户',
    content_id BIGINT NOT NULL,
    author_id BIGINT NOT NULL,
    create_time DATETIME NOT NULL,
    INDEX idx_user_time(user_id, create_time DESC)
);

优势

  • 读取性能高,直接查收件箱即可
  • 实现逻辑简单

劣势

  • 写扩散严重,大V发一条内容需要写入百万粉丝的收件箱
  • 存储成本高

拉模式(Pull)

用户读取Feed时,实时从关注的人那里拉取内容并聚合。

mermaid
sequenceDiagram
    participant Reader as 内容读者
    participant Service as Feed服务
    participant Follow as 关注关系
    participant Outbox as 发件箱
    
    Reader->>Service: 请求Feed流
    Service->>Follow: 获取关注列表
    Follow-->>Service: 返回关注的用户ID
    
    loop 遍历关注用户
        Service->>Outbox: 查询用户1最新内容
        Service->>Outbox: 查询用户2最新内容
        Service->>Outbox: 查询用户N最新内容
    end
    
    Service->>Service: 聚合排序
    Service-->>Reader: 返回Feed列表

优势

  • 存储成本低,只存发件箱
  • 发布时写入操作简单

劣势

  • 读取时需要聚合多个用户的内容,性能差
  • 关注人数多时延迟高

推拉结合模式

针对不同用户采用不同策略,是实际生产环境的最佳选择:

mermaid
graph TD
    A["内容发布"] --> B{"发布者类型判断"}
    
    B -->|普通用户| C["推模式"]
    B -->|大V用户| D["拉模式"]
    
    C --> E["推送给所有粉丝"]
    D --> F["仅写入发件箱"]
    
    G["用户读取Feed"] --> H{"粉丝类型判断"}
    H -->|活跃用户| I["直接读收件箱"]
    H -->|不活跃用户| J["实时拉取聚合"]
    
    style A fill:#4A90E2,color:#fff,rx:10,ry:10
    style C fill:#27AE60,color:#fff,rx:10,ry:10
    style D fill:#E67E22,color:#fff,rx:10,ry:10
    style I fill:#27AE60,color:#fff,rx:10,ry:10
    style J fill:#E67E22,color:#fff,rx:10,ry:10

策略细节

  • 大V定义:粉丝数超过10万的用户
  • 活跃用户定义:7天内有登录行为的用户
  • 大V发内容时,仅推送给活跃粉丝
  • 不活跃用户登录时,再补充拉取

存储架构设计

多级缓存方案

Feed流是典型的读多写少场景,适合采用多级缓存:

mermaid
graph TD
    A["用户请求"] --> B{"本地缓存"}
    B -->|命中| C["直接返回"]
    B -->|未命中| D{"Redis缓存"}
    D -->|命中| E["回填本地缓存"]
    D -->|未命中| F["数据库查询"]
    F --> G["构建Feed数据"]
    G --> H["回填Redis"]
    H --> E
    E --> C
    
    style A fill:#4A90E2,color:#fff,rx:10,ry:10
    style B fill:#E67E22,color:#fff,rx:10,ry:10
    style D fill:#9B59B6,color:#fff,rx:10,ry:10
    style F fill:#27AE60,color:#fff,rx:10,ry:10

Redis数据结构选择

使用Sorted Set存储用户的Feed流:

java
@Service
public class FeedCacheService {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private static final String FEED_KEY_PREFIX = "feed:inbox:";
    private static final int MAX_FEED_SIZE = 1000;  // 每个用户最多缓存1000条
    
    /**
     * 向用户收件箱推送内容
     */
    public void pushToInbox(Long userId, Long contentId, long timestamp) {
        String key = FEED_KEY_PREFIX + userId;
        
        // 使用发布时间作为score
        redisTemplate.opsForZSet().add(key, 
            contentId.toString(), 
            timestamp);
        
        // 控制收件箱大小
        Long size = redisTemplate.opsForZSet().size(key);
        if (size != null && size > MAX_FEED_SIZE) {
            // 删除最旧的内容
            redisTemplate.opsForZSet().removeRange(key, 0, size - MAX_FEED_SIZE - 1);
        }
    }
    
    /**
     * 获取用户Feed流
     */
    public List<Long> getFeed(Long userId, long maxScore, int count) {
        String key = FEED_KEY_PREFIX + userId;
        
        // 按score倒序获取,支持滚动分页
        Set<String> contentIds = redisTemplate.opsForZSet()
            .reverseRangeByScore(key, 0, maxScore, 0, count);
        
        if (contentIds == null || contentIds.isEmpty()) {
            return Collections.emptyList();
        }
        
        return contentIds.stream()
            .map(Long::parseLong)
            .collect(Collectors.toList());
    }
}

数据库分库分表

当数据量超过单库承载能力时,需要进行分库分表:

mermaid
graph TD
    A["inbox表"] --> B["按user_id分片"]
    
    B --> C["分库0"]
    B --> D["分库1"]
    B --> E["分库..."]
    B --> F["分库N"]
    
    C --> C1["inbox_0"]
    C --> C2["inbox_1"]
    D --> D1["inbox_0"]
    D --> D2["inbox_1"]
    
    style A fill:#4A90E2,color:#fff,rx:10,ry:10
    style B fill:#9B59B6,color:#fff,rx:10,ry:10
    style C fill:#27AE60,color:#fff,rx:10,ry:10
    style D fill:#27AE60,color:#fff,rx:10,ry:10

分片策略

  • 按用户ID哈希分库,保证同一用户数据在同一库
  • 分表数量根据数据增长速度规划
  • 历史数据定期归档到冷存储

实时性优化

异步推送流程

mermaid
sequenceDiagram
    participant Author as 发布者
    participant API as API服务
    participant MQ as 消息队列
    participant Push as 推送服务
    participant Cache as Redis
    
    Author->>API: 发布内容
    API->>API: 写入内容表
    API-->>Author: 返回成功
    
    API->>MQ: 发送推送消息
    
    MQ->>Push: 消费消息
    Push->>Push: 获取活跃粉丝列表
    
    loop 批量推送
        Push->>Cache: 写入粉丝Feed缓存
    end

批量写入优化

java
@Service
public class FeedPushService {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    /**
     * 批量推送到粉丝收件箱
     */
    public void batchPushToFollowers(Long contentId, 
                                      long timestamp,
                                      List<Long> followerIds) {
        // 使用Pipeline批量写入
        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (Long followerId : followerIds) {
                String key = "feed:inbox:" + followerId;
                connection.zSetCommands().zAdd(
                    key.getBytes(),
                    timestamp,
                    contentId.toString().getBytes());
            }
            return null;
        });
    }
    
    /**
     * 异步分批推送
     */
    @Async
    public void asyncPushToFollowers(Long authorId, Long contentId, long timestamp) {
        int pageSize = 1000;
        int page = 0;
        
        while (true) {
            // 分页获取活跃粉丝
            List<Long> followers = followerService.getActiveFollowers(
                authorId, page * pageSize, pageSize);
            
            if (followers.isEmpty()) {
                break;
            }
            
            // 批量推送
            batchPushToFollowers(contentId, timestamp, followers);
            page++;
        }
    }
}

推荐系统集成

推荐召回与排序

mermaid
graph LR
    subgraph 召回阶段
        A["协同过滤召回"]
        B["内容相似召回"]
        C["热门召回"]
        D["关注召回"]
    end
    
    subgraph 融合排序
        E["特征提取"]
        F["排序模型"]
        G["重排序"]
    end
    
    subgraph 展示
        H["Feed列表"]
    end
    
    A --> E
    B --> E
    C --> E
    D --> E
    E --> F
    F --> G
    G --> H
    
    style A fill:#4A90E2,color:#fff,rx:10,ry:10
    style B fill:#4A90E2,color:#fff,rx:10,ry:10
    style C fill:#4A90E2,color:#fff,rx:10,ry:10
    style D fill:#4A90E2,color:#fff,rx:10,ry:10
    style F fill:#9B59B6,color:#fff,rx:10,ry:10
    style H fill:#27AE60,color:#fff,rx:10,ry:10

混合策略实现

java
@Service
public class HybridFeedService {
    
    /**
     * 获取混合Feed流
     * 70%关注内容 + 30%推荐内容
     */
    public List<FeedItem> getHybridFeed(Long userId, int count) {
        int followCount = (int) (count * 0.7);
        int recommendCount = count - followCount;
        
        // 获取关注者内容
        List<FeedItem> followFeed = getFollowFeed(userId, followCount);
        
        // 获取推荐内容
        List<FeedItem> recommendFeed = getRecommendFeed(userId, recommendCount);
        
        // 合并并去重
        List<FeedItem> result = new ArrayList<>();
        Set<Long> contentIds = new HashSet<>();
        
        // 交叉插入
        int i = 0, j = 0;
        while (result.size() < count) {
            if (i < followFeed.size() && (j >= recommendFeed.size() || 
                result.size() % 3 != 2)) {
                FeedItem item = followFeed.get(i++);
                if (contentIds.add(item.getContentId())) {
                    result.add(item);
                }
            } else if (j < recommendFeed.size()) {
                FeedItem item = recommendFeed.get(j++);
                if (contentIds.add(item.getContentId())) {
                    result.add(item);
                }
            } else {
                break;
            }
        }
        
        return result;
    }
}

更新: 2025-12-06 17:31:22
原文: https://www.yuque.com/u22210564/zoxfmt/sw6y7dft5a6w9rtf

Java 后端面试知识库