Feed 流

Posted by Run-dream Blog on October 24, 2025

背景

Feed 流在 游戏内 UGC 内容社区(朋友圈,关卡)中有着广泛应用,比如首页推荐、关注页、话题页等。本文结合我们在 UGC 系统中的实践,说明不同架构模式与实现要点。

image

Feed 是指状态或消息,Feed 流是一个持续更新、并按照特定排序策略向用户展示的个性化内容序列。

  • 基于时间:全局时间线与关注创作者时间线。
  • 基于热度/权重:按互动数据(点赞、评论、转发等)计算热度。
  • 基于兴趣:基于用户历史行为的召回与重排。

本文聚焦“基于关注的单向关系(关注创作者)”的时间线实现。

功能范围:

  • 发布作品:创作者发布图文/视频内容。
  • 获取 Feed 流:关注页与个人主页时间线。
  • 关注关系:关注/取关创作者。

对应的数据与系统关注点:

  • 存储:保存创作者发布的数据。
  • 同步:保存用户可读取的数据(收件箱/发件箱视图)。
  • 元数据:关注关系与用户信息。

核心架构模式:推与拉的博弈

模式一:拉模式 (Fan-out-on-Load / Pull)

*   **工作原理**:

    *   写扩散低:发布仅写入自己的发件箱。
    *   读聚合:读取时查询所有关注者发件箱,聚合、排序后返回。
*   **流程**:用户请求 → 查询关注列表 → 并行查询发件箱 → 聚合排序 → 返回 Feed

```mermaid
sequenceDiagram
    participant U as 用户
    participant C as 客户端
    participant API as API 网关
    participant R as 关系服务
    participant O as 发件箱存储
    participant F as Feed 服务
    U->>C: 打开关注页
    C->>API: GET /feed?cursor=...
    API->>R: 查询关注创作者列表
    API->>O: 并行拉取各发件箱最新内容
    O-->>API: 返回候选内容集合
    API->>F: 聚合/去重/排序
    F-->>C: 返回分页结果
```
*   **优点**:

    *   写轻量,发布快。
    *   无冗余,存储小。
    *   适合关注关系稀疏的场景。
*   **缺点**:

    *   读昂贵,延迟高。
    *   容易形成数据库瓶颈。
*  **注意点**
    *   深翻页陷阱:避免 `LIMIT offset, count`。
    *   建议游标分页:`WHERE id < last_id ORDER BY id DESC LIMIT 20`。
    *   Redis ZSET 可用 `ZREVRANGEBYSCORE`。 ### 模式二:推模式 (Fan-out-on-Write / Push)

*   **工作原理**:

    *   写扩散:发布时查询粉丝,将内容写入每个粉丝收件箱。
    *   读直接拉取:用户读取时按页直接从自身收件箱获取。
*   **流程**:发布 → 查询粉丝 → 并行写收件箱 → 读取 → 直接查收件箱

```mermaid
sequenceDiagram
    participant U as 创作者
    participant C as 客户端
    participant API as API 网关
    participant UGC as UGC 服务
    participant MQ as 消息队列
    participant F as Fan-out 消费者
    participant R as 关系服务
    participant I as 收件箱(Redis)
    U->>C: 发布作品
    C->>API: POST /ugc
    API->>UGC: 校验并保存作品
    UGC-->>MQ: 发送"内容发布"事件
    F->>R: 拉取粉丝列表
    F->>I: 批量写入粉丝收件箱
    Note right of I: 读路径直接分页拉取
```
*   **优点**:

    *   读极快,体验好。
    *   适合高并发读(头部创作者)。
*   **缺点**:

    *   写重,发布延迟高。
    *   存储冗余大。
    *   关系变更处理复杂。 ### 混合模式:现实世界的选择

*   **场景分析**:多数系统并非纯推或纯拉。
*   **典型方案**:

    *   普通用户用推:保障大多数用户读取速度。
    *   大 V 用拉:超过阈值时仅推活跃粉,其余读时拉取并混排。
*   **优点**:读写性能与成本平衡。
*   **缺点**:
    *   系统更复杂,容灾与回退更难。
    *   阈值/策略调优复杂,边界抖动。
    *   幂等合并更难,需避免重复与错序。
    *   一致性成本高,取关清理与补偿任务增加。
    *   峰值难预测,需双侧容量预留。
    *   监控排障更难,跨多环节定位。
    *   降级策略复杂,需无感退化。

