服务化架构设计:客户端-服务端模型、请求排队与批处理策略、同步与异步接口选择

说到服务化架构,很多同学第一反应就是「把模型包装成一个 HTTP 接口」。嗯,这确实是最直观的做法。但真正上线后你会发现,事情远没那么简单。

我记得第一次做 TensorRT-LLM 服务化时,就踩了个大坑。模型推理本身很快,但客户端一多,延迟直接飙升。后来我才明白——服务化架构的核心不是「怎么调模型」,而是「怎么管请求」

客户端-服务端模型:谁该等,谁不该等?

先聊聊最基础的模型。TensorRT-LLM 的服务化,本质上是一个生产者-消费者模型。

  • 客户端:发送请求,等待结果。它只关心「快不快」。
  • 服务端:接收请求,调度推理。它关心「稳不稳」。

我个人习惯把服务端拆成三层:

  1. 接入层:负责接收请求、鉴权、限流。说白了就是「门卫」。
  2. 调度层:负责请求排队、批处理、资源分配。这是核心。
  3. 执行层:真正跑 TensorRT-LLM 推理的地方。

为什么要拆?因为每一层的关注点不同。接入层要快,调度层要稳,执行层要准。混在一起,你调优都不知道从哪下手。

关键原则:客户端永远不要直接操作 GPU 资源。所有请求必须经过调度层。

我在项目中遇到过一种情况:客户端直接调 TensorRT-LLM 的 Python API,结果多个客户端同时调用,GPU 显存直接爆了。嗯,这就是典型的「架构没设计好」。

请求排队与批处理策略:别让 GPU 闲着

GPU 这玩意儿,最怕的就是「等」。你想想看,一个请求来了,GPU 启动推理,算完,然后闲着。下一个请求来了,再启动。这效率能高吗?

所以我们要做两件事:排队批处理

排队策略

排队不是简单的 FIFO。我建议至少考虑三种策略:

策略 适用场景 缺点
FIFO(先来先服务) 请求量均匀,无优先级 长请求会阻塞短请求
优先级队列 有 VIP 用户或紧急任务 低优先级可能饿死
动态优先级 需要自适应调整 实现复杂

我个人比较推荐「动态优先级 + 超时机制」。什么意思呢?就是每个请求进来时有个基础优先级,但等待时间越长,优先级自动提升。这样既保证了公平,又避免了饿死。

小技巧:排队队列不要用内存无限存储。设置最大队列长度,超出的请求直接返回「服务繁忙」。我曾经因为队列太长,导致服务端 OOM,教训深刻。

批处理策略

批处理是 TensorRT-LLM 提升吞吐量的核心手段。但批处理不是「攒够了再跑」,而是「时机到了就跑」。

我常用的两种策略:

  • 固定窗口批处理:每 10ms 或攒够 8 个请求,就执行一次推理。适合请求量稳定的场景。
  • 动态窗口批处理:根据当前队列长度和 GPU 负载,动态调整批大小。适合流量波动的场景。

这里有个坑要注意:批处理不是越大越好。批处理越大,单个请求的延迟越高。你想想看,一个请求本来 50ms 能搞定,结果为了等批处理,等了 100ms,总延迟 150ms。用户能接受吗?

避坑指南:我曾经把批大小设成 32,结果吞吐量上去了,但 P99 延迟从 80ms 飙到了 500ms。后来我改成动态批处理,根据延迟目标自动调整批大小,才平衡了吞吐和延迟。

同步与异步接口选择:用户要快,系统要稳

这是服务化架构里最纠结的问题。同步还是异步?

先看个对比:

特性 同步接口 异步接口
用户体验 简单直观,等结果 需要轮询或回调
服务端压力 连接保持时间长 连接释放快
适用场景 低延迟、短请求 长文本生成、高并发

我的建议是:能用同步就别用异步。为什么?因为异步接口会增加客户端复杂度。你想想看,客户端发个请求,还得轮询结果,或者等回调。出错了还得重试。麻烦不麻烦?

但有一种情况必须用异步——长文本生成。比如生成一篇 2000 字的文章,可能要 10 秒。如果客户端一直保持连接,服务端资源会被大量占用。这时候用异步,客户端发完请求就走,服务端生成完再通知客户端。

我在项目中遇到过一种折中方案:半同步半异步。什么意思呢?就是客户端发请求时,服务端先返回一个「请求 ID」,然后客户端用这个 ID 轮询结果。但轮询间隔要设计好——太频繁浪费资源,太稀疏用户等得急。我一般设成 100ms 轮询一次,配合指数退避。

核心原则:同步接口适合「快请求」(< 1s),异步接口适合「慢请求」(> 1s)。如果请求时间不确定,用异步更安全。

架构图:服务化核心流程

下面这张图是我自己总结的服务化架构核心流程。你可以看到,从客户端到 GPU,中间经过了接入层、调度层、执行层。每一层都有明确的职责。

TensorRT-LLM 服务化架构核心流程 客户端 接入层 鉴权 / 限流 调度层 排队 / 批处理 资源分配 执行层 TensorRT-LLM GPU 请求路径(实线) 返回路径(虚线) 批处理策略 • 固定窗口:每10ms或攒够N个 • 动态窗口:根据负载自适应 • 注意:批大小影响延迟

这张图里,实线是请求路径,虚线是返回路径。你可以看到,请求从客户端进来,经过接入层、调度层、执行层,最后到 GPU。返回时原路返回。

这里有个细节:调度层和执行层之间,我建议用消息队列解耦。比如用 Redis 或 Kafka。这样即使执行层挂了,请求也不会丢。我在项目中就用 Redis 做队列,效果不错。

总结一下

服务化架构设计,说白了就是三件事:

  1. 分层:接入层、调度层、执行层,各司其职。
  2. 排队与批处理:别让 GPU 闲着,也别让用户等太久。
  3. 同步 vs 异步:快请求用同步,慢请求用异步。不确定就用异步。

嗯,这些就是我这些年踩坑踩出来的经验。你想想看,如果一开始就把架构设计好,后面能省多少事?

最后一个小建议:上线前一定要做压力测试。我曾经在压测时发现,批处理策略在低并发下表现很好,但高并发下反而变差。后来调整了窗口大小才解决。所以,别信理论,信数据。


专注资料整理