【独立开发日记 008】拆解头条推荐系统:从召回到排序,我们到底在算什么?

Hello,我是小兔大白糖。
最近在研究一个开源的头条推荐系统项目(toutiao_project)。作为一个沒有写过复杂推荐系统的开发,我对“推荐系统”这个词一直带着点敬畏。
以前觉得它是大厂的黑科技,是算法博士们的专属领域。但当我真正钻进代码,扒开那些高大上的名词(ALS、Lambda 架构、Wide&Deep)之后,我发现它的本质其实也没有特别高深,起码浅层次还是可以理解的。
借着这个开源的练手项目,用大白话聊聊一个工业级推荐系统到底是怎么跑起来的。
为什么推荐系统这么难?
核心矛盾就一个:海量数据 vs 实时响应。
想象一下,数据库里有 100 万篇文章。用户下拉刷新的那一瞬间,系统必须在 100 毫秒 内,把这 100 万篇里最适合他的 10 篇挑出来。
如果每篇都算一遍“用户喜好度”,服务器早冒烟了。
所以,推荐系统的架构设计,本质上就是一场**“用空间换时间,用离线换实时”**的交易。
架构全景:分层而治
这个项目的架构非常经典,遵循了 Lambda 架构的设计思想:
![架构图占位:用户 -> gRPC -> 推荐中心 -> 召回/排序 -> HBase/Redis]
整个链路就像一个漏斗,分三步走:
- 召回 (Recall): 从 100 万篇里,粗略筛出 500 篇“可能感兴趣”的。(不用太准,但不能漏)
- 过滤 (Filter): 踢掉这 500 篇里用户看过的、质量差的。(去重)
- 排序 (Ranking): 对剩下的 100 篇进行精细打分,选出分数最高的 10 篇。(精准)
核心技术拆解:我是怎么看这个项目的
1. 召回:为了不漏掉好东西
项目用了多路召回策略,这就像是派出了几路侦察兵,各显神通:
- ALS 协同过滤: “物以类聚,人以群分”。利用 Spark 离线计算用户矩阵,找到和你口味相似的人,把他们看的推给你。这是解决“惊喜感”的关键。
- 内容召回 (Content): 基于文章内容的向量相似度。你看了 Python 教程,就给你推 Django 教程。这是解决“相关性”的基础。
- 实时召回: 基于你几秒钟前的点击行为。这得靠 Kafka + Spark Streaming 实时计算,主打一个“趁热打铁”。
- 冷启动: 新用户没行为怎么办?简单粗暴,上 Redis 里的“热门文章”和“新文章”。
这些召回结果,都是预计算好的。
- 离线: 每天凌晨,Spark 集群疯狂运转,把全量用户的召回结果算好,存进 HBase。
- 在线: 用户一请求,直接从 HBase 捞数据。这就是“用离线换实时”。
2. 排序:不仅要准,还要快
到了排序阶段,剩下的文章不多了(几百篇),这时候就要上重武器了。
项目使用了 LR (逻辑回归) 和 Wide&Deep 模型。
- 输入: 用户画像(你喜欢啥) + 文章画像(文章是啥) + 场景特征(时间/地点)。
- 输出: 一个 0~1 之间的点击概率(CTR)。
这里有个很有意思的工程细节:ABTest 分流。
代码里用 md5(user_id) 取首字符,把用户分成了不同的桶。
- 桶
0-d的用户:走 Algo-1 (LR 模型)。 - 桶
e-f的用户:走 Algo-2 (Wide&Deep 模型)。 这样就能在生产环境里,用真实流量去 PK 不同算法的效果。这才是大厂的做派。
3. 存储:为什么是 HBase + Redis?
这也是后端同学最容易困惑的地方:为什么不用 MySQL?
- HBase (温数据): 存用户画像、历史记录、全量召回结果。
- 理由: 数据量极大(亿级),且是宽表结构(画像可能有几千个标签)。HBase 的列式存储和 RowKey 查询(O(1)复杂度)完美契合。
- Redis (热数据): 存热门文章、新文章、以及第一级推荐缓存。
- 理由: 毫秒级响应。对于高频访问的 Top 榜单,必须放在内存里。
那些让我“掉头发”的细节
看代码的时候,有两个逻辑让我印象深刻,也是最容易踩坑的地方。
1. 时间戳的双向逻辑 同一个接口,要处理两种动作:
- 下拉刷新: 用户想要新内容。逻辑:
if 请求时间 > 上次推荐时间,走完整推荐流程。 - 上滑翻页: 用户想看历史。逻辑:
else,直接查历史记录表。 这个判断必须极其精准,否则用户就会遇到“怎么刷都是旧内容”的 Bug。
2. 三级缓存的兜底 为了把延迟压到极致(目标 <50ms),项目设计了三级缓存:
- Redis: 有就直接返。
- HBase wait_recommend: 上次算多了没推完的,存这儿备用。
- 实时计算: 全都没有,才现场算。 这种设计保证了 99% 的请求都不会真正触发繁重的计算逻辑,系统极其稳健。
写在最后:技术人的祛魅
拆解完这个项目,我最大的感触是:没有神话,只有取舍。
推荐系统不是什么魔法,它就是工程化到了极致的产物。
- 为了快,我们做了预计算。
- 为了准,我们上了深度学习。
- 为了稳,我们做了多级缓存和降级。
虽然这个项目离真正的“头条”还有距离(比如没做精细的重排、没上实时特征服务),但它的骨架是完整的。
对于我们独立开发者来说,理解这套 “召回 -> 排序 -> 缓存” 的逻辑,价值不仅仅在于做一个新闻 App。 你想做个 AI 知识库?想做个电商选品工具?底层的**“从海量数据中捞出价值”**的思路,是完全通用的。
#推荐系统 #架构设计 #后端开发 #技术复盘