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

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

Hello,我是小兔大白糖。

最近在研究一个开源的头条推荐系统项目(toutiao_project)。作为一个沒有写过复杂推荐系统的开发,我对“推荐系统”这个词一直带着点敬畏。

以前觉得它是大厂的黑科技,是算法博士们的专属领域。但当我真正钻进代码,扒开那些高大上的名词(ALS、Lambda 架构、Wide&Deep)之后,我发现它的本质其实也没有特别高深,起码浅层次还是可以理解的。

借着这个开源的练手项目,用大白话聊聊一个工业级推荐系统到底是怎么跑起来的。

为什么推荐系统这么难?

核心矛盾就一个:海量数据 vs 实时响应

想象一下,数据库里有 100 万篇文章。用户下拉刷新的那一瞬间,系统必须在 100 毫秒 内,把这 100 万篇里最适合他的 10 篇挑出来。

如果每篇都算一遍“用户喜好度”,服务器早冒烟了。

所以,推荐系统的架构设计,本质上就是一场**“用空间换时间,用离线换实时”**的交易。

架构全景:分层而治

这个项目的架构非常经典,遵循了 Lambda 架构的设计思想:

![架构图占位:用户 -> gRPC -> 推荐中心 -> 召回/排序 -> HBase/Redis]

整个链路就像一个漏斗,分三步走:

  1. 召回 (Recall): 从 100 万篇里,粗略筛出 500 篇“可能感兴趣”的。(不用太准,但不能漏)
  2. 过滤 (Filter): 踢掉这 500 篇里用户看过的、质量差的。(去重)
  3. 排序 (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),项目设计了三级缓存:

  1. Redis: 有就直接返。
  2. HBase wait_recommend: 上次算多了没推完的,存这儿备用。
  3. 实时计算: 全都没有,才现场算。 这种设计保证了 99% 的请求都不会真正触发繁重的计算逻辑,系统极其稳健。

写在最后:技术人的祛魅

拆解完这个项目,我最大的感触是:没有神话,只有取舍。

推荐系统不是什么魔法,它就是工程化到了极致的产物。

  • 为了快,我们做了预计算。
  • 为了准,我们上了深度学习。
  • 为了稳,我们做了多级缓存和降级。

虽然这个项目离真正的“头条”还有距离(比如没做精细的重排、没上实时特征服务),但它的骨架是完整的。

对于我们独立开发者来说,理解这套 “召回 -> 排序 -> 缓存” 的逻辑,价值不仅仅在于做一个新闻 App。 你想做个 AI 知识库?想做个电商选品工具?底层的**“从海量数据中捞出价值”**的思路,是完全通用的。

#推荐系统 #架构设计 #后端开发 #技术复盘