60 路并发出图率从 20% 到 90%:我踩的四个坑
前言
我写了一套串行排队调度模块,上线压测后,8 秒出图率纹丝不动——还是 20%。
这不是一篇"怎么排查出图慢"的技术教程,那种文章 CSDN 上够多了。这篇写的是我在排查过程中犯的四个错:为什么错、错在哪一层、下一次怎么避开。
如果你也在做服务端性能优化,大概率会踩到同类坑——不是因为技术不够,是因为排查路径本身有陷阱。
背景
- 基于 WVP + ZLMediaKit 自研国标视频平台
- 60 路摄像头瞬时并发实时预览
- 压测暴露:8 秒出图率 19%,累计 5596 次点播失败 2584 次,90% 是 RTP 收流超时
- 服务器 CPU/内存/磁盘/网络全程无压力——瓶颈不在本机
全链路耗时拆开一看:WVP 下发 SIP 0~2s,下级应答占大头,TCP 通道建立约 4.5s,ZLMediaKit 首帧仅 1~2s。
延迟卡在下游设备/平台,不是在自家服务里。
坑一:没做对照实验就开始写代码
我做了什么
看到 60 路瞬时并发失败率 80%,第一反应是"并发太高冲击下游了"。于是写了一个 SerialRequestProcessor:有界阻塞队列 + 独立工作线程,把并行请求转成串行、按间隔分批下发。
30ms / 80ms / 100ms 三档间隔可调,队列上限控制,3s 超时直接返回失败。开发 + 自测 + 部署,完整走了一遍。
压测结果
| 下发间隔 | 8 秒出图率 |
|---|---|
| 30ms | 10%~25% |
| 80ms | 0%~20% |
| 100ms | 18%~22% |
| TCP + 100ms | 11%~21% |
无论怎么调,出图率都在 20% 上下,跟不串行的基线一模一样。
我错在哪
我在用写代码替代做实验。
对照实验只需要 30 分钟:开一组保活、关一组保活,同时压测,结果就出来了。但我选了写代码——因为写代码更"可控"。代码写完、跑通、部署,每一环都在我自己掌控的范围内。做对照实验需要协调下游配合——可能要改设备配置、要找运维开权限、要跟其他团队对齐窗口——这些事比写代码更"麻烦"。
工程师的本能是往自己最舒适的方向走。 排查问题时的舒适区就是"我再写个工具/模块/中间件来解决它"。但瓶颈如果在你的控制范围之外,写再多代码都是绕路。
正确做法
在动手写任何优化代码之前,先回答一个问题:"同样的设备、同样的并发,什么条件下它其实是快的?"
这个问题只能用对照实验回答。一行代码都不需要写。
坑二:把"并发冲击"和"吞吐上限"搞混了
我当时的假设
"60 路瞬时并发 → 下游被冲垮 → 串行平滑下发 → 下游处理得过来"
实际发生的事
下游有个固定的单位时间吞吐上限。不管你是一秒内砸 60 个请求过去,还是用 6 秒均匀喂给它,60 个请求的总处理量不变。下游该处理的还是要处理,该排队的还是排队。
串行只是把拥堵往后平移了几秒,没有让拥堵消失。
打个比方:麦当劳厨房一小时最多出 200 个汉堡。你在前台同时来 60 个客人点单,和每隔 6 秒来一个客人——对厨房来说,汉堡还是要做 60 个,速度不会变快。串行调度就是让客人排成一列、间隔入场——看起来有序了,但最后那个人还是要等一样久。
怎么区分两种问题
- 瞬时冲击:下游队列短、处理快,只是同一毫秒来了太多请求 → 串行/削峰有效
- 吞吐上限:下游单位时间只能处理 N 个,总任务量 > N × 窗口 → 串行无效
如果你的链路耗时拆解里,单次处理耗时本身就很大(比如 5s+),那基本就是吞吐问题而不是冲击问题——单个任务就要这么久,来多少都是排队。
坑三:先信了直觉,再看数据——顺序反了
正确顺序
先看数据分布,再形成假设。不同失败类型的占比直接指向不同瓶颈位置:
| 失败分布特征 | 指向瓶颈位置 |
|---|---|
| RTP 收流超时占大头 | 下游媒体处理 |
| SIP INVITE 无响应超时占大头 | 下游信令处理或我方调度 |
| 失败均匀分布在各类 | 网络或系统资源 |
我的实际顺序
我的数据里 RTP 收流超时占 90.2%,SIP 超时仅 3.8%——这意味着信令是通的,媒体数据没来。瓶颈在下游推流侧,不在我方信令调度侧。
这个数据我很早就拿到了。但我没有用它来修正假设,而是继续沿着"并发冲击"的直觉往下走,直到写完了串行模块、压测打了脸才回头。
数据在你手上,但你选择性地看它。 这是一个需要刻意训练的肌肉——拿到数据的第一个动作不应该是"找证据支持我的假设",而是"这个数据如果否定了我的假设,它会怎么否"。
坑四:找到了解法,但没有追问"解法意味着什么"
找到解法
串行方案失败后,回到最基本的问题——同样设备、同样并发,"快"的场景和"慢"的场景差在哪?
两组对照压测:
| 组别 | 8 秒出图率 |
|---|---|
| 无保活(设备无活跃媒体会话) | ~20% |
| 有保活(设备维持常驻录像) | 60%+ |
| 有保活 + 移除串行 | 90% |
差的只有一个变量:设备侧是否已有活跃的媒体通道。
原理
关掉常驻录像,每次预览请求下游都要从头走:SIP 会话创建 → RTP 通道初始化 → 编码器冷启动 → 推流。冷启动的代价在并发场景下被放大。
开了常驻录像,会话和通道长期活跃,编码器始终在工作。点播请求过来,只需要在已有通道上快速切换——跳过了整个初始化链路。
我漏掉的追问
解法找到了,但我当时没继续往下想。真正有价值的问题在后面:
"这个解法暴露了什么?"
暴露了我方架构的一个默认假设:设备默认不休眠、通道即用即建。这个假设在低并发场景下成立,在并发场景下崩溃。不是下游性能有问题,是我方架构在设计之初就没有为"高并发冷启动"这个场景做过应对。
"竞品为什么没这个问题?"
排查后发现:竞品默认开启了设备常驻保活录像,媒体通道始终预热。不是他们技术更强,是他们的默认配置就避开了这个坑。这件事告诉我——工程问题不一定是技术差距,很多是默认配置的差距。 搞清楚竞品怎么配的,比闷头优化代码快十倍。
可复用的排查框架
从这个坑里爬出来之后,我整理了一个四次排查顺序——以后遇到类似问题,按这个来,不再绕路:
第 1 层:系统硬件(5 分钟排除)
top / iostat -x / sar -n DEV
任一持续 > 80% 才进入深入排查。否则直接下一层。
第 2 层:媒体服务
看首帧耗时、缓冲队列深度。本次首帧 1~2s,排除。
第 3 层:业务调度
看并发策略和重试机制。但这一步的核心动作不是"优化调度",是看失败分布:
- RTP 超时多 → 直接跳到第 4 层,不要在调度层浪费时间
- SIP 超时多 → 这层值得深入
第 4 层:下游级联(最常被忽略)
设备/平台并发承载 + 通道保活能力。本次真实瓶颈。
核心原则:失败分布决定排查方向。先看数据分布,再选排查路径。
总结
四个坑,根因是一个:用写代码替代了科学方法。
| 坑 | 本质 | 下一次 |
|---|---|---|
| 没做对照实验就写代码 | 用编码舒适区逃避跨团队协作 | 先回答"它什么时候是快的" |
| 混淆瞬时冲击和吞吐上限 | 没有区分峰值的两种成因 | 看单次处理耗时判断 |
| 先信直觉再看数据 | 证实偏差主导了排查路径 | 先看数据分布再形成假设 |
| 找到解法就停 | 没有追问解法暴露了什么 | 继续问"竞品为什么没这问题" |
文中设备编号、项目地名、内网 IP 均已做脱敏处理。如果你遇到过类似的排查弯路,或者有不同的问题定位思路,欢迎留言讨论。
浙公网安备 33010602011771号