技术探索与实践:一个老后端的代码人生碎碎念

Mock数据工厂
2025-12-16 00:49
阅读 763

上周五晚上十点半,办公室只剩我和隔壁工位的运维小哥。耳机里放着周杰伦的《稻香》,眼前是刚上线的 AI 推荐服务在 Prometheus 里疯狂飙红的 CPU 曲线。我一边敲着 kubectl logs -f,一边默默点开外卖软件——这已经是我入职新公司两个月以来第三次深夜排查线上问题了。

没错,我是那个在京东干了五年后端的老兵,经历过无数个 618 和双 11 的流量洪峰洗礼,自以为见过大风大浪。结果跳槽到这家创业型公司才两个月,就发现自己像个刚毕业的实习生一样手忙脚乱。特别是最近被领导“温柔”地安排去搞 AI 相关的技术栈,每天都在怀疑人生:“我是不是该回老家卖红薯?”

但吐槽归吐槽,作为一个靠代码吃饭的人,总得把活儿干完。今天这篇文章,就是想和大家聊聊我在最近这次技术探索中踩过的坑、悟出的道理,以及那些让我半夜惊醒又豁然开朗的“最佳实践”。不灌鸡汤,只讲干货,顺便夹带点程序员的牢骚和自嘲。


起因:AI 不是魔法,但产品经理觉得它是

事情要从上个月说起。产品总监在周会上轻描淡写地说:“咱们能不能加个‘智能推荐’功能?用户看了 A 商品,就给他推 B,就像淘宝那样。”
我说:“行啊,但得先定指标、埋点、训练模型、部署推理服务……至少两个月。”
他眨眨眼:“能不能下周上线?MVP 版就行。”

我当时差点把咖啡喷出来。MVP?你当 AI 是 CRUD 啊?

但现实是,老板要,就得上。于是我硬着头皮接下了这个任务,开始从零搭建一个轻量级的推荐系统后端。目标很明确:用最小成本跑通端到端流程,支撑业务快速验证想法

这就引出了我今天想分享的第一个核心理念:

技术探索的本质,不是追求最酷最前沿,而是找到“足够好+可迭代”的平衡点。


技术选型:别一上来就上 K8s + Ray + Triton

很多新人(包括曾经的我)一听到 AI,立刻想到的是大模型、GPU 集群、分布式训练。但在这个场景下,我们根本不需要那么重。

我们的业务数据量其实不大——日活用户不到十万,商品库也就几万 SKU。用户行为日志每天也就几个 GB。用不上动辄几十卡的训练集群。

所以我做了如下决策:

组件 初期方案 理由
特征工程 Pandas + Scikit-learn 快速验证特征有效性,无需复杂 pipeline
模型 LightFM(混合协同过滤) 支持 user/item 特征,训练快,内存友好
推理服务 Flask + Gunicorn 轻量、熟悉、5 分钟能跑起来
部署 Docker + 公有云 ECS 团队没专职 SRE,K8s 上手成本太高

是的,你没看错,我用了 Flask。在 2024 年,一个五年经验的后端工程师,居然用 Flask 写 AI 服务。要是让前司的架构师看到,怕是要连夜把我拉黑。

但事实证明,这个“土法炼钢”策略非常有效。三天时间,我就跑通了从日志解析 → 特征构建 → 模型训练 → API 推理的完整链路。虽然性能一般(QPS 大概 80),但足够支撑 A/B 测试了。

而且,因为代码简单,测试同学都能看懂逻辑,提 Bug 时直接说:“你第 42 行那个相似度阈值是不是设太高了?” —— 这种协作效率,在微服务地狱里简直不敢想。


踩坑实录:那些让我想砸电脑的瞬间

当然,过程绝不是一帆风顺。下面这几个坑,我替你们踩过了,记得绕道。

坑 1:模型版本和代码版本对不上

第一次上线后,发现线上推荐结果和本地完全不一样。查了一晚上,才发现是模型文件用错了——本地训练完没 push 到 Git LFS,CI/CD 脚本拉了个空模型。

教训:模型也是代码的一部分!必须纳入版本管理。

后来我改成了这样:

# 训练脚本末尾自动打 tag
git add model.pkl
git commit -m "feat: update model v$(date +%Y%m%d)"
git push origin main

同时在服务启动时校验模型 hash:

import hashlib

def load_model(path):
    with open(path, 'rb') as f:
        model = pickle.load(f)
        f.seek(0)
        file_hash = hashlib.md5(f.read()).hexdigest()
    # 打印 hash 便于排查
    logger.info(f"Loaded model with hash: {file_hash}")
    return model

坑 2:冷启动问题被忽视

新用户进来,没有任何行为记录,模型直接返回空列表。产品一看:“怎么新用户看不到推荐?是不是挂了?”

其实不是挂了,是没数据。但我忘了提前和产品沟通“冷启动策略”。

