这一页回答什么
本页将各个子系统串联为主线,解析一条帖子在发布之后经历的完整推荐链路。内容重点区分后台异步写入环节与实时请求读取环节,并说明两类环节在存储层交接的具体机制。
核心结论
- 推荐数据流分为读写两侧。写入侧由“帖子发布”事件驱动,在后台异步执行,不依赖特定用户的请求;读取侧由“用户打开 For You”动作触发,受制于严格的接口延迟预算。
- 读写两侧仅通过存储系统交接。写入侧将计算产物写入 Thunder 内存库、标签存储、召回索引与热门帖子列表;读取侧在处理推荐请求时查询上述存储系统。帖子发布流程与推荐展示流程之间不存在同步远程调用。
- 写入侧包含四条后台异步管线:Thunder 实时摄入、内容理解与打标签(标签路径)、Phoenix 召回索引更新以及热门帖子离线任务。每条管线具有独立的运行周期与延迟特征。
- Thunder 仅在内存中保留最近 2 天内的帖子。参数
post_retention_seconds默认配置为 172800 秒(thunder/args.rs:48-49);预打分过滤器同步剔除发布时间超过 48 小时的候选帖子(home-mixer/params/config.rs:39)。 - 读取侧将推荐结果异步写回系统。接口返回推荐结果后,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
相关页面
- 系统架构总览 —— 请求路径与标签路径的静态总览
- home-mixer 编排层 —— 服务、流水线、候选源
- candidate-pipeline 框架 —— 读取侧的阶段框架
- Thunder Kafka 摄入 —— 写入侧 ①
- Grox 服务架构 —— 写入侧 ② 的内容理解
- 标签规则 —— 写入侧 ② 的规则层
- Phoenix RankAll 召回索引 —— 写入侧 ③
- 可见性过滤 —— 标签在请求中的使用方
- For You 是怎么工作的 —— 白话入口
- 帖子是怎么被选中的 —— 白话版