Skip to content

端到端数据流:一条帖子从发布到被推荐

一条帖子从发布到出现在用户的 For You 信息流:后台写入侧(摄入、标签、索引、热门列表)与实时请求路径的分工,以及两者经存储系统交接的机制

Updated

这一页回答什么

本页将各个子系统串联为主线,解析一条帖子在发布之后经历的完整推荐链路。内容重点区分后台异步写入环节与实时请求读取环节,并说明两类环节在存储层交接的具体机制。

核心结论

  1. 推荐数据流分为读写两侧。写入侧由“帖子发布”事件驱动,在后台异步执行,不依赖特定用户的请求;读取侧由“用户打开 For You”动作触发,受制于严格的接口延迟预算。
  2. 读写两侧仅通过存储系统交接。写入侧将计算产物写入 Thunder 内存库、标签存储、召回索引与热门帖子列表;读取侧在处理推荐请求时查询上述存储系统。帖子发布流程与推荐展示流程之间不存在同步远程调用。
  3. 写入侧包含四条后台异步管线:Thunder 实时摄入、内容理解与打标签(标签路径)、Phoenix 召回索引更新以及热门帖子离线任务。每条管线具有独立的运行周期与延迟特征。
  4. Thunder 仅在内存中保留最近 2 天内的帖子。参数 post_retention_seconds 默认配置为 172800 秒(thunder/args.rs:48-49);预打分过滤器同步剔除发布时间超过 48 小时的候选帖子(home-mixer/params/config.rs:39)。
  5. 读取侧将推荐结果异步写回系统。接口返回推荐结果后,side effect 异步记录已展示帖子历史、缓存候选帖子并上报事件日志;后续请求读取已展示历史以执行去重与翻页加载(home-mixer/candidate_pipeline/for_you_candidate_pipeline.rs:241-260)。

全景图

flowchart TB
    POST([某人发布一条帖子]) --> KAFKA[(Kafka 帖子事件)]

    subgraph WRITE["写入侧 · 后台异步"]
        KAFKA --> THI["Thunder 摄入"]
        THI --> THS[(Thunder 内存库<br/>近 2 天)]
        KAFKA --> GROX["grox 等内容理解"]
        GROX --> RULES["打标签规则<br/>scarecrow / 处置服务"]
        RULES --> LBL[(标签存储)]
        POST -. "帖子事件" .-> RA["phoenix-rankall 索引更新"]
        RA --> IDX[(Phoenix 召回索引)]
        THS --> PPJ["热门帖子任务<br/>每 300 秒"]
        PPJ --> POP[(热门帖子列表)]
    end

    subgraph READ["读取侧 · 实时请求"]
        REQ([有人打开 For You]) --> QH[Query 水合]
        QH --> SRC[候选源]
        SRC --> HYD[候选水合]
        HYD --> FIL[预打分过滤]
        FIL --> SCO[打分]
        SCO --> SEL[选择]
        SEL --> VF["选后水合与过滤<br/>可见性 / 品牌安全"]
        VF --> MIX[混排]
        MIX --> FEED([For You 信息流])
        MIX --> SE["side effect"]
    end

    THS -. ThunderSource .-> SRC
    IDX -. PhoenixSource .-> SRC
    POP -. PopularPostsSource .-> SRC
    LBL -. "可见性过滤读取" .-> VF
    SE -. "served history / 缓存" .-> QH

phoenix-rankall 的事件来源与 Kafka 的关联关系以官方文档说明为准(“随事件更新索引”,README.md:289-290);具体消费的 topic 清单详见 Phoenix RankAll 召回索引。

写入侧:帖子发布后在后台发生什么

① Thunder 摄入

Thunder 从 Kafka 消费帖子创建与删除事件,在内存中维护按作者组织的索引数据。系统支持两种运行角色,通过 is_serving 参数切换(thunder/kafka_utils.rs:71-125):

  • feeder:消费原始帖子事件,将其转码为精简的 InNetworkEvent 并重新发布;
  • serving:消费 InNetworkEvent,将帖子写入 PostStore 内存库。

数据写入阶段直接丢弃超出保留期的历史帖子(thunder/posts/post_store.rs:127-135),后台任务每隔 2 分钟执行一次内存数据裁剪(thunder/main.rs:109)。实现细节详见 Thunder Kafka 摄入 与 Thunder 站内帖子内存库。

② 内容理解与打标签(标签路径)

grox 是独立的流式服务,同样从 Kafka 消费帖子数据。例如 KafkaPostLoader 根据不同 topic 生成计算任务(grox/flows/upa/generators.py:1-51)。grox 执行分类器推理并生成文本特征嵌入向量;media-model-proxy 与 clip 计算图像和视频特征;agatha、bdsm 与 user-cred-v2 分析账号行为特征并计算账号信用分。上述特征经 scarecrow 规则引擎评估、abuse-enforcement-service 处置判定以及 safety-label-user-agg 汇总计算,最终转化为帖子和账号的安全标签写入存储(README.md:186-218)。

