技术探索与实践背后的真实故事:一次推荐系统升级的旅程
开篇:技术不是写出来的,是“干”出来的

作为一名在互联网公司做推荐系统开发的工程师,我常常被问到这样一个问题:“你每天都在研究新技术,觉得累吗?”每次听到这个问题,我都忍不住笑一笑。因为答案其实很简单:不累,而且很兴奋。
在我参与的一个真实项目中,我们遇到了一个典型的挑战——用户兴趣变化快、点击率下降、冷启动新用户难……这些问题听起来都很常见,但真正解决起来,却远比想象中复杂得多。而正是在这样的过程中,技术探索与实践的价值才真正显现出来。
这篇文章想跟你分享的就是那次经历,一段关于如何通过技术探索和实践,把一个“看似简单”的推荐需求,变成一个稳定可控、效果可量化的系统过程。
问题描述:老系统跟不上业务节奏了

事情要从2023年初说起。当时我们在做的产品是一个内容型社区平台,核心功能之一就是首页内容推荐。原来的推荐机制是一个基于协同过滤的老系统,跑得好几年了,也还能用,但有几个明显的问题:
- 点击率逐渐下降:老系统的推荐结果越来越“千人一面”,用户开始流失。
- 冷启动问题突出:新注册用户打开 App 后看到的内容质量参差不齐,跳出率高。
- 策略更新慢:每次新增一点内容分类规则,都需要全量重跑,效率极低。
我们团队意识到,这套系统虽然没坏,但已经跟不上业务的发展节奏了。于是公司决定启动一次“推荐系统重构计划”,目标是提升整体 CTR(点击率),降低新用户跳出率,并为后续引入深度学习模型打下基础。
解决方案:从规则引擎到实时特征 + 模型服务化

初步调研与选型
一开始我们尝试优化协同过滤算法,比如引入时间衰减因子、热度加权等策略。但这只是“修修补补”,无法从根本上解决问题。
最终我们决定分三步走:
- 构建轻量级特征工程模块:采集更多维度的用户行为数据。
- 接入实时推荐服务:采用离线训练 + 实时预测的模式。
- 搭建灵活的模型调度架构:支持未来引入更复杂的模型。
技术栈方面,我们选择了以下几个核心组件:
- Flink:用于处理实时特征,计算用户兴趣得分。
- Redis + Kafka:作为中间数据缓存和消息队列。
- TF-Serving + ONNX Runtime:部署模型服务,兼容不同格式。
- Airflow:管理离线训练流程。
- Prometheus + Grafana:监控推荐链路指标。
我们的思路是从最容易落地的点入手,先解决冷启动问题,然后逐步过渡到个性化推荐。
代码实践:以“用户画像特征提取”为例
为了说明具体做法,我想重点分享一下我们在构建用户画像时的一段关键 Flink 程序逻辑。
假设我们要提取用户的近期偏好标签,比如过去一小时内浏览过的文章类型分布:
# Flink 流处理逻辑简化示例
def process_user_click(event):
user_id = event["user_id"]
article_type = event["article_type"]
# 更新用户最近浏览的历史
if user_id not in user_history:
user_history[user_id] = deque(maxlen=50) # 最多保留最近50条记录
user_history[user_id].append(article_type)
# 计算当前兴趣分布
interest_counter = Counter(user_history[user_id])
total_actions = len(user_history[user_id])
return {
"user_id": user_id,
"features": {k: v / total_actions for k, v in interest_counter.items()}
}
这段代码虽然简化了不少,但它展示了我们是怎么利用滑动窗口+历史统计的方式构建动态特征的。
然后我们会将这个特征同步到 Redis 中,供实时服务调用:
// Go 示例代码片段,用于写入 Redis
func WriteUserFeaturesToRedis(userID string, features map[string]float64) error {
ctx := context.Background()
redisKey := fmt.Sprintf("user_features:%s", userID)
for k, v := range features {
if err := rdb.HSet(ctx, redisKey, k, v).Err(); err != nil {
return err
}
}
return nil
}
这些特征最终会在推荐排序阶段被用来计算打分,形成个性化推荐结果。
踩坑经验:技术探索从来都不是一帆风顺的