团队现状与演进方案

现状

以拉为主(Fan-out-on-Load),各项目单独实现。

考虑因素

  • 规模与关系图:粉丝中位数低、关注关系稀疏,读 QPS 可控。
  • 研发效率:读侧统一去重/聚合/排序,上线快、维护低。
  • 成本与风险:无冗余写、存储小,避免写扩散热点与雪崩。

问题

  • 活跃用户与头部创作者增加后,读侧聚合放大、深翻页增多。

实践:L22 UGC 发布提醒的推-拉结合

背景

订阅作者发布新作品提醒为高 QPS 红点服务,直接拉取关注列表并查 Feed 表会带来较大 DB 压力。

方案概述

  • Push:作品发布/生效时刷新作者最新作品视图,确保读路径低延迟。
  • Pull:客户端轮询获取是否有新订阅作品,读取后回写已读/短期忽略。

接口

  • GET /notification/subscription/status → 读取是否有新订阅作品,返回最新作品摘要。
  • POST /notification/subscription/read → 标记已读与“近期不再提醒”窗口。

缓存与缓存内容

  • InformProfileCache (inform_profile_s9:<roleId>,30m):用户提醒偏好。
  • FollowedRoleIdsCache (FollowedRoleIds:<roleId>,20m):用户关注的作者 ID 列表。
  • CreatorLastestValidWorkCache (CreatorLastestValidWork:<creatorRoleId>,24h):作者最新有效作品快照(WorkIdWorkCtime)。
  • NotificationSubscriptionStatusCache (RoleSubscriptionStatus:<roleId>,30m):订阅提醒状态(LastReadTimeIgnoreUntilTime)。

整体流程

sequenceDiagram
  participant U as 作者端/管理端
  participant C as Client
  participant A as API
  participant S as NotificationService
  participant R as Redis Caches
  participant D as DB

  rect rgba(200,255,200,0.3)
  note right of U: Push: 作品发布/生效
  U->>D: 持久化作品(含 Ctime)
  U->>R: 刷新 CreatorLastestValidWorkCache(creatorRoleId)
  end

  rect rgba(200,200,255,0.3)
  note over C,A,S: Pull: 客户端轮询读取
  C->>A: GET /notification/subscription/status
  A->>S: NotificationSubscriptionStatus(req)
  S->>R: Get InformProfileCache(roleId)
  S->>R: Get NotificationSubscriptionStatusCache(roleId)
  S->>R: Get FollowedRoleIdsCache(roleId)
  S->>R: Gets CreatorLastestValidWorkCache(followedRoleIds)
  S->>D: Fetch Work detail(必要时)
  S-->>C: HasNewSubscription? + Work(可选)
  end

  rect rgba(255,230,200,0.3)
  note over C,A,S: Pull: 回写已读/忽略
  C->>A: POST /notification/subscription/read
  A->>S: NotificationSubscriptionRead(req)
  S->>D: Save LastReadTime/IgnoreUntilTime
  S->>R: Set NotificationSubscriptionStatusCache(roleId)
  S-->>C: OK
  end

最终目标

参考

OKR(2025 Q4 — 2026 Q1)

  • O1 统一 Feed 基础设施落地
    • KR1 发布内部规范与 SDK v1,覆盖收/发件箱、游标分页、幂等去重
    • KR2 完成 2 个存量项目 + 1 个新项目接入,零 P0 事故
    • KR3 关注页读取 P95 < 120ms、P99 < 300ms;错误率 < 0.1%
    • KR4 峰值读侧 DB QPS 较现状下降 ≥ 40%,缓存命中率 ≥ 90%
  • O2 推-拉混合能力最小可用闭环
    • KR1 支持按粉丝量/活跃度阈值自动切换;策略灰度与回退可用
    • KR2 取关清理与补偿任务上线;重复/错序率 < 0.05%
    • KR3 提供读写降级与熔断开关,30 分钟内完成回退
  • O3 可观测性与工程化完善
    • KR1 完成端到端看板与告警,覆盖 99% 关键链路
    • KR2 建立压测基线与容量模型,通过 10x 峰值演练
    • KR3 完成文档与 Playbook,举办 2 场培训,满意度 ≥ 4.5/5

结合我们的业务场景与团队架构,产出一套内部规范与 SDK,提升复用、降低成本、增加稳定性。