在安全标签就绪前,请求侧读取到的标签数据为空。空标签的判定逻辑因业务方而异:品牌安全判定在缺失 Grok 标签时默认返回中风险(home-mixer/models/brand_safety.rs:116-120),导致帖子无法紧邻商业广告展示。详见 Grox 服务架构、媒体模型、账号信号、标签规则 与 处置服务。

③ Phoenix 召回索引

phoenix-rankall 维护供 Phoenix 召回检索的帖子索引;phoenix-rankall-strato 在帖子进入索引前预先查询可见性过滤服务,决定其写入的目标索引(README.md:289-290)。因此,站外召回阶段检索出的候选帖子已预先完成一轮可见性筛选。详见 Phoenix RankAll 召回索引。

④ 热门帖子任务

popular_posts_job 默认每隔 300 秒运行一次:任务读取高活跃作者名单,从 Thunder 检索上述作者的原创帖子,读取帖子浏览量,结合浏览量与发布时长计算综合得分,最终筛选出 500 条热门候选写入存储(home-mixer/main.rs:52-53、home-mixer/popular_posts_job.rs:134-179)。仅高粉丝量的活跃作者帖子能够入选该列表。详见 home-mixer 编排层。

读取侧:一次 For You 请求

请求入口为 ForYouFeedServer::get_for_you_feed,该方法调用外层流水线的 execute_traced(home-mixer/for_you_server.rs:30-50)。外层流水线通过 ScoredPostsSource 调度内层 PhoenixCandidatePipeline(详见 home-mixer 编排层)。推荐请求的处理流程按阶段划分为六个步骤:

1. 查询准备

QueryBuilder 拉取用户画像数据并评估实验开关状态(home-mixer/server.rs:79-184)。外层 Query 水合阶段读取 served history,获取近期已展示帖子标识符与模块疲劳度状态(query_hydrators/served_history_query_hydrator.rs:29-57)。内层 21 个 Query 水合器并发补全用户互动序列、关注列表、屏蔽列表、曝光 Bloom 过滤器与互动特征(phoenix_candidate_pipeline.rs:285-348)。

2. 候选召回

内层 9 个候选源并发执行拉取任务(phoenix_candidate_pipeline.rs:380-390)。其中 4 个主力候选源直接读取写入侧生成的存储数据:

源 读取目标
ThunderSource 写入侧 ① 维护的 Thunder 内存库
PhoenixSource 写入侧 ③ 维护的召回索引(经由 Phoenix 召回服务检索)
PopularPostsSource 写入侧 ④ 维护的热门帖子列表
SimclustersSource SimClusters ANN 向量检索服务(以用户近期互动帖子为种子)

当用户关闭“为你推荐”开关时,系统仅保留 ThunderSource 拉取关注流内容;当上一轮请求缓存的候选帖子数量充足时,系统优先命中本地缓存源。详见 home-mixer 编排层。

3. 水合与预打分过滤

15 个候选水合器并发补全候选帖子的正文文本、作者信息、引用帖、多媒体元数据、语言属性与互动计数(phoenix_candidate_pipeline.rs:392-423)。21 个预打分过滤器按序剔除重复帖子、过期帖子、作者本人的帖子、站外转发帖子、已读已展示帖子、屏蔽与静音账号帖子及命中静音词的帖子(phoenix_candidate_pipeline.rs:429-453),最后根据召回源配额将候选总量截断至不超过 2800 条。详见 过滤流水线。

4. 候选打分

PhoenixScorer 调用 Phoenix 排序模型 计算各项互动行为的发生概率;VMRanker 对各项概率执行加权计算与冷启动分数调整,随后调用 vm-ranker 服务完成多样性重排(phoenix_candidate_pipeline.rs:475-481)。详见 打分与排序。

5. 选择与选后检查

TopKScoreSelector 截取排序得分最高的前 50 条候选。VFCandidateHydrator 仅针对这 50 条候选帖子查询可见性过滤服务(candidate_hydrators/vf_candidate_hydrator.rs:53-130),该环节是标签路径产物接入请求路径的主要汇合点;AdsBrandSafetyVfHydrator 同步读取安全标签以计算品牌安全判定。VFFilter 等 5 个选后过滤器按序剔除不可见帖子与同一会话的重复帖子,最终将候选结果截断至 35 条。详见 候选选择与成型 与 可见性过滤。

6. 广告混排与结果写回