在整个项目推进过程中,我们踩过不少坑,有些是技术层面的,有些是协作上的。这里想重点讲两个例子:
坑点一:模型版本控制不清晰,导致线上预测混乱
刚开始我们部署了一个简单的 LR 模型,结果上线后发现预测结果时好时坏,一度怀疑是特征提取出错。
后来才发现,训练用的特征和在线预估用的特征不一致!一部分特征在离线计算的时候做了归一化,在线时候漏掉了这一步骤。
解决方案:
- 所有模型必须绑定对应的 feature schema 文件;
- 在线预估服务需要校验输入特征是否匹配 schema;
- 加入 AB 测试模块,不同版本的模型并行运行验证差异。
坑点二:忽视性能瓶颈,影响主流程
在初期,我们设计了一套基于 HTTP 接口的模型预测服务。本地测试没问题,但一上压测环境就卡得不行。
后来分析发现是因为每次请求都要访问 Redis 多次,串行操作拉低了响应速度。
改进方案:
- 引入批量预测接口,减少 IO 次数;
- 将部分高频特征预加载到内存;
- 使用 gRPC 替换 JSON 协议通信。
这些小改动带来了显著的性能提升,QPS 提升了近三倍。
效果总结:数字是最诚实的语言
经过将近三个月的努力,这套新的推荐系统正式上线。
上线后的几个核心指标如下:
| 指标 | 上线前 | 上线后 | 变化幅度 |
|---|---|---|---|
| 首页 CTR | 2.1% | 3.8% | ↑ 81% |
| 新用户跳出率 | 58% | 41% | ↓ 17% |
| 冷启动推荐准确率 | 39% | 65% | ↑ 26% |
虽然不是翻天覆地的变化,但对于一个日活百万的产品来说,每一点微小的提升都是巨大的商业价值。
更关键的是,我们建立了一套可扩展、可迭代的技术架构,为后面引入深度学习模型、强化学习策略打下了坚实的基础。
经验分享:真正的技术探索是不断试错的过程

回望这次项目经历,我觉得最重要的一点是:技术探索不是为了炫技,而是为了解决实际问题。
在这条路上,我总结了几点建议,希望对大家有帮助:
1. 先跑通再优化:别上来就追求最先进算法
很多刚接触推荐的同学喜欢直接搞个 Transformer 或者 Graph Neural Network。其实大可不必。
“能 work 的东西永远比完美的方案更有价值。”
先用最简单的模型快速验证逻辑是否可行,再慢慢升级才是正道。
2. 数据口径一定要统一
我见过太多项目因为特征不一致、标签定义不清而导致模型反复训练失败。记住一句话:
“特征即模型的一部分。”
所以在训练、评估、部署三个阶段,必须保持特征提取逻辑一致。
3. 做好灰度发布和 AB 测试
任何新模型上线,都不要一次性切流量。哪怕你信心十足,也要从小流量开始验证。
否则一旦出现偏差,后果可能难以挽回。
4. 不要忽略工程能力的重要性
很多时候我们太专注模型精度,却忽略了系统性能、稳定性、可维护性。尤其是推荐这种对延迟敏感的场景,工程实现同样重要。
就像我们后来发现的那样,有时候优化一个 Redis 查询的结构,带来的收益比调模型参数还明显。
结语:探索与实践,从来不是一个选择题
技术这条路,没有谁一开始就全都会。我们遇到问题、设计方案、编写代码、调试排错,最后交付价值——这一整套流程的本质,其实就是不断探索与实践的过程。
在这个过程中,你会犯错、会迷茫、会焦虑,但只要你坚持下去,最终收获的不只是技术的成长,还有解决问题的能力和团队的信任。
最后送给大家一句话,也是我一直坚信的:
“真正的好技术,藏在细节里。”
如果你愿意深入每一个 bug,认真对待每一行代码,我相信,你也一定能在自己的实践中找到属于你的“技术之美”。
参考资料 & 工具清单:
- Apache Flink 官方文档(https://flink.apache.org/)
- TensorFlow Serving(https://www.tensorflow.org/tfx/serving/)
- Redis 设计与实现(https://github.com/huangz1990/redis-book)
- Prometheus 监控系统(https://prometheus.io/)
如你对文中提到的模型部署或特征工程有进一步兴趣,欢迎留言交流!

评论 0