从踩坑到成长:一次真实项目中的技术探索与反思

代码杂货铺
2025-06-29 05:19
阅读 658

引言:为什么想写这篇文章?

引言:为什么想写这篇文章?

我是一个有着五年工作经验的 Coze 工程师,经历过从零搭建平台、优化性能瓶颈,也处理过线上事故。今天想和大家分享一次让我印象深刻的“踩坑”经历 —— 它不是一次简单的 Bug 修复,而是一场关于技术选型、工程实践和团队协作的综合性挑战。

在这个项目中,我们尝试用一套新的 AI 对话引擎架构来替代老系统,以支持更高并发、更低延迟的用户交互体验。听起来挺理想,但在实际落地过程中,我们踩了不少坑,也学到了很多宝贵的教训。

希望这篇文章不仅能带大家看到我们解决问题的过程,也能引发一些对于当前 AI 架构下工程化实践的思考。


项目背景:我们要做什么?

项目背景:我们要做什么?

我们当时负责的是一个面向企业客户的智能客服系统,核心功能是通过对话引擎实现自动回复、意图识别、上下文管理等能力。随着客户数量增长以及对响应质量的要求提升,原有的基于规则+简单模型的系统已经无法满足需求。

于是我们决定引入一个新的基于 LLM(大语言模型)的对话引擎框架 —— 也就是 Coze 的早期形态,并开始构建一套可插拔、易扩展、高可用的新系统。

目标很明确:

  • 提升对话理解能力和回答准确性;
  • 支持灵活的对话流程配置;
  • 实现模块化设计便于后续维护;
  • 满足高并发场景下的性能需求。

但理想丰满,现实骨感。


遇到的挑战:你以为的“换套框架”,其实是个大坑

第一坑:低估了模型推理的资源消耗

起初,我们认为只要把模型跑起来,加个服务层就能解决。结果上线前测试才发现 —— 一个 Qwen2.5 推理在 GPU 上单次请求居然要 3s 多!更别说并发一上来,GPU 资源直接被打满。

原因分析:

  • 模型太大(7B),没有做量化或蒸馏处理
  • 请求未做批量合并(batching)
  • 没有使用高效的推理引擎(如 vLLM、Triton)

第二坑:服务调度与超时问题频发

我们用了 gRPC 做远程调用,但是经常出现 timeout、connection reset 等异常。

根本原因:

  • 没有合理设置重试策略
  • 缺乏负载均衡机制
  • 推理节点挂掉后未及时熔断

第三坑:对话状态一致性难以保证

对话流程中有大量上下文需要保存,包括用户的多轮对话记录、临时状态数据等。我们最初使用 Redis 存储 session,但由于网络延迟和并发操作,出现了多次状态错乱。


我们是怎么解决这些问题的?

下面我将从几个关键环节,详细讲讲我们的解决方案和实现思路。

1. 推理性能优化:从单次推理到批处理 + 服务编排

方案选择与权衡

我们调研了几种方案:

方案 优势 劣势
直接调用 Transformers API 快速原型开发 性能差,不支持并发
使用 vLLM 进行推理 支持 batching 和并行 配置较复杂
Triton Inference Server 支持异步、动态 batch、多种模型格式 学习成本高
自研中间代理进行合并 完全可控 开发维护成本高

技术应用场景-1

最终我们选择了 vLLM + FastAPI 中间层 的组合。一方面它开源且社区活跃,另一方面可以快速部署,也不失扩展性。

核心代码片段

from vllm import LLM, SamplingParams

# 初始化模型
llm = LLM(model="qwen/Qwen2.5", trust_remote_code=True)

# 设置采样参数
sampling_params = SamplingParams(temperature=0.8, top_p=0.95, max_tokens=512)

# 批量推理调用
def batch_generate(prompts):
    outputs = llm.generate(prompts, sampling_params)
    return [output.text for output in outputs]

当然这是最简版本,我们在实际中还做了 token 缓存、prompt 编码复用、并发控制等多个层面的优化。


2. 分布式服务与熔断机制改进

我们用 K8s 部署服务,结合 Istio 做服务治理,并集成了 Hystrix 风格的熔断逻辑。

核心做法:

  • 服务注册发现统一使用 Consul
  • 请求链路增加 Retry、CircuitBreaker 中间件
  • Prometheus 监控指标接入告警系统

示例配置(FastAPI + Starlette-Middleware)

from fastapi.middleware import Middleware
from starlette.middleware.cachedintervals import IntervalRateLimitMiddleware

app.add_middleware(
    IntervalRateLimitMiddleware,
    interval=60,
    limit_per_interval=1000
)

@app.middleware("http")
async def add_circuit_breaker(request: Request, call_next):
    try:
        response = await circuit_breaker.execute(call_next(request))
        return response
    except CircuitBreakerError:
        return JSONResponse(status_code=503, content={"error": "Service Unavailable"})