外层 BlenderSelector 执行广告混排并插入推荐模块,生成最终不超过 48 项的信息流内容(home-mixer/params/config.rs:21-22)。推荐结果返回客户端后,13 个 side effect 任务在后台异步执行:

  • UpdateServedHistorySideEffect 记录本次推荐展示的帖子与模块标识符,供后续请求更新 served_ids 与疲劳度状态;
  • PublishSeenIdsToKafkaSideEffect、ServedCandidatesKafkaSideEffect 与 ClientEventsKafkaSideEffect 将展示记录与客户端事件投递至 Kafka;
  • 内层 RedisPostCandidateCacheSideEffect 将本轮入选与未入选的候选帖子(上限 750 条)写入 Redis,供后续翻页请求快速复用(side_effects/redis_post_candidate_cache_side_effect.rs:45-60、params/param.rs:701-706)。

离线异步与实时:对照表

环节 所在侧 触发方式 时效特征
Thunder 摄入 写入 帖子事件流驱动 准实时处理,存在消费延迟
内容理解与打标签 写入 帖子事件、账号行为及定时批任务驱动 流式或批处理执行,存在分钟级延迟
召回索引更新 写入 索引事件流驱动 官方文档未公开具体延迟数值
热门帖子任务 写入 定时触发,每 300 秒执行一次 配合每 60 秒一次的本地缓存刷新
查询准备、召回、水合、过滤、打分、选择、混排 读取 用户主动请求驱动 在单次请求的延迟预算内同步完成
side effect 读取侧尾部 推荐响应返回后触发 后台异步执行,不计入用户端延迟

时效性:帖子发布后多久能被推荐

  • 进入站内候选集:取决于 Thunder 消费 Kafka 的摄入延迟。在帖子写入内存库前,关注者的 ThunderSource 无法检索到该帖子。
  • 进入站外候选集:取决于召回索引的更新周期;若要进入热门帖子列表,还需等待下一轮热门任务执行以及 60 秒的本地缓存刷新(home-mixer/util/popular_posts.rs:7)。
  • 安全标签就绪前:可见性过滤服务仅能读取到未完成的空标签,品牌安全判定默认标记为中风险。
  • 可被推荐的生命周期有限:发布时长超过 48 小时的帖子由 AgeFilter 自动剔除,Thunder 内存库同样仅保留 2 天内的内容。

线上各处理环节的实际延迟数值,开源代码库未作公开说明。

设计决策

决策 理由
读写两侧经存储系统解耦 帖子发布的高吞吐写入与推荐请求的低延迟读取属于不同约束,解耦后可独立优化
内容理解计算置于后台异步执行 分类推理与向量嵌入计算开销大,在请求路径中仅读取预存标签以保护响应延迟
仅对精选入围候选执行可见性查询 跨服务远程查询开销较高,对精选出的 50 条候选发起查询远比全量查询数千条经济
召回索引写入前预先执行可见性过滤 避免站外召回阶段将宝贵配额消耗在无法展示的违规内容上
读取侧将展示历史异步写回存储 支持跨请求去重控制,并管理推广模块的曝光疲劳度

常见问题

Q:系统为何不在用户请求到达时实时执行候选帖子的安全分类?
A:模型分类与特征嵌入属于重计算密集型任务。若将其置于在线请求路径,接口响应延迟将无法满足实时交互要求。系统通过 grox 等服务在后台预先计算并持久化标签,推荐引擎在线仅读取计算结果。

Q:写入侧与读取侧在工程架构上如何实现关联?
A:两者不直接建立进程间连接。ThunderSource 查询 Thunder 内存库,PhoenixSource 查询召回索引,PopularPostsSource 查询热门列表缓存,VFCandidateHydrator 通过可见性过滤服务读取标签存储。

Q:新发布的帖子是否会立即出现在关注者的 For You 信息流中?
A:无法绝对保证立即展现。展示前需要等待 Thunder 消费 Kafka 事件并完成内存写入;若可见性规则依赖的安全标签尚未落库,推荐判定结果也可能与标签就绪后存在差异。

源码锚点

  • thunder/kafka_utils.rs:71-125 —— Thunder 两种 Kafka 角色
  • thunder/posts/post_store.rs:127-135 —— 写入时按保留期过滤
  • thunder/args.rs:48-49 —— 保留期默认 172800 秒
  • grox/flows/upa/generators.py:1-51 —— grox 以 Kafka 为输入
  • home-mixer/popular_posts_job.rs:134-179 —— 热门帖子任务
  • home-mixer/for_you_server.rs:30-50 —— For You 请求入口
  • home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs:285-543 —— 内层各阶段装配
  • home-mixer/candidate_hydrators/vf_candidate_hydrator.rs:53-130 —— 标签进入请求路径
  • home-mixer/candidate_pipeline/for_you_candidate_pipeline.rs:241-260 —— 外层 side effect

相关页面

Type to search…

↑↓ navigate↵ selectEsc close