ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

DeepSeek API百万级QPS架构设计:从网关到推理层的全链路实践

2026/9/6 16:28:13 拓冰建站 浏览量
DeepSeek API百万级QPS架构设计:从网关到推理层的全链路实践 简介一份聚焦DeepSeek API高并发场景的分布式架构设计文档面向后端开发、架构师及云平台运维人员系统讲解如何构建支撑百万级QPS的API集群。文档从实际业务出发覆盖需求分析与性能评估、集群架构选型、服务器与网络硬件规划、分布式存储系统设计、负载均衡策略、缓存优化、高可用与容错、安全防护及自动化部署测试等完整链路并结合案例展示实践效果适合需要提升大规模API服务能力的技术人员。包体为一个PDF文件大小2.31MB共38页内容完整、目录清晰方便按章节查阅。已有118人学习下载可作为DeepSeek服务化落地与集群扩展的实用参考。1. 先别急着谈架构百万级QPS的需求拆解与目标定义接到“把DeepSeek API服务做到百万级QPS”这个目标时如果第一反应是“GPU不够就加GPU节点不够就加节点”那后面大概率要返工。我最近在整理这套分布式架构方案时踩过不少弯路最深的感触是百万级QPS从来不是机器数量堆出来的而是每一层架构决策叠加出来的。真正动手前先要把“百万级QPS”这个数字拆开看否则后面做的所有设计都是在盲人摸象。1.1 一个反直觉的事实大模型推理的单点QPS并不高很多人对QPS的直觉来自普通Web服务——一个优化得不错的Java服务单机扛几千QPS很常见Nginx单机甚至能扛十万级。但放到大模型推理场景情况完全不一样。一次LLM推理请求要经过提示词预处理、显存分配、逐token自回归解码单个GPU实例的实际吞吐可能只有几十到几百QPS而这已经是连续批处理调度优化之后的结果了。这意味着如果“百万级QPS”指的是“每秒钟有一百万个请求都要触发一次DeepSeek模型推理”那它不是架构问题而是物理极限问题。光靠堆GPU成本也会在第一时间击穿预算。所以我在方案文档第一页就写了一句结论百万级QPS应该定义为“API网关入口QPS”或“平台总吞吐能力”而不是“模型推理QPS”。系统必须有能力接受、校验、路由如此海量的外部请求同时通过缓存、聚合、排队等手段把真正打到模型推理层的请求控制在合理范围内。这是一个“外松内紧”的漏斗式结构。1.2 把百万QPS拆解到每一层才能知道该做什么我会在方案评审时让团队先认同一张目标拆解表所有争议在架构图展开之前就解决掉架构层目标承载能力关键手段DNS/接入层百万级并发连接多区域DNS、四层负载均衡API网关层百万级QPS水平扩展、无状态化、连接复用推理调度层数千级QPS入队连续批处理、动态排队、Token配额缓存层几十万级QPS读Redis集群分片、热点兜底模型推理层数百到数千级QPSGPU池、模型分片、预热这张表的价值在于每一层都知道自己的“真实目标”是什么。网关层冲一百万的分子缓存层用几十万承接推理层守住自己的吞吐上限谁也不用硬扛不属于自己的压力。1.3 不分清QPS口径后面全是糊涂账做这套方案之前我建议先统一三个口径入口QPS外部客户端每秒发起的HTTP/HTTPS请求数包括健康检查、鉴权、指标上报、实际调用。有效推理请求数真正进入模型队列的请求数通常只有入口QPS的几个百分点。Token吞吐量TPM每分钟处理的输入输出Token总量。对LLM服务来说TPM比QPS更能反映真实成本。举个例子入口每秒进来100万请求可能有60万是心跳和鉴权30万命中了缓存或规则兜底最后只有10万进入推理调度调度层再按优先级合并成几千个批处理单元。这时候如果你对研发说“我们要扛百万推理QPS”大家都会懵但如果目标是“入口百万QPS推理层扛住对应比例的Token吞吐”设计起来就有章可循了。2. 五层链路设计从DNS入口到DeepSeek模型推理每一层在扛什么整个集群我习惯用“餐厅运营”来类比门口接待负责迎宾和判断你有没有预约DNS与接入层前台收银确认订单并收费API网关层排号系统决定你什么时候能坐下推理调度层后厨按单批量做菜模型推理层储物柜暂存你的随身物品分布式缓存/状态层。任何一层不合理整条链路都会堵。2.1 接入层先用四层负载把流量均匀撒开最外层不要做任何复杂逻辑职责只有一个快速转发。我选择的是DNS多区域解析加LVS/Keepalived做四层负载均衡后端挂一组Nginx或直接挂网关节点。这里有一个容易被忽略的细节连接数估算。百万级QPS按平均每个请求耗时50ms计算意味着系统同时存活的连接大约在5万到10万级别。如果客户端没有启用连接复用HTTP Keep-Alive每次请求都新建TCP连接那么接入层的每秒新建连接数会直接击穿Linux系统的tcp_max_syn_backlog和文件描述符上限。所以接入层要做两件事打开HTTP/2和Keep-Alive强制要求长连接调大net.core.somaxconn、net.ipv4.ip_local_port_range、fs.file-max等内核参数。这段我放在压测阶段再细说但设计阶段就必须预留余量否则后面压测一上来就“三握手重传、连接被拒”。2.2 API网关层协议适配与流量治理的真正主场接入层后面的API网关是整个方案的核心决策点。无论你选Kong、APISIX、自研Go网关还是云厂商网关核心能力都一样把外部“OpenAI兼容协议”翻译成内部RPC协议同时完成鉴权、计量、限流、路由。DeepSeek API对外兼容主流大模型接口规范但内部调用需要带上模型版本、推理参数、用户上下文等字段这一层不能把外部请求裸转到后端。网关要做一次“瘦身”去掉无用Header给每个请求生成全局唯一的request_id再按用户维度注入配额信息最后才转发到推理调度层。网关还必须无状态。这意味着所有用户Session、Token桶状态都要外置到Redis或分布式内存网格网关节点可以随时杀掉、随时新增。我在方案里给网关设置的副本数基线是20个单个节点按5万QPS规划留出两倍余量。2.3 推理调度层不让任何一个推理节点过载网关后面是调度层它负责三件事把请求按模型版本分组决定进入哪个推理池维护全局请求队列应用动态批处理策略continuous batching把token粒度相近的请求尽量拼到一个batch里提高GPU利用率超时和优先级的控制VIP用户优先插队普通用户排队等待。这一层的架构和消息队列很像但我不建议直接用Kafka当主队列因为有状态、重试语义不一致。我更倾向于用自研的内存队列加Redis持久化兜底或直接用NATS这类轻量消息系统。调度层要单独扛住几十万级QPS的入队操作普通关系型数据库在这里完全不合适。2.4 状态层与缓存层把“记忆力”从服务里剥离所有需要跨请求保留的数据都放这里用户鉴权信息Access Token的校验结果缓存5分钟会话上下文多轮对话的history写入Redis设置TTL限流计数令牌桶或滑动窗口的计数器放在Redis Cluster模型配置版本路由表、推理参数模板放etcd网关和调度层Watch热更新。这一层容易出现“缓存穿透”和“热点Key”问题。热点特别明显——如果某个头部客户的Token被高频校验同一个Key的QPS可能冲到几十万单个Redis分片会直接打满。我的解法是本地缓存Redis多级结构网关节点本地Caffeine缓存放一层Redis再放一层只有本地未命中才去请求Redis把对Redis的压力降低一个数量级。3. 集群部署的骨架K8s节点规划与推理池的无状态化改造链路设计完了接下来回答一个逃不掉的问题这套东西到底布在什么样的集群上3.1 节点分组别把推理节点和网关节点混在一起我在方案里把K8s集群明确分成三组Pool理由很简单GPU资源一旦被非推理任务挤占重新调度的代价极高。节点池机器规格示意用途control-pool8C16G3台起K8s控制面组件、etcdgateway-pool16C32G20台起API网关、调度器、接入Nginxgpu-pool按GPU型号定至少8卡起步DeepSeek模型推理Pod节点池之间用nodeSelector或taint/toleration严格隔离禁止普通工作负载调度到GPU节点。每个GPU池里按模型版本再细分deepseek-v3-pool、deepseek-r1-pool等推理Pod通过nodeAffinity固定到对应池子。这样某个模型版本发新版时可以只滚动升级一个池子不影响其他线上流量。3.2 无状态化改造推理服务也能随时重启很多人觉得“模型推理服务有状态”因为模型参数在显存里。这类说法其实混淆了冷数据和热状态。模型权重是只读的重新加载只需要时间不属于不可丢失的状态。真正有状态的是模型推理过程中的临时上下文和会话数据。我做了一个关键改造所有会话级数据外置到Redis推理Pod本身完全无状态。一个请求进来调度器从Redis拉取该会话的历史记录拼进提示词推理完成后再把新的一句话写回会话存储。这样任何一个推理Pod崩溃只需要重新调度一个Pod并加载模型权重不会丢用户上下文也不用做复杂的Pod原地恢复。无状态化带来的第二个好处是滚动发布变得极简单——新模型版本上线先起两个新Pod跑完健康检查后逐步摘流量整个过程不需要停机。3.3 弹性伸缩的触发指标GPU队列长度比CPU更重要K8s的HPA默认看CPU和内存但在GPU推理池里CPU使用率低不代表机器不忙。真正能反映推理压力的指标是GPU利用率、推理队列深度、单批次的排队延迟。我给推理池配置了基于Prometheus自定义指标的HPA核心触发条件就是“排队请求数超过单节点承载上限”。示意配置如下apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: deepseek-inference-hpa namespace: ai-platform spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: deepseek-inference minReplicas: 10 maxReplicas: 100 metrics: - type: Pods pods: metric: name: inference_queue_depth target: type: AverageValue averageValue: 8 - type: Object object: metric: name: gpu_utilization behavior: scaleUp: stabilizationWindowSeconds: 30 policies: - type: Percent value: 100 periodSeconds: 15这里有两个细节值得注意一是stabilizationWindowSeconds不能设置太长否则流量突增时扩缩容反应太慢二是扩缩容策略用百分比倍速确保能在分钟级翻倍扩容。实际部署时我还给HPA加了冷却保护防止队列一抖动就疯狂扩缩容把集群API Server打爆。3.4 模型热加载与预热避免每次扩容都让用户等半天模型文件动辄几十GB加载到显存需要几分钟甚至十几分钟。如果每次扩出一个新Pod都要现场加载模型扩容基本等于没扩——流量早被打穿了。我的做法是两段式提前把模型镜像预热到节点本地磁盘用DaemonSet在每个GPU节点预拉模型文件新Pod启动后先做一次“模型预热请求”跑一组固定prompt等推理延迟回到正常水平后再把它加入负载均衡后端池。这套机制我另外做了个简单的启动脚本用startupProbe来探测预热完成状态避免K8s在模型还没加载完时就把流量打进来。4. 限流、熔断、降级流量治理三板斧的落地配置与参数选择流量治理是百万级QPS方案的“安全气囊”。很多团队把限流熔断做成了一堆没人调的配置项真正出事时发现参数选得不对该拦的没拦住不该拦的全被误伤。4.1 限流用Token配额对齐真实成本对LLM API来说按请求次数限流RPM是必要的但远远不够。真正的成本单位是Token不是请求次数。一个只问“你好”的请求可能消耗10个Token一个上传三万字的文档分析请求可能消耗四万个Token。按次限流对大请求毫无约束力。所以我在网关层做了两级限流第一级按用户维度限制每秒请求数RPS使用滑动窗口算法第二级按用户维度限制每分钟Token消耗量TPM使用令牌桶。这里我在方案中用Go伪代码示意令牌桶的核心逻辑type TokenBucket struct { rate float64 // 每秒补充的Token数 capacity float64 // 桶容量决定瞬间爆发上限 tokens float64 lastTime time.Time } func (b *TokenBucket) Allow(tokenCount float64) bool { now : time.Now() b.tokens math.Min(b.capacity, b.tokensnow.Sub(b.lastTime).Seconds()*b.rate) b.lastTime now if b.tokens tokenCount { return false } b.tokens - tokenCount return true }参数选择上我的经验是桶容量不能设置得太大。如果你给一个用户的瞬间爆发量是10万Token那就等于给了他一台单机推理的整个显存空间一旦他真的爆发后端根本接不住。容量设置成日常使用峰值的两倍到三倍即可瞬时并发靠排队机制去消化。4.2 熔断保护推理集群不在雪崩中当“替罪羊”限流管住入口熔断管住出口。当推理集群出现异常——比如某个GPU节点故障、模型推理延迟飙升、队列打满——网关如果继续把流量打进去只会让故障扩大。熔断器的核心思想是快速失败而不是排队等待超时。我参考了通用的熔断器语义在网关里实现了三种状态关闭正常转发、打开直接拒绝、半开放少量请求探测恢复。关键的触发参数如下参数建议值说明错误率阈值50%10秒窗口内错误率超过此值触发熔断最小请求数50请求太少时不触发避免抖动误判熔断打开时长10秒熔断后等待时间再进入半开探测半开允许请求数10探测期间最多放过的请求数一个星期内我连续调整了三次这个表最终发现“错误率阈值50%”太激进很多场景下40%就足够说明问题了。另外熔断打开之后不能干等网关要能返回一个明确的HTTP 503和错误码告诉调用方“服务暂时过载请稍后重试”这比一直挂着请求让人家超时要体验好得多。4.3 降级先保体验再保吞吐降级和熔断常被混在一起说但我更愿意把它单独拎出来。因为熔断是被迫的降级是主动的。流量高峰期宁可牺牲一小部分请求的“完美度”也要保住核心可用性。我在方案里设计了三个层级的降级策略小模型兜底当DeepSeek大模型的推理队列超过阈值时将简单请求自动路由到一个小尺寸模型或规则引擎。比如闲聊、翻译、关键词抽取这些对答案质量不敏感的任务小模型完全够用。缓存兜底对改写、润色、摘要类请求如果历史上有语义相似度高于0.92的缓存结果直接返回缓存不进入推理链路。注意这里不能用精确匹配要靠向量检索实现。排队替代拒绝超过配额的用户请求不直接拒绝而是返回一个“任务已接受请稍后轮询结果”的异步凭证。用异步化削峰填谷比硬拒绝体验好很多。降级策略我最怕做成“人手一个开关出事了再开会决定”。正确做法是把降级策略配置化、自动化网关监控模块发现推理队列长度超过阈值自动把降级级别从L0调到L1全程不需要人工介入。5. 压测复盘从“接近百万”到“稳住百万”的瓶颈排查与参数调优方案设计得再漂亮不上压测就是纸上谈兵。这一章我记录几个印象最深的瓶颈和调优过程希望能帮大家少走几个月的弯路。5.1 压测工具的选择单机压测机连“入口”都打不满用wrk压测网关单机轻松可以压出十万级QPS但想压百万QPS单机压测机本身就成了瓶颈——文件描述符上限、网络软中断、压测线程调度都会限制你的施压能力。我的选择是部署一组K8s Job作为分布式压测端每个Pod用k6脚本往同一个网关地址上打流量通过协调器控制总的施压速率。压测拓扑一定要和真实生产保持一致压测端 → DNS/LVS → 网关集群 → 调度层 → 推理池。如果只压网关而mock掉后面所有依赖得到的数据没有任何参考价值。5.2 第一个坑长连接不释放连接数直接打爆压测刚开始没多久LVS层的连接数就冲到了上限大量请求出现“Connection timed out”。排查了半天才发现问题不在LVS而在压测端和网关之间的连接管理。压测客户端发完请求后没有复用连接每条请求都新建TCP连接而且客户端库默认的Keep-Alive超时设置过短连接被频繁关闭和重建。结果就是真正有效的业务流量不高系统全在忙于建立和销毁TCP连接。解决方式有两步压测端开启连接池并调大Keep-Alive复用时间网关侧调大keepalive_timeout并且在Nginx层开启HTTP/2多路复用。5.3 第二个坑热点Key把Redis打挂熔断器开始“误伤”流量跑到六十万QPS左右时突然出现一波错误率飙升。查了监控发现Redis Cluster有个分片的CPU使用率到了100%其他分片都挺闲。定位下来是缓存设计问题所有请求都先查同一个“用户Token黑白名单”的全局Key这个Key的读频率和总QPS成正比。百万级QPS场景下单个Key的访问频率到了一个Redis分片完全扛不住的程度。最后用了三层优化本地缓存前置网关进程内缓存黑白名单TTL设成30秒绝大多数请求不需要打到Redis热点Key拆分把全局Key按用户ID哈希拆成128个分片Key把读压力平摊到多个分片多级缓存Redis前面加了一层进程内缓存命中率提升到95%以上。改完之后同一压测场景下Redis的整体CPU使用率从70%降到了15%。5.4 参数调优的优先级先把内核参数和容量规划做完再谈业务优化压测阶段最容易忽略的是内核参数。一些直接关系到能否撑住百万级连接的关键参数我的调优顺序如下# 1. 文件描述符 fs.file-max 2097152 net.ipv4.ip_local_port_range 1024 65535 # 2. TCP连接队列 net.core.somaxconn 65535 net.ipv4.tcp_max_syn_backlog 65535 # 3. TIME_WAIT 快速回收 net.ipv4.tcp_tw_reuse 1 net.ipv4.tcp_fin_timeout 15 # 4. 连接保活 net.ipv4.tcp_keepalive_time 600 net.ipv4.tcp_keepalive_intvl 30 net.ipv4.tcp_keepalive_probes 3要特别提醒的是tcp_tw_reuse只在客户端主动关闭连接时有效如果是服务端TIME_WAIT过多核心解法还是让客户端复用连接而不是靠内核参数硬撑着。6. 可观测性建设百万级QPS下如何快速定位故障系统规模越大故障排查的成本越高。百万级QPS场景下一个Go panic、一条Redis慢查询、一次GC抖动都可能让体验从“正常”变成“雪崩”。所以可观测性不是锦上添花而是分布式架构的刚性需求。6.1 三类数据必须全量打通指标、日志、链路追踪之前很多团队的做法是各搞一套日志进了ELK指标进了Prometheus/Grafana链路追踪单独搭一套Jaeger。结果查一个问题时三套系统来回切换效率特别低。我这次的方案统一用OpenTelemetry做数据采集MetricsPrometheus协议暴露记录每一层的QPS、延迟、错误率、饱和度Logs结构化JSON日志必须带上request_id、user_id、model_version、latency_ms字段Traces全链路trace从HTTP ingress → 网关 → 调度器 → 推理引擎每跳都记录耗时。两条最重要的原则large-scale系统里日志必须采样trace和log必须用request_id关联。全量记录百万级QPS的log既不现实也没必要我的方案是错误日志全量正常日志按1%采样把成本控制在可接受范围内。6.2 四类核心指标用RED方法管住延迟和错误不用贪多每个服务盯住四个指标就够指标说明告警阈值参考Rate流量每秒请求数、每刻钟Token消耗量突增超过3倍即告警Errors错误4xx/5xx比例、熔断触发次数错误率超过5%持续2分钟Duration延迟P50/P95/P99延迟P99超过500ms持续5分钟Saturation饱和队列深度、GPU利用率、连接数队列深度超过容量80%每一层都要有独立指标不能只监控网关层。网关延迟正常不代表推理层没有在超时边缘挣扎很可能请求都快超时了才被网关兜住。6.3 一次真实排障演示P99延迟突刺是怎么被找到的这套可观测性体系建成后的第一周就发挥了大作用。某天早上监控告警网关P99延迟从80ms飙升到1.2s但平均延迟看起来变化不大如果不看分位数值这个故障很可能被忽略。我直接从Grafana点击异常时间点的P99曲线下钻到关联的trace列表发现所有慢请求都有一个共同特征都在等调度层的队列许可。顺着trace继续看调度层的指标发现队列深度从正常的200暴涨到5000再往下钻发现是早上某头部客户突然推了一波大批量调用TPM瞬间打满。因为限流配额是分钟级的头一分钟没有拦住等到第二分钟配额重新计算后才缓过来。后续我把限流从单纯的“分钟级TPM桶”改成“秒级预检分钟级结算”双桶结构才真正解决了这个突刺问题。最后分享一个我在整个方案落地过程中的体会架构设计文档写得好不好不取决于画了多少层框图而取决于你能不能回答“每一层为什么存在”“每一层怎么扩容”“每一层挂了会发生什么”这三个问题。百万级QPS的DeepSeek API集群部署真正难的不是引入多牛的技术组件而是把每一层的职责边界和故障预案做扎实。这篇方案里的很多参数和配置都是基于实际压测不断调出来的建议大家在复用的时候一定要结合自己的流量模型和模型版本重新验证一遍。本文还有配套的精品资源点击获取