技术探索与实践的一些思考
开篇:从一次“崩溃”说起

记得去年冬天,我们团队接手了一个关键的项目:为一家大型连锁餐饮品牌搭建一套智能点餐推荐系统。这个系统需要实时分析用户的点单数据、历史行为、当前时间以及所在门店情况,进行个性化推荐,提升转化率和客单价。
然而,在上线前的最后一次压力测试中,系统频繁出现延迟甚至短暂崩溃,尤其是在高峰时段,请求量大的时候响应慢得让人抓狂。那会儿我作为项目的Coze工程师,负责整个推荐系统的构建与优化,面对这个问题确实压力山大。
这篇文章想通过这次经历,聊聊我在技术探索与实践中的一些思考和收获——既包括技术层面的问题定位和解决思路,也包括对工程化思维和协作方式的理解。
问题描述:从性能瓶颈到架构混乱

背景介绍
我们要做的这套推荐系统,核心逻辑是根据用户输入的历史订单、菜品热度、节假日等因素,动态生成一个排序列表,返回给前端用于展示。后端基于 Python 构建,使用 FastAPI 提供 API 接口,推荐算法模块使用 PyTorch 模型做预测,部署在本地 GPU 环境上。
一开始看起来都挺顺利的,原型阶段响应很快,模型也训练得不错。但当进入联调和压测阶段,问题就开始暴露了。
遇到的几个典型问题:
接口响应慢,延迟严重
特别是在并发访问量增加时(比如模拟 500+ 用户同时请求),API 响应时间从 200ms 直接飙升到 3s 以上,部分请求直接超时。模型推理卡顿
在多个请求同时触发模型推理的时候,GPU 利用率波动很大,有时候还会报 out of memory,导致服务不可用。代码结构混乱,维护困难
由于前期追求快速迭代,很多代码耦合度很高,状态管理不规范,后期修改功能时经常牵一发而动全身。日志和监控缺失,排查困难
一旦出问题就只能靠打印日志去猜,根本不知道具体是哪个环节出了错。
这些问题一度让项目陷入停滞,我们意识到不能再继续“拍脑袋式开发”,必须停下来重新审视我们的技术选型和系统设计。
解决方案:重构 + 优化 + 工程化升级
第一步:问题定位 —— “诊断”比“治疗”更重要
我们决定先冷静下来,逐一排查问题源头。
使用 Locust 进行压测 + 日志打点
我们用了 Locust 对推荐接口进行了压测,并结合日志埋点来记录每个阶段的耗时。最终发现:
- 80% 的时间花费在模型推理阶段
- 模型加载没有复用,每次请求都重新初始化,导致资源浪费
- FastAPI 默认的线程池配置不够高,并发处理能力不足
这让我们明确了优化的重点方向:模型推理效率 + 并发支持 + 异步处理机制。
第二步:技术选型调整
模型推理优化:引入 TorchScript 和缓存机制
我们原有的 PyTorch 模型是直接以 .pt 文件的形式保存的,每次加载都需要反序列化整个模型对象,效率很低。
于是我们改用 TorchScript 来导出模型:
script_model = torch.jit.script(model)
torch.jit.save(script_model, "recommendation_scripted.pt")
这种方式导出的模型可以直接在生产环境中加载运行,不再依赖原始定义类文件,加载速度提升了 3~5 倍。
此外,我们还做了两件小事:
- 将模型实例作为全局变量缓存起来,避免重复加载;
- 使用 LRU 缓存用户最近几次输入特征,减少重复计算。
效果非常显著:模型推理时间从平均 600ms 下降到 80ms,几乎可以忽略不计。
接口层优化:异步 + 线程池扩容
我们发现 FastAPI 默认的线程池设置较低,在大量并发请求下成了瓶颈。
为了应对这种情况,我们做了两个改动:
- 将关键函数改为
async def函数,利用 asyncio 提高性能; - 扩展默认的线程池大小,提高 I/O 密集型任务的吞吐能力。
示例代码如下:
from concurrent.futures import ThreadPoolExecutor
# 自定义线程池
executor = ThreadPoolExecutor(max_workers=32)
@app.post("/recommend")
async def recommend_api(request: RequestModel):
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(executor, model_predict, request)
return result
这一招在高并发场景下特别有效,CPU 利用率提高了,接口响应时间也更稳定了。
架构拆分:微服务 + 状态分离
我们还意识到一个更大的问题:所有业务逻辑都堆在一个服务里,职责不清、难以扩展。为此,我们将系统拆分成三个独立的服务:
- Recommend Service:核心推荐引擎,接收特征输入,返回推荐结果;
- Feature Service:统一提供用户画像、商品热度等特征数据;
- Cache Service:负责热点缓存、结果复用、防重请求。
服务之间通过 gRPC 通信,数据传输高效且清晰。这样一来,不仅提升了性能,也为后续扩展打下了基础。
工程化补课:接入 Prometheus + ELK
最后,我们开始重视工程化建设,重点做了三件事:
- 接口级日志追踪:为每一个请求打上唯一的 trace_id,便于问题定位;
- 接入 Prometheus + Grafana:监控 QPS、P99 延迟、错误率等指标;
- 使用 ELK 收集日志:方便按关键词查询,快速分析故障原因。
这些看似“边缘”的工作,其实是保障长期稳定运行的基础。有一次我们在凌晨收到告警,发现某个特征服务内存异常上涨,正是靠着完善的监控工具及时定位到了问题代码,否则后果不堪设想。
效果总结:从“跑通”到“跑好”
经过几周的努力,我们成功把推荐系统从“勉强能用”的状态推到了“线上可用”的水平。
| 指标 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| 平均响应时间 | 1.2s | 150ms | 87.5% |
| 最大并发支持 | ~120QPS | ~1200QPS | 10倍 |
| 推荐准确率 | N/A | 87.2% | 新增 |
| 系统稳定性 | 崩溃频繁 | 稳定上线 | 完全改观 |
不仅如此,代码结构也更加清晰,接口文档完整,后续新功能开发效率大幅提升。
更令人欣慰的是,客户反馈也非常好。上线两个月后,他们告诉我们:
“自从用上了你们的新系统,门店下单转化率提升了 15%,顾客满意度也有明显上升。”
一句话,胜过千言万语。那一刻,我们都觉得所有的努力都值得。
经验分享:写给后来者的几点建议
在这次项目中,我学到很多,也有很多教训。以下是我总结出来的几个经验点,希望对你有所帮助。
1. 技术选型不是越“新”越好,而是要看是否合适
我们初期尝试过用 TensorFlow Serving 来部署模型,但最终还是选择了 PyTorch + TorchScript。为什么?因为团队熟悉 PyTorch,而且部署流程简单、调试方便。
选择技术栈,应该以团队能力和实际需求为核心,而不是盲目追新。新技术固然好,但如果不能被团队驾驭,只会成为负担。
2. 工程化意识要早点建立,哪怕只是简单的日志输出
我以前也很轻视工程化的东西,觉得“能把功能跑通就行”。直到那次半夜服务器挂了、查日志查了两个小时才发现问题是线程阻塞……我才明白:
可观测性就是系统的第二生命。
所以,如果你也在开发后端服务,请务必:
- 为每个请求添加唯一标识;
- 输出 structured log;
- 接入基本的 metrics 监控;
- 写清晰的接口文档。
3. 系统设计要有前瞻性,不能只看当下
刚开始我们为了赶进度,把所有功能都写在一起,后来发现改一个小功能都要牵涉多个模块,非常痛苦。
现在我会坚持做一个最小的抽象设计:
- 核心功能模块化;
- 数据流清晰,上下游解耦;
- 接口标准化,未来容易扩展。
即使项目再紧急,也要抽出时间梳理结构。这不是浪费,这是为未来节省时间。
4. 性能优化不是一蹴而就,要有方法和耐心
优化性能这件事,很多人都想“一口吃个胖子”,但我发现最有效的方式是:
- 先压测找到瓶颈
- 再逐个击破
- 记录每一轮优化的结果
不要指望一次就把所有问题解决完,持续观察、逐步改进才是王道。
5. 合作沟通是解决问题的关键
在这次项目中,我最大的感触是:一个人再强,也无法解决所有问题。
我们必须跟产品、运营、运维、测试保持良好的沟通,才能明确需求边界、理解风险点、协调上线节奏。尤其是 Coze 工程师,往往既是开发者又是架构师,更是跨部门协调的核心节点。
学会倾听、表达、协作,是每一位工程师成长路上的必修课。
结尾:技术的温度在于人
写到这里,我想起项目上线那天晚上,我们一起喝了杯咖啡,聊了聊过去几个月的点滴。有人说:“其实不是我们多厉害,只是我们没放弃。” 我深以为然。
技术从来不是冰冷的代码和公式,它背后是一群人不断尝试、不断失败、不断改进的过程。每一次 bug 的修复,都是对我们意志力的考验;每一段高效的代码,也都藏着我们的心思和智慧。
我希望这篇带着真实场景、真实挑战、真实思考的文章,能给你一些启发。无论是正在做推荐系统的你,还是刚刚起步的技术新人,亦或是已经奋战多年的老兵,愿你在自己的岗位上,始终保有一份热爱,一份执着。
毕竟,这个世界不会记住你写了多少行代码,但一定会记住你做出的事情有没有价值。
文末小彩蛋:
有人问我,如果再来一遍,会不会做得更好?
我说:“当然!但正是因为曾经踩过坑,才会知道哪些地方该绕着走,哪些地方值得冲一把。”
共勉。

评论 0