02 | 工程视角:用微服务架构,将大模型封装为商用级 API 网关

在上一篇《# 01 | 不做算法,做交付:我如何用全栈底子,切入大模型企业级落地?》里,我聊到了传统开发者在 AI 时代的切入点。

今天这篇,咱们不聊高深的算法和微调。我们要做的是:把大模型当做一个需要被严格管控的"超级微服务",给它套上一个企业级的 API 网关。

为什么要做这个?因为在真实的 ToB 交付中,如果让业务代码直接连接底层的模型接口,不仅面临极大的安全裸奔风险,一旦未来底层模型从 Ollama 换成 vLLM 甚至商业大模型,上层业务代码全部要重构。

所以,咱们的第一仗,就是用十几年沉淀的运维和全栈底子,在底层模型和业务系统之间,砌一堵"防撞墙"。

🏗️ 架构设计:用微服务思维解耦

这套架构非常简单,但完全遵循了生产环境的安全隔离原则:

  1. Nginx(边缘层) :处理跨域,反向代理。最重要的是,它必须解决流式输出(SSE)时的数据缓冲问题。
  2. Django(网关层) :负责 API Key 鉴权,统一输入输出标准(完美兼容 OpenAI 格式),屏蔽底层差异。
  3. Ollama(算力层) :藏在 Docker 内部的 internal 网络中,彻底断开外网,只允许网关层访问,防止算力被白嫖或接口被恶意攻击。

底层框架我选了 Django 5.0 + ASGI 异步模式,配合 httpx 库来处理请求。生产环境绝不能拿简单的阻塞式脚本来糊弄。


🔨 实战排雷:那些教程里永远不会告诉你的坑

代码很快就写完了,但这套环境在服务器上跑起来,真的是一波三折。下面是我这半天的真实排错记录,每一个坑都够折腾半天的。

💥 坑一:Docker 镜像拉取的"幽灵超时"

一开始直接跑 docker compose up,结果卡在拉取 ollama/ollama 镜像上,报错 context deadline exceeded。

排查与解决 :一看就是国内网络问题。我先清理了 Docker 悬空镜像和 2.7GB 的构建缓存。配置了加速源还是不行。最后直接换了带前缀的完整镜像源 docker pull docker.1ms.run/ollama/ollama:latest,拉下来后打个官方 Tag 骗过 Compose,才顺利过关。

💥 坑二:Ollama 的启动陷阱与内网预装载

为了做内网隔离,我写了一个部署脚本。原本想通过 docker run --rm ollama/ollama ollama pull qwen 先把模型下到共享卷里。

结果直接报错:Error: unknown command "ollama" for "ollama"。

排查与解决 :查了文档才发现,官方镜像的 Entrypoint 默认就是 ollama,我又加了一个多余的命令。

更重要的是,因为容器的内部网络是断网的,不能直接拉取。最终我改成了"曲线救国":先用宿主机网络起一个临时的后台容器(serve),再通过 docker exec 进去执行 pull。模型下完后销毁临时容器。这样就算以后去了断网的政企机房,也能用这个脚本一键导入模型。

💥 坑三:Nginx 的"吞字"惨案

网关起起来了,鉴权也测通了,结果用 curl -N 测流式输出时,前端死活不出字,等了十几秒,突然一大段话一次性全糊在屏幕上,毫无"打字机"体验。

排查与解决 :老运维一眼就能看穿,Nginx 默认开启了 proxy_buffering。它在等后端把数据全部生成完,才一次性转发给客户端。

在 nginx.conf 里果断加上 proxy_buffering off;,并且强制使用 HTTP/1.1 传输。同时,在 Django 的 StreamingHttpResponse 响应头里加上 X-Accel-Buffering: no。再次测试,完美的流式输出回来了。

💥 坑四:1.8G 内存引发的 OOM 血案(最惨的一步)

前面的坑全填平了,满心欢喜地发出第一次请求,结果终端直接甩脸:

data: {"error": "{"error":"llama-server process has terminated: signal: killed"}"}

排查与解决 :signal: killed 只有一种可能------被 Linux 系统的 OOM Killer 杀掉了。

顺手敲了一下 free -h,好家伙,这台跑着测试环境的服务器,可用物理内存只剩 348M 了。模型一加载,内存瞬间撑爆。

立刻安排急救:用 fallocate 划了 4G 的 Swap 虚拟内存保底;然后去 docker-compose.yml 里限制 Ollama 的资源占用(设置 OLLAMA_NUM_PARALLEL=1 限制并发,把上下文长度砍到 1024)。

重启,再次 curl,终于稳稳地活下来了。


🎯 最终验收:这就是交付的底气

1. 鉴权防线拦截

不带 Token 直接裸奔请求?抱歉,网关直接打回原形,返回 401 Unauthorized。这道门禁,替企业挡掉了所有非法的算力白嫖。

2. 标准流式透传

带上 Token,再次请求。看着终端里一行行规整地吐出 data: {"choices":[{"delta":{"content":"春"}}]},一块块流式数据完美穿过 Nginx,穿过 Django 网关,呈现在眼前。这一刻,这套网关算是彻底立住了。


💡 总结一下

弄完这一套,体会是:无论 AI 多么"高大上",当它被放进生产环境时,它就是一个吃显存、吃内存、需要安全隔离和限流的"服务端进程"。

高可用架构、容器隔离、网络穿透、甚至 Swap 虚拟内存,这些老掉牙的工程基座,在 AI 时代依然是一切的基石。

下一篇,我准备在这个网关后面接入 Qdrant 向量数据库,给它加上"外挂记忆"(RAG)。这堵墙既然砌好了,接下来就该上点真家伙了。

(本项目已开源在 Gitee,包含 deploy.sh 一键部署脚本,感兴趣的朋友可以拿去跑跑看。)

相关推荐
SL_staff2 小时前
制造业订单交付总踩坑?用结构化节点协同模型重构计划系统
java·开源·全栈
前端双越老师11 小时前
程序员 1v1 访谈系列第九期:要不要晋升管理层?
agent·ai编程·全栈
vivo互联网技术1 天前
从知识沉淀到编程范式:AI Coding 项目冷启动落地实践
ai编程·全栈·vibecoding
2601_967987371 天前
Next.js 文档
react·开发·全栈·文档·next.js
挖掘狂人2 天前
全栈正在杀死前端?被淘汰的从来不是岗位,只会是执行层
前端·ai编程·全栈
codigger2 天前
前端没有被杀死,被杀死的是"只会把设计稿变成页面"
前端·ai·ai编程·编程语言·全栈·#人工智能
前端双越老师2 天前
程序员 1v1 访谈系列第八期:大龄女程序员的困境
agent·ai编程·全栈
SL_staff3 天前
运维救火实战:钉钉API变更致薪资计算中断,如何用可视化逻辑引擎5分钟热修复
java·开源·全栈
mantou1323 天前
从纯 CSR 到 SSR:用 WebComponents 做一个 Next.js 式框架 Gemkit
前端·全栈·web components