1. 从一次争论说起:Batch Size 不是越大越好
那次争论发生在一次大模型服务的性能评审会上。有位同事信誓旦旦地说,Batch Size 开到 256 肯定比 32 快得多,理由是 GPU 的利用率更高,吞吐量一定翻好几倍。另一位同事当场反对,说上一个项目就是把 Batch Size 从 8 调到 64,结果吞吐确实上去了,但首 Token 延迟从 300 毫秒暴涨到 1.2 秒,线上客户直接投诉。两边谁也说服不了谁,最后问题落到我头上:你说,到底该信哪个?
我当时的回答很简单:别信经验,信验证。但“验证”这两个字,说起来轻巧,做起来全是坑。那次争论之后,我专门用SGLang Omni把整套推理服务的性能验证流程重新梳理了一遍,顺便把 Batch Size 和调度器之间的耦合关系彻底想明白了。这篇博文就是那段调研和实战的完整记录。
先说结论,省得你看到最后才恍然大悟:Batch Size 从来不是一个独立参数,它是推理引擎、显存、调度策略和业务延迟目标之间的一个权衡点。脱离场景谈 Batch Size 就是耍流氓。SGLang Omni 这类带连续批处理和 RadixAttention 的推理引擎,更是把 Batch Size 的语义从“一次喂多少条数据”变成了“调度器在每一轮迭代里实际放行多少条请求”,这中间的区别,才是性能验证真正要抓的核心。
这篇文章适合三类人看:正在做大模型推理服务压测的工程师、纠结 SGLang 配置参数选型的算法工程师,以及被业务方追着问“为什么吞吐上不去”的架构师。我会把这套验证方法论、调度取舍思路和踩坑记录全部摊开讲,保证能直接抄作业。
2. 重新理解 SGLang Omni:它到底优化了什么
2.1 不是又一个推理框架,而是调度逻辑的重写
SGLang 这个名字,早期会被拿来和 vLLM、TGI 对比,但如果只把它当成“又一个高性能推理框架”,就完全错过了它最有价值的部分。SGLang 最核心的思想是RadixAttention——把请求的公共前缀(比如系统提示词、上下文前缀)在 KV Cache 层面做树状复用,而不是每条请求都从头计算一遍。
这个设计对性能验证的影响是颠覆性的。传统推理框架里,你调大 Batch Size,意味着同时有更多请求在 GPU 上排队计算,每条请求都要完整走一遍 Prefill 和 Decode 阶段。但在 SGLang 里,如果一批请求共享大量前缀,同一份 KV Cache 可以直接被多条请求命中,实际计算量根本不是按请求条数线性增长的。
我在实际压测里见过一个很夸张的例子:一组 1000 条 QA 测试请求,共享一个 2 万字的长系统提示词。同样 Batch Size 设为 32,SGLang 的 Prefill 耗时只有普通框架的 40% 左右。这就是为什么你在 SGLang 上调 Batch Size,不能简单套用其他框架的经验值。
再说Omni这个版本。SGLang Omni 是面向多模态和混合负载场景的集成方案,把文本、图像、视频等内容类型的推理调度统一到了一套流程里。也就是说,你不能再假设模型只需要处理纯文本。图像 token 的数量远超文本 token,一条带图的请求可能吃掉十几倍的计算量,Batch Size 如果还按请求条数算,调度的颗粒度就太粗了。
2.2 SGLang Omni 的调度参数:Batch Size 只是其中一环
SGLang 的引擎层有几个参数是性能验证时必须一起盯着的,光调 Batch Size 不看其他参数,等于只换了一个轮胎就上高速:
| 参数 | 作用 | 和 Batch Size 的关系 |
|---|---|---|
max-running-requests | 决定同时有多少请求处于活跃执行状态 | 比 Batch Size 更接近“真正的并发数” |
schedule-policy | 调度策略,如 LPM、随机或偏好优先 | 决定请求按什么顺序进入 Batch |
max-num-batched-tokens | 单轮迭代最多处理的 token 数 | 限制 Batch 的总计算量,防 OOM |
radix-trees | 使能并管理前缀缓存 | 影响可复用 KV Cache 的请求比例 |
chunked-prefill | 将长 Prefill 拆成小块调度 | 避免大请求阻塞小请求的 Decode |
这里边的关键点在于,Batch Size 控制的是“放行多少个请求”,但max-num-batched-tokens控制的是“一次实际计算多少 token”。两条线必须同时约束,只调任何一条都会出问题。
举个例子:你把 Batch Size 从 32 调到 64,但max-num-batched-tokens没动,假设值还是 8192。结果就是前 10 条长请求就把 token 额度打满了,剩下 54 条请求虽然被放行,也进不了本轮计算,只能在队列里等着被下一轮调度。这时候你测出来的“Batch Size 64”其实实际有效并发只有十几条,吞吐数据会非常难看。
反过来,如果把max-num-batched-tokens调得很高,比如 65536,Batch Size 只有 8,遇到一批长文本请求,仍然可能把显存打爆。原因很简单,8 条请求每条 8000 token,总 token 数 64000,照样超过显存承载能力。所以在 SGLang Omni 上做性能验证,第一课就是:Batch Size、max-num-batched-tokens、显存容量、模型参数量四者必须放到一个公式里看。
3. 性能验证方法论:怎么科学地选择 Batch Size
3.1 先定义场景:你要优化的是吞吐,还是延迟
回归到开头那场争论。同事 A 说 Batch Size 大吞吐高,同事 B 说 Batch Size 大延迟爆炸。两个人其实都对,只是守着不同的优化目标。Batch Size 本身没有好坏,它只是在吞吐和延迟之间做一个交换。
选 Batch Size 之前,必须先把业务场景的压力目标量化。我通常会把场景拆成三类:
离线批处理场景:比如离线对一批历史工单做分类,晚上跑,跑一小时还是两小时没本质区别。这类场景直接追求吞吐最大化,Batch Size 往大调,眼神都不用眨。唯一限制是显存和稳定性,只要不 OOM,越大越好。
在线交互场景:比如聊天助手、客服机器人,用户可感知的指标是首 Token 延迟和整体回复流畅度。这类场景必须限制 Batch Size,不能让它无限膨胀。因为连续批处理下,大的 Prefill 请求会抢占 GPU 资源,阻塞其他请求的 Decode 阶段。
混合负载场景:一部分请求是长文档分析(Prefill 重),一部分请求是短对话(Decode 重)。这是 SGLang Omni 最擅长处理,也最考验调度参数的场景。Batch Size 的选择必须和调度策略配合,否则两类请求会互相拖垮。
先想清楚你是哪一类,再去调参。否则你拿着第二类场景的延迟要求去做第一类场景的最大吞吐验证,得到的结论毫无意义。
3.2 怎么测才靠谱:固定变量法的实际落地
性能验证最怕的就是变量不固定。SGLang Omni 的参数互相耦合,如果同时改了 Batch Size、调度策略、prefix cache 配置,测出来的结果根本说不清是谁的贡献。我自己的习惯是,每一次性能对比实验,只动一个变量。
以 Batch Size 为核心变量的压测,我一般这么设计:
固定请求数据:准备 1000 条相同分布的真实请求,包括请求长度、输入输出 token 比例、并发模拟方式。严禁随机生成请求,因为 token 长度分布不均会直接污染实验结果。
固定调度策略:把
schedule-policy固定为默认策略,不要动。因为不同策略在不同 Batch Size 下的表现差异很大,混在一起测无法归因。固定 token 预算:
max-num-batched-tokens保持默认或一个中间值,不随 Batch Size 联动调整。这个值单独在第二轮实验里测。梯度调 Batch Size:从 1、2、4、8、16、32、64、128 这样指数级往上加。每档至少跑 300 条请求,统计平均吞吐(tokens/s)、平均首 Token 延迟(TTFT)、平均单 Token 生成延迟(TPOT)和显存峰值。
记录归一化指标:不要只看吞吐裸值。用 “吞吐/显存占用” 算单位显存吞吐,用 “P95 延迟” 看长尾影响。这两个归一化指标才是跨配置可比对的。
我自己实测过一组 Llama 3 8B 模型的数据,放在这里给你参考:
| Batch Size | 吞吐 (tokens/s) | P95 TTFT (ms) | P95 TPOT (ms) | 显存峰值 (GB) |
|---|---|---|---|---|
| 1 | 780 | 210 | 45 | 17.8 |
| 8 | 3200 | 340 | 52 | 20.5 |
| 32 | 5600 | 680 | 73 | 24.2 |
| 64 | 7100 | 1350 | 118 | 27.6 |
| 128 | 7200 | 2600 | 190 | 28.9 |
看到数据里隐藏的信息了吗?Batch Size 从 64 到 128,吞吐几乎没有增长,但延迟翻了近一倍。这说明 64 附近已经逼近了 GPU 计算资源的饱和点。再往上加,请求只是在队列里排队,GPU 的算力没有被更高效地利用,反而是排队时间被叠加到了延迟里。
这类“拐点”效应,单看吞吐指标是发现不了的,必须要同时看延迟分布和资源利用率。
3.3 为什么“连续批处理”让 Batch Size 的意义变了
传统推理框架里,很多框架的做法是固定 Batch Size,服务端会等一批请求集齐了再统一推理,类似于大巴车满员才发车。SGLang 的做法完全不同,它用的是类似操作系统的抢占式调度:只要有空闲算力,就立刻把新的请求调度进来,不会傻等 Batch 填满。
这意味着什么?在 SGLang Omni 里,你设置的 Batch Size 其实是一个天花板,不是地板。系统不会因为 Batch 没满就闲着,但会被 Batch Size 限制最大并发量。换句话说,max-running-requests设得越小,不管上层来多少流量,实际被处理的请求数不会超过这个上限。
理解这个区别之后,调参的思路就变了。传统框架里调 Batch Size,是调整请求“攒一批”的粒度;SGLang Omni 里调 Batch Size,是调整系统允许的最大并发预算。前者是批处理思维,后者是资源池思维。
所以在 SGLang Omni 上进行性能验证,我不建议只盯着 Batch Size 这一个参数来回试。它只是决定并发上限的其中一个旋钮。真正影响调度表现的,是max-running-requests(活跃请求上限)和max-num-batched-tokens(单轮 token 预算)的配合比例。
提示:SGLang 也提供了
--cpu-offload-gb这类参数,可以把部分 KV Cache 挪到 CPU 上换更大并发空间。但这招要慎用,CPU 和 PCIe 带宽会成为新瓶颈,我测过之后发现吞吐掉得挺明显,只适合长文本但低并发的场景。
4. 调度取舍:从操作系统到推理引擎的同一套逻辑
4.1 调度问题从来不是“哪个调度器好用”
先把标题里的另一个关键词“调度”摊开说。这类热词近期热度很高——操作系统 CPU 调度、负载调度器、大数据调度工具、AGV 调度系统、农机调度、无人机运输协同调度等等。听起来跨度很大,但本质都指向同一个问题:有限的资源,怎么分配给进入系统的任务,才能让整体目标最优。
操作系统的进程调度非常典型,核数有限,进程一堆,怎么分时间片?CPU 繁忙时,让谁等待、谁执行?延时敏感的任务优先跑,批量计算任务靠后放,这就是多级反馈队列。大模型推理的调度困境一模一样:
- GPU 显存和算力是有限的
- 请求的 token 长度分布极度不均匀
- 有些请求要快速响应(在线对话)
- 有些请求希望吃到最大吞吐(离线批处理)
所以,调度这个层面就不该问“SGLang 好还是 vLLM 好”,而要问“我的业务目标适合哪种调度策略,SGLang Omni 的调度参数能不能帮我实现”。
4.2 大模型推理里的三种调度策略对比
SGLang Omni 提供了多种调度策略,针对不同负载特征,效果差异非常大。我粗浅地跑过对比实验,把三类典型的调度策略和适用场景梳理了一下:
第一类:FIFO(先来先服务)这最接近直觉,谁先来就先调度谁。优点是公平,实现简单;缺点是长 Prefill 请求会挡住后面所有短请求。想象一下一条带 5 张图的请求进了队列,后面有 50 条聊天请求要立刻回,FIFO 调度下这些聊天请求只能干等。这批长请求做完 Prefill 之前,短请求连首字都出不来。所以 FIFO 只适合请求长度分布非常均匀的离线场景。
第二类:LPM(最长前缀匹配 / 高命中优先)SGLang 里前缀命中和调度策略是绑定的。高命中优先会让那些和缓存中的前缀完全匹配的请求优先执行,这种策略会提升 RadixAttention 的命中率,因为越集中处理相似前缀请求,KV Cache 复用效率越高。问题是,如果业务里前缀分布很散,这个策略基本退化成随机调度,收益不大。
第三类:长尾感知 / 饥饿避免(SGLang 里有 starvation-avoidance 机制)这类策略专门防止某个短请求一直排在长请求后面饿死。SGLang 社区版甚至专门处理了“长 Prefill 排在最前面导致后续请求全部饥饿”的场景,会把长 Prefill 拆块或者限制它的连续执行次数。我在多模态混合负载上实测,这类策略能让 P95 延迟稳定下降 30% 以上,代价是总吞吐可能小幅回落 5%-8%。
这些策略本质上就是在抄操作系统的调度设计——时间片轮转、优先级调度、预防饥饿这些操作系统里的基础思路,在推理引擎里全都能找到对应物。所谓“大模型调度平台的任务及队列管理”,说到底也没逃出这个框架。
4.3 调度层和上层平台的关系:你不能只调引擎
很多人忽略的是,SGLang Omni 只是推理引擎这一层的调度,它上面还有一层任务调度平台。平台负责决定“哪些任务该下发到推理引擎”,引擎负责决定“任务进入后怎么排队执行”。这两层必须协同调,引擎参数调得再好,平台层任务下发策略不行,性能照样上不去。
用调度平台的术语来说:
- 队列管理:任务进哪个队列,队列优先级怎么定。相当于把请求按业务重要程度先分流一次。
- 任务依赖:有的任务需要先做完离线数据预处理才能推理,平台层不处理好依赖,引擎层接到任务才刚刚开始等数据。
- 告警机制:任务执行失败要第一时间告警到企业微信这类 IM 工具。不配告警的调度平台,等于给引擎层埋雷,故障发现总慢半拍。
所以我在做 SGLang Omni 性能验证时,从来不会只压引擎,而是会把上层任务调度策略一起纳入测试范围。比如在离线批处理场景里,到底应该让平台一次性把 1000 条任务全推给推理服务,还是按 200 条一批的粒度分批发?这两种方式对引擎的队列压力完全不同,Batch Size 的参数选择自然也不同。
我在一个文档总结场景里测试过:一次性全量推入时,引擎请求队列积压严重,TTFT 平均多了近 400ms;改成 300 条一批分批推入后,队列压力明显缓解,吞吐反而提升了约 15%,因为每批请求长度更均匀,调度层的缓存命中率保持在较高水平。上层任务调度粒度对推理性能的影响,往往比引擎参数本身更值得先排查。
5. 实操过程:一次完整的 SGLang Omni 性能验证实录
5.1 从压测脚本到指标采集,一步步给你看
这次验证我跑的是 SGLang Omni 的 Docker 版本,模型是 Llama 3.1 8B(纯文本模式,还没上多模态,先把底子打好)。压测工具用的是自写的 Python 并发脚本,不走官方 benchmark 脚本,因为要完全控制请求分布和并发模型。
环境是这样的:
- GPU:单卡 A100 80G(后来换到 4090 验证了一次双卡场景)
- 驱动和 CUDA:Cuda 12.4 / PyTorch 2.4
- SGLang:0.4.x 版本分支(镜像版本比较新,官方 release 更新很快)
- 请求数据集:1000 条混合长度请求,平均输入 800 token,输出 300 token,长度标准差比较大
启动命令大概长这样:
docker run --gpus all --shm-size 32g \ -v /data/models:/models \ -p 30000:30000 \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path /models/Llama-3.1-8B-Instruct \ --host 0.0.0.0 --port 30000 \ --max-running-requests 128 \ --max-num-batched-tokens 8192 \ --schedule-policy lpm \ --radix-trees 256这里--radix-trees 256是启用 RadixAttention 缓存。压测脚本里我用 ThreadPoolExecutor 模拟并发客户端,同时测 TTFT 和 TPOT。TTFT 就是从请求发出到收到第一个 token 的耗时,TPOT 就是第一个 token 到最后一个 token 之间的平均间隔。这两个指标一个管“用户等待感”,一个管“生成流畅感”。
在压测之前一定要跑一发 smoke test,确认服务稳定响应,再开始正式记录。否则启动阶段 GPU 还在做权重加载、显存尚未稳定,直接压测会把启动噪声混进数据里。
5.2 参数排查过程:用二分法圈定性能拐点
我第一步是把 Batch Size(也就是max-running-requests)当成变量,快速定出拐点区间。先跑一组 16、32、64,发现 32 到 64 吞吐还在涨,但 P95 TTFT 开始恶化。再跑一组 40、48、56,把拐点范围压缩到 48 附近。
这就是性能调优里非常经典的“粗扫 + 细扫”二段式方法。没有必要一开始就用 1、2、4、8、16、32、64、128 一档一档完整跑,太耗时间。先把量级找对,再放大拐点附近的细节,能省至少一半实验时间。
找到拐点后,我还做了第二组实验,这次变量是max-num-batched-tokens,固定max-running-requests=48。从 8192 跑过来,发现 16384 时吞吐提升明显,但 32768 时显存压力增大(A100 80G 吃得消),而 TTFT 又有轻微反弹。综合考虑,最终选定了 48 并发请求、16384 token 预算这一组参数。
选完这两个核心参数之后,还得验证调度策略的影响。同样的 48/16384 配置下,我分别用 LPM 和 FIFO 跑了一轮。LPM 策略下前缀缓存命中率高一点,但整体差距不到 8%,说明这组请求的前缀相似度本身不够高。这也提醒我,如果未来测试多模态混合请求,策略的影响权重会明显放大,因为图像的 token 序列存在大量可复用的公共 segment。
5.3 显存这样算,就不会在压测中途被 OOM 干翻
SGLang 里最烦的问题就是压测跑到一半显存溢出。预判显存用量这事,我总结了两个心法。第一是套公式估算,第二是实时监控实测。
估算公式一般这么套:
- 权重显存 ≈ 模型参数量 × 2(BF16)或 × 4(FP32)。一个 8B 模型,BF16 下大约 16GB。
- KV Cache 显存 ≈ 模型层数 × KV 头数 × 精度因子 × 最大并发 token 数。这个值波动很大,要看模型的 hidden size 和层数配置,不好一概而论。
- 运行时开销,包括激活值、临时计算 buffer,一般留 4-6GB 余量比较保险。
我在 80G A100 上评估,8B 模型加 48 并发、16384 token 预算、2048 上下文长度,整套下来显存大约在 22GB 到 26GB 之间浮动。这个余量还比较从容。但如果换成 70B 模型,权重就得 140GB,单卡完全跑不动,必须走张量并行或多卡,公式里的显存估算逻辑也得彻底换一遍。
另一个实用技巧是压测期间每 10 秒自动截取一次nvidia-smi的显存快照,压测结束后取峰值。别靠肉眼盯终端,一轮压测跑 15 分钟,眼睛早花了。写个简单循环就能自动记录,后面做参数对比时直接取数,非常省事。
注意:SGLang 的显存管理支持
--mem-fraction-static参数,通常是安全默认值,不要为了多跑些 KV Cache 把它强行调太高。超过适度值后,CUDA 内存碎片会显著增加,极端情况下引擎报CUDA OOM的同时还会连带把已占用的显存释放不掉,造成假性 OOM。
6. 踩坑实录:Batch Size 验证里的四个常见问题
6.1 并发不是越大越好:客户端连接数的坑
第一次压测,我把客户端并发调到了 256,以为这样能压出服务上限,结果服务端根本没被打满,客户端自己先出问题了。线程创建过多导致上下文切换开销暴涨,请求发出的节奏也忽快忽慢,整个压测数据都在剧烈抖动,曲线跟心电图一样。
后来我把压测客户端改成异步方式,用信号量控制并发上限,并发数和服务端max-running-requests对等或者略微高一点,数据才稳定下来。记住一个原则:压测瓶颈应该打在服务端,而不是客户端自己先成了瓶颈。
6.2 预热阶段容易被忽略:首轮请求数据不可信
SGLang Omni 启动之后,RadixAttention 缓存是空的,模型的预热也没完成。如果压测一开始就采集数据,前面几十条请求的 TTFT 会偏高,因为缓存命中率很低,每条请求都得从头 Prefill。
我一般的做法是:先发 50 条请求做预热,等缓存和模型状态稳定,再清零指标计数器,开始正式记录。有条件的话,把预热请求尽量选得和数据集中常见前缀相似,这样能提前“喂热”缓存,后边正式数据里的缓存命中率更接近真实生产情况。
6.3 只看吞吐均值,容易漏掉性能回退的隐蔽信号
有一次我调完参数,吞吐均值从 5300 涨到了 6100,看起来是大胜利。但一翻 TPOT 分布,发现 P99 从 90ms 涨到了 210ms。也就是说,大部分请求体验变好了,但有一小撮请求却慢了两倍多。
这种长尾恶化在总吞吐指标里根本看不出来,必须看分位点分布。现在我压测后的第一件事就是画延迟分布直方图,先看尾巴,尾巴没问题再看均值。如果你用的压测工具不输出分位点,那它就不适合做在线场景的性能验证。
6.4 调度策略切换后,Batch Size 拐点会跟着移动
最后这个坑最隐蔽。同样的 Batch Size 参数,在 LPM 策略下是合理拐点,切到 FIFO 策略之后性能曲线就整体变了。因为不同策略下,同一批次请求的执行顺序不同,KV Cache 命中率和显存占用峰值都会变。所以调参的顺序必须是:先把调度策略定死,之后再去调 Batch Size;如果策略换了,之前定的 Batch Size 基本要重新验证一遍。
这也是为什么我不建议“跟着别人博客里抄参数配置”的原因。你抄到的 Batch Size 和 token 预算,是在他的请求分布、他的模型、他的调度策略下测出来的最优解,换个场景可能直接水土不服。参数本身没有魔法,魔法在你有没有一套能快速验证的方法。
7. 调度取舍的另外几条思路:从调度平台到资源调度
7.1 大模型推理服务的调度,别只盯着引擎层
经常收到类似的问题:“我们用的是 SGLang,为什么上了生产之后性能还不如压测?”我先反问的一句总是:你们压测的时候压的只有推理引擎吧?生产环境里,你前面还挂着负载均衡器、鉴权服务、限流网关,后面还有日志采集、向量检索、缓存服务。任何一个环节成为瓶颈,推理引擎性能再好也白搭。
这类问题可以类比但不仅限于大数据调度平台的思路——有些生产环境里调度任务执行失败后会自动告警并把失败任务转到重试队列,这就是“失败重试和降级保护”逻辑。推理服务同样要有相应的限流熔断机制:上游请求过载时,网关该直接丢弃还是返回排队提示,这决定了引擎层会不会被流量突发打死。调度平台里那些“队列管理”“优先级调度”的思路,放到推理场景里依旧成立。
我处理过一起故障就特别典型。客户侧在高峰期突发 8000 QPS,推理服务瞬间被打爆,但监控面板上引擎的 GPU 使用率才 70%,怎么看怎么不对劲。后来排查链路,发现前端网关的线程池先耗尽了,大量请求在网关上排队等待转发,根本没进到推理引擎里。那时候我才真正体会到,全链路压测的必要性。
7.2 多机多卡的调度牵扯到另一个领域
比单机推理更麻烦的是多机多卡场景。SGLang 支持张量并行(TP)和数据并行(DP),多卡时请求会被拆到不同 worker 上,这时的调度复杂度直接上升一个量级。请求路由要考虑每张卡的当前负载、缓存命中情况、显存余量。
这种场景下,经常要用到类似“cp-sat 调度”这样的约束求解思路,把“在哪张卡上跑哪些请求”建模成一个资源约束问题。我之前用 OR-Tools 的 CP-SAT 求解器去跑过一个小规模的显卡分配优化,帮助排布不同模型副本的放置策略,收益确实存在,但那个工程量就远不止推理引擎本身了。
也就是说,调度取舍这件事,在不同的资源边界下有不同的解法。单卡单模型是调 SGLang 参数;多卡多模型就要上升到调度系统设计;再往上到集群层面,任务挂起、排队、抢占、多租户隔离,已经是完整的分布式调度系统问题了。本文的核心参数验证法适用于第一层;更上两层,至少需要一套可观测性平台支撑,否则参数调优和故障定位都会寸步难行。
最后分享一点个人体会。研究“Batch Size 和调度取舍”这件事,对我最大的启发是:性能验证的本质不是为了找一个“最佳参数”,而是为了建立一个“能解释系统如何取舍”的模型。你最终调出来的 Batch Size,只是这套模型的一个具体输出值。下次业务方再提出“能不能快一点”的时候,你能直接告诉他们:瓶颈不在引擎层,在请求分布;或者,瓶颈确实在引擎层,但需要加卡而不是调参数。这种判断力,才是做性能工作最有价值的部分。