解决方案:兜底策略必须有!

def recommend(user_id):
    if not has_behavior(userid):
        # 返回热门商品(缓存 5 分钟)
        return get_hot_items()
    
    # 正常走模型
    return model.predict(user_id)

而且这个“热门商品”列表,我们用 Redis Sorted Set 实现,每天凌晨用 Spark 跑一次 Top 100,既准又快。

坑 3:日志打得太少,排查靠猜

上线第一天,监控显示 5% 的请求超时。但日志里只有 “Request processed”,连 user_id 都没打。

我只好临时加日志重新部署,结果又引发了一次滚动更新抖动……运维小哥看我的眼神都变了。

现在我的日志规范是:

logger.info(
    "recommend_request",
    extra={
        "user_id": user_id,
        "item_count": len(items),
        "model_version": MODEL_VERSION,
        "latency_ms": time.time() - start,
        "fallback_used": fallback_used
    }
)

配合 ELK,任何异常都能秒级定位。


性能优化:从 80 QPS 到 1500 QPS 的野路子

初期 Flask 服务确实扛不住。压测到 100 并发就开始丢请求。但我不想立刻上 gRPC 或者换 Go,毕竟团队都是 Python 老兵。

于是开始“抠细节”:

  1. 模型加载只做一次
    model = load_model() 放到全局,而不是每个请求都加载。省下 200ms。

  2. 用 joblib 替代 pickle

    from joblib import dump, load
    # 序列化速度提升 3 倍,内存占用减少 40%
    
  3. 引入缓存层
    对高频用户(比如运营号、爬虫)的结果做 1 分钟缓存:

    @lru_cache(maxsize=10000)
    def cached_recommend(user_id, timestamp_minute):
        return _real_recommend(user_id)
    
  4. 异步化非关键路径
    比如打点上报、ABTest 分流,全部扔进线程池:

    from concurrent.futures import ThreadPoolExecutor
    executor = ThreadPoolExecutor(max_workers=2)
    
    def log_async(event):
        executor.submit(_send_to_kafka, event)
    

优化后,单机(4C8G)QPS 从 80 提升到 1500,P99 延迟 < 80ms。产品终于闭嘴了。


工程化思维:代码人生不只是写函数

在京东那几年,我最大的收获不是技术,而是工程化思维。什么叫工程化?就是:

  • 可观测性 > 功能完整性
  • 可回滚 > 快速上线
  • 文档即代码 > 口头约定

所以在新项目里,我坚持了几件事:

✅ 所有接口都有 OpenAPI 文档(用 FastAPI 自动生成)
✅ 每次部署必须带 changelog 和回滚脚本
✅ 关键路径必须有单元测试(哪怕只有 30% 覆盖率)
✅ 所有配置走环境变量,禁止 hardcode

举个例子,我们的 ABTest 配置:

# abtest.yaml
experiments:
  - name: "rec_v2"
    traffic: 10%  # 逐步放量
    enabled: true
    rules:
      - field: "user.country"
        value: "CN"

服务启动时自动加载,支持热更新。再也不用求运维改 Nginx 配置了。


最佳实践总结:给后端同行的几句真心话

经过这两个月的折腾,我对“技术探索”有了新的理解。分享几点心得:

1. 先跑通,再优化

别一上来就设计“完美架构”。MVP 的核心是验证假设,不是炫技。能跑就行,跑不动再改。

2. 技术债要主动管理

我知道用 Flask 不够“高大上”,但我在 README 里明确写了:“待 QPS > 2000 时迁移到 FastAPI + ASGI”。这就是技术债的透明化。

3. 和业务对齐语言

不要和产品说 “embedding dimension”,要说 “推荐准确率”。他们关心的是结果,不是过程。

4. 保护自己的精力

我给自己定了规矩:晚上 9 点后不处理非 P0 问题。代码人生很长,别 burnout 在第一个月。

5. 保持学习,但别焦虑

AI 很火,但后端的基本功(网络、存储、并发)永远不过时。我现在学 PyTorch,不是为了转算法,而是为了更好地和算法同学沟通,写出更高效的推理服务。


结语:代码人生,是一场马拉松

写这篇文章的时候,窗外天刚亮。昨晚的线上问题终于定位到是 Redis 连接池泄漏,修复后一切恢复正常。

回想这五年,从京东大促的通宵值守,到现在创业公司的灵活试错,我越来越觉得:所谓“最佳实践”,不是教科书上的金科玉律,而是在具体约束下做出的最优权衡。

技术会变,框架会过时,但解决问题的思路、对系统的敬畏、对协作的尊重,这些才是代码人生的底层逻辑。

最后送大家一句我工牌背面的话(前司文化):“Stay humble, stay hungry.”

共勉。

P.S. 如果你也正在被 AI 项目折磨,欢迎留言交流。或者,一起听周杰伦写代码?🎧

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