3. Session 状态管理重构

为了解决对话状态一致性问题,我们引入了一个轻量的 “对话状态机” 概念,同时将 session 数据存在本地内存缓存中,并配合 Redis 作为持久化存储。

实现逻辑:

  • 每个对话流拥有独立的状态 ID
  • 初始会话进入时从 Redis 加载上下文
  • 每轮对话结束后更新内存缓存
  • 定期异步落盘,避免每次访问都走网络 I/O

示例伪代码

class SessionManager:
    def __init__(self):
        self.cache = LRUCache(max_size=10000)
    
    def get_session(self, session_id):
        if session_id in self.cache:
            return self.cache[session_id]
        else:
            # 从 Redis 获取
            session = redis.get(session_id)
            self.cache.set(session_id, session)
            return session
    
    def update_session(self, session_id, state):
        self.cache.update(session_id, state)
        # 定期异步刷新到 Redis

踩坑经验总结:那些年我们一起掉过的坑

🐞 1. 不要盲目相信模型输出

我们曾有一段时间完全信任模型的输出内容,在业务侧直接展示给用户。结果有一次模型返回了敏感词,被客户投诉,影响非常不好。

教训:

  • 所有输出必须经过敏感词过滤和服务端校验
  • 增加关键词替换、内容审核中间件
  • 设立白名单/黑名单机制

🔥 2. 上线之前一定要做压力测试!

我们一开始没太重视压测,结果灰度发布当天就出现了雪崩效应。服务 A 挂了,导致服务 B 无限等待,连锁反应整片微服务瘫痪。

改进措施:

  • 使用 Locust 做接口压测
  • 模拟极端情况下的失败场景
  • 加入限流、熔断、降级机制

💔 3. 技术文档缺失毁一生

项目初期大家都忙于编码,没人写文档。后来新同事接手的时候,只能靠看代码猜逻辑。一度造成多个 bug 是因为理解错误导致的重复修改。

建议:

  • 每个模块都要有清晰的文档说明
  • 推荐使用 MkDocs 或 Docusaurus
  • Git 提交记录也要规范,方便回溯

效果总结:这次改造带来的收益

开发工具界面-2

经过两个月的折腾,整个系统焕然一新:

指标 改造前 改造后
单轮响应时间 ~3.5s ~0.7s
同时并发数 ~200 QPS ~1500 QPS
故障率 高(频繁超时) < 0.5%
维护成本 高(无文档) 明显下降

更重要的是,现在的架构更加清晰,各模块职责分明,未来扩展也更有底气。


经验分享:给同行朋友们的一些建议

✅ 1. 技术选型别图快,要图稳

很多人喜欢追热点,看到哪个新技术火就想上马。但工程落地要考虑稳定性、成熟度、社区生态等多个因素。比如我们在对比 vLLM 和 HuggingFace TGI 的时候,最终还是选了前者,因为它更适合我们这种偏实时推理的场景。

✅ 2. 性能优化要从底层抓起

不要只停留在“加缓存”或者“换个模型”的表层优化。真正的性能瓶颈往往在硬件利用率、并发控制、模型结构设计上。有时候一个 kernel 的改动,就能带来几十倍的效率提升。

✅ 3. 写文档、建规范、做 Code Review,这些都不是形式主义

刚开始觉得文档麻烦、Code Review 耽误进度,但后来发现这些都是防止项目失控的关键步骤。尤其是多人协作的项目,如果没有统一的代码风格和流程规范,很容易陷入“一团乱麻”。

✅ 4. 埋点监控必须从第一天做起

我们项目中期才加上日志埋点,结果排查问题非常痛苦。建议所有服务一开始就集成以下基础能力:

  • 日志收集(ELK)
  • 接口埋点追踪(OpenTelemetry)
  • 错误码分类与统计
  • 机器资源监控(Prometheus + Grafana)

写在最后:技术成长是一次次“踩坑”的累积

写这篇文章的时候,我翻出了当时的日志记录和 Slack 消息,一幕幕历历在目。有些是我们深夜调试的问题,有些是凌晨三点被报警叫醒的崩溃时刻。

但也正是这些坑,让我们从“写代码的人”慢慢成长为“懂系统的开发者”。

如果你也在做类似的事情,希望这篇记录能给你一些参考和鼓励。记住一句话:每一个让你痛苦的技术难题,最终都会成为你最有价值的经验资产

如果这篇文章对你有所帮助,欢迎留言交流,一起探讨更多 AI 工程落地的实战心得。


📅 作者信息: Coze 工程师 | 5年 AI 系统开发经验
GitHub:@coderzhao
邮箱:zhaocoding@example.com

💡 欢迎关注我的专栏《AI落地笔记》,持续分享一线 AI 工程实践经验。

评论 0

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