服务化架构设计:客户端-服务端模型、请求排队与批处理策略、同步与异步接口选择
说到服务化架构,很多同学第一反应就是「把模型包装成一个 HTTP 接口」。嗯,这确实是最直观的做法。但真正上线后你会发现,事情远没那么简单。
我记得第一次做 TensorRT-LLM 服务化时,就踩了个大坑。模型推理本身很快,但客户端一多,延迟直接飙升。后来我才明白——服务化架构的核心不是「怎么调模型」,而是「怎么管请求」。
客户端-服务端模型:谁该等,谁不该等?
先聊聊最基础的模型。TensorRT-LLM 的服务化,本质上是一个生产者-消费者模型。
- 客户端:发送请求,等待结果。它只关心「快不快」。
- 服务端:接收请求,调度推理。它关心「稳不稳」。
我个人习惯把服务端拆成三层:
- 接入层:负责接收请求、鉴权、限流。说白了就是「门卫」。
- 调度层:负责请求排队、批处理、资源分配。这是核心。
- 执行层:真正跑 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,中间经过了接入层、调度层、执行层。每一层都有明确的职责。
这张图里,实线是请求路径,虚线是返回路径。你可以看到,请求从客户端进来,经过接入层、调度层、执行层,最后到 GPU。返回时原路返回。
这里有个细节:调度层和执行层之间,我建议用消息队列解耦。比如用 Redis 或 Kafka。这样即使执行层挂了,请求也不会丢。我在项目中就用 Redis 做队列,效果不错。
总结一下
服务化架构设计,说白了就是三件事:
- 分层:接入层、调度层、执行层,各司其职。
- 排队与批处理:别让 GPU 闲着,也别让用户等太久。
- 同步 vs 异步:快请求用同步,慢请求用异步。不确定就用异步。
嗯,这些就是我这些年踩坑踩出来的经验。你想想看,如果一开始就把架构设计好,后面能省多少事?
最后一个小建议:上线前一定要做压力测试。我曾经在压测时发现,批处理策略在低并发下表现很好,但高并发下反而变差。后来调整了窗口大小才解决。所以,别信理论,信数据。