← 返回目录
ap7.1实操⏱ 约 12 分钟

从脚本到服务:同一段代码,10 个人同时用会怎样

实测中位等待 4.6s → 0.8s;以及服务化的三张入场券

🔎 最后验证 2026-08📚 来源:本节并发实测为 ECS 实测(2026-08,标准库 http.server,10 并发请求,脚本与原始输出见本节代码)🧰 Python、DeepSeek API
为什么学这个

到这里你手上有一堆能跑的脚本:会重试、会缓存、会检索、会评测、会降级。但它们全都是"你在终端里敲 python3 xxx.py"。

产品不是这样交付的。这一章把它变成别人能用的东西,而第一步就有个坑:

同一段业务代码,只是包成 HTTP 服务的方式不同,10 个用户同时提问——

  • 单线程服务:中位等待 4.6 秒,最慢的那个等了 8 秒;
  • 多线程服务:中位等待 0.8 秒,最慢 1.1 秒

业务代码一个字没改。第 10 个用户等的那 8 秒,全是排队。

💡 打个比方

你在家做饭,做一道等一道,没问题——只有你一个人吃

开成餐馆之后,10 桌客人同时点菜,你还是一道一道做:第一桌 5 分钟上菜,第十桌等了 50 分钟。菜谱没错,厨房的组织方式错了。

实测:一行服务器类的差别

【单线程 HTTPServer】
   10 个请求全部完成耗时:  8.0s
   单请求等待:最快 0.9s | 中位 4.6s | 最慢 8.0s

【多线程 ThreadingMixIn】
   10 个请求全部完成耗时:  1.1s
   单请求等待:最快 0.8s | 中位 0.8s | 最慢 1.1s

差别只有这一行:

class ThreadedHTTPServer(socketserver.ThreadingMixIn, HTTPServer):
    daemon_threads = True

为什么线程有效:调用大模型的这几秒里,你的进程什么也没干,只是在等网络(ap1.3 讲并发时是同一个道理)。单线程服务器在等的时候不接新请求,于是所有人排队。

要看的指标是"中位等待",不是"总耗时"。总耗时是给你自己看的,中位等待是用户真实的体感——4.6 秒和 0.8 秒是"卡"和"快"的区别。

生产里当然用 FastAPI / Flask + Gunicorn / Uvicorn,不会手写 http.server本节用标准库是为了让你看清里面到底发生了什么——换成任何框架,你要调的还是同一件事:worker 数 / 线程数 / 是不是 async

服务化的三张入场券

脚本崩了只有你看见,服务崩了是所有用户看见。三件事必须做:

① 校验在最前面

if not q:               return self._json(400, {"error": "question 不能为空"})
if len(q) > 500:        return self._json(400, {"error": "question 过长"})

实测输出:

   空 question     → 400 {"error": "question 不能为空"}
   超长 question    → 400 {"error": "question 过长"}
   坏 JSON         → 400 {"error": "bad_json"}

长度上限那一条是在保护你的钱包:没有它,一个用户粘贴 10 万字进来,你就为他付了这笔钱(ap6.4 的配额是按次数,这一条是按体积)。

② 错误码要分清是谁的错

情况返回为什么
参数不对、JSON 坏了4xx是调用方的错,别重试
超配额、限流429让客户端知道该等多久(ap6.4)
上游模型挂了502不是 500——是依赖的服务挂了,不是你的代码崩了
你自己的代码抛异常500这个应该告警(ap7.4)

这个区分不是洁癖:客户端要靠状态码决定"重试还是放弃"(ap1.1),监控要靠它区分"用户输错了"和"我的服务坏了"。全都返 500 的服务,监控面板永远在骗你。

③ 永远不要把异常直接吐给用户

except Exception as e:
    return self._json(502, {"error": "upstream_failed"})   # 不要 str(e)

原始异常里可能带着你的 URL、参数,甚至 key 的片段(ap1.5)。给用户一个稳定的错误码,把细节记进日志。

接口该长什么样

POST /ask
{ "question": "...", "session_id": "..." }        # 请求

{ "answer": "...",                                 # 响应
  "tokens": 123,
  "latency_ms": 812,
  "request_id": "req_a1b2c3",                      # ← 排查问题的钥匙
  "degraded": false }                              # ← 这次是不是降级答的(ap6.1)

三个容易被漏掉的字段:

  • request_id:每个请求一个,同时写进响应和日志。用户来投诉"刚才那次答得不对",没有它你根本找不到是哪次;
  • degraded:让前端知道这次走的是降级链路,可以给个"当前服务繁忙,结果可能不完整"的提示;
  • latency_ms / tokens:前端不一定用,但它们让你的接口自带可观测性(ap7.4)。

🔧 动手做:把你的脚本变成服务(20 分钟)

  1. 跑本节代码,亲眼看中位等待 4.6s → 0.8s
  2. 选一段你最想交付的脚本(建议是 AP4 的 RAG 问答或 AP3 的抽取),包成一个 POST /ask
  3. 加三张入场券:参数校验(含长度上限)、错误码分级(4xx/429/502/500)、异常不外泄。
  4. request_id:生成一个短 id,写进响应 + 写进 ap1.6 的成本流水——这两处能对上,排查就不再靠猜。
  5. 压一下:用本节的并发脚本打 10 个并发,看你的中位等待是多少。如果超过 3 秒,先去看是不是单 worker。
  6. 把 ap6.4 的配额和 ap6.1 的降级接进这个入口——服务化之后,它们才有统一的落点。

做到这里你应该有:一个能并发处理的 HTTP 接口 + 三级错误码 + request_id 贯通 + 一次并发压测数据。

为什么:这一节是整门课的转折点。前面六章你在打磨"一次调用怎么又好又便宜又安全";从这一节开始,你面对的是"很多人同时用"。这两件事的难点完全不同——而绝大多数 AI Demo 死在这个转折点上:在作者电脑上完美,一发出去就卡成一团。

❓ 测验
10 个并发请求,单线程服务总耗时 8.0s、中位等待 4.6s;多线程服务总耗时 1.1s、中位等待 0.8s。为什么线程能带来这么大的差别?
⚠️ 避坑线程不是无限的——它只是把瓶颈往后推了一格

多线程解决的是"等网络时能不能接新请求",没有解决"上游能不能扛住"。并发开太大,你会撞上三堵墙:

① 上游限流:模型服务商的 QPS 上限(ap1.3 实测过,并发 20 已接近收益上限); ② 内存:每个线程一份上下文,长文本场景下涨得很快; ③ 你自己的下游:数据库连接池、向量库、日志写入。

实践做法:进程内并发设一个明确的上限(比如 20-50),超出的排队而不是直接开线程,并且给队列一个最大等待时间——等 30 秒还没轮到,不如立刻告诉用户"现在人多,请稍后",这比让他盯着转圈强(ap6.1 的降级思路在这里同样适用)。

🤖 让 AI 帮你把脚本改成服务
这是我的脚本:【贴代码】。请帮我把它改成一个可上线的 HTTP 服务(用【FastAPI/Flask】),要求:1) 请求/响应的 JSON 结构设计,含 request_id、tokens、latency_ms、degraded 字段;2) 参数校验放在最前面(含长度上限);3) 错误码分级:参数错 4xx、超配额 429、上游失败 502、内部异常 500,异常细节不外泄只记日志;4) 并发配置建议(worker/线程/async 怎么选,上限设多少);5) 一段并发压测脚本,输出中位和 P95 等待时间。
✅ 小结

服务化三件事:并发方式决定用户体感(实测中位等待 4.6s → 0.8s)三张入场券(校验在最前、错误码分清是谁的错、异常不外泄)接口自带 request_id / degraded / tokens

下一节讲多轮对话真正的坑:不是技术难,是钱——每多说一轮,你都要把之前所有内容重新付一遍费。

下一节 → 会话与历史裁剪:多轮对话的成本会失控
🔎 来源与核验· 2 条,点开核对
本节每个关键论断都对应一个可追溯的来源 —— 这是本课程"靠谱、不过时"的底线。
「10 并发请求实测:单线程 HTTPServer 总耗时 8.0s、中位等待 4.6s、最慢 8.0s;多线程 ThreadingMixIn 总耗时 1.1s、中位等待 0.8s、最慢 1.1s;业务代码完全相同」
📚 ECS 实测 serve.py(2026-08,Python 标准库 http.server,deepseek-chat)✓ 已核验 2026-08
「参数校验实测:空 question、超长 question、坏 JSON 均返回 400 及结构化错误体」
📚 ECS 实测 serve.py(2026-08)✓ 已核验 2026-08
智图软件的赞赏码
都看到这了,打个赏呗!
接下来 · ap7.2
会话与历史裁剪:省 token 的那个方案,贵了 54%
继续读下一节 →