上周排查一个商品列表接口:P99 1.8s,代码 50ms,DB 30ms,CPU 35%——横向加了实例,单节点出口带宽规格没升,还是慢。
最后发现是出站带宽打满了,利用率 96%。响应体在发送队列里排队,客户端才觉得卡。
这类问题 Java 后端不罕见,排查清单里却常漏「带宽」这一项。
一、带宽打满是什么
带宽 = 单位时间内网络能传多少数据,云主机常见上限按 Mbps(兆比特/秒)卖。
Mbps ÷ 8 = MB/s
100Mbps → 12.5 MB/s
200Mbps → 25 MB/s
打满 = 出站流量长期顶到上限。这时 CPU、内存、DB 可能都很闲,QPS 却上不去——瓶颈在出口带宽,不在业务代码。
上线前可以粗算天花板:
理论最大 QPS ≈ 带宽(MB/s) ÷ 单次响应体(MB)
100Mbps、单次响应 200KB → 大约 62 QPS。再优化代码也突破不了这条线。
二、Java 开发里哪些写法容易踩坑
| 场景 | 典型写法 | 为什么危险 |
|---|---|---|
| 列表一次吐全量 | findAll() |
单次响应 MB 级 |
| 实体整包返回 | 关联集合全塞 JSON | 字段膨胀 |
| 应用层中转文件 | Controller 读 OSS 写回响应 | 文件字节走 ECS 出口 |
| 同步导出大表 | 百万行 Excel 直出 | 长连接 + 大 payload |
| 微服务传大 DTO | Feign/Dubbo 整对象 | 内网带宽也会满 |
| JSON 不压缩 | 明文传输 | 体积多 70% 左右 |
误区 1:只有下载文件才会占带宽。普通 REST 接口,响应体大 + QPS 高,照样能把 100Mbps 打满。
误区 2:图片链接不就是字符串吗?200 条纯 URL 一般也就 几十 KB,单靠字符串很难打满带宽。体积往往出在:
- 图片是对象:
url、thumbUrl、宽高、排序…… - 列表项还带了描述、规格、SKU 等字段
「首屏 20 条、详情再拉剩余」——多数时候是按需加载,不一定是在救带宽。算不算带宽问题,用 响应体大小 × QPS 估一下,接近上限才算。
三、怎么确认是带宽而不是代码
四条里命中三条,就该查带宽:
- CPU / 内存 / DB 指标正常
- 服务端逻辑耗时不长,客户端却觉得慢
- 云监控「外网出带宽利用率」持续 > 80%
- 压测 QPS 到某个值后不再涨
Linux 上看流量:
iftop -i eth0 # 需安装,网卡名按环境替换
nload eth0
云控制台:阿里云 ECS → 网络监控 → 流出带宽;腾讯云 → 外网出带宽利用率。
四、Java 侧怎么改
4.1 响应体做减法
// 别这样:一次全量
@GetMapping("/users")
public List<UserVO> list() {
return userService.findAll();
}
// 分页 + 列表专用 VO
@GetMapping("/users")
public Page<UserListVO> list(@RequestParam(defaultValue = "0") int page) {
return userService.pageList(page, 20);
}
列表 VO 只留展示字段;详情接口再拿完整数据。
4.2 文件字节别过应用层
静态资源走 CDN / OSS,Java 返回链接即可。下面这种会把 ECS 出口带宽吃光:
@GetMapping("/download/{id}")
public void download(@PathVariable Long id, HttpServletResponse resp) throws IOException {
byte[] data = ossClient.getObject(bucket, key);
resp.getOutputStream().write(data);
}
4.3 开 Gzip
server:
compression:
enabled: true
mime-types: application/json,application/xml,text/html,text/plain
min-response-size: 1024
JSON 一般能压掉六成以上体积。
4.4 日志别打大对象
log.info("订单详情:{}", order); // 别这样
log.info("订单创建成功 orderId={}", order.getId()); // 够用
高 QPS 接口避免 INFO 级别输出整对象。
4.5 大导出异步化
提交任务 → 返回 taskId → 后台生成文件上传 OSS → 客户端从 OSS 拉。别在 HTTP 连接里同步吐百万行。
架构层:CDN 扛静态、多实例分摊出口、限流防突发;带宽不够再升规格——最直接,也最贵。
五、排查怎么落地
单次响应 ~2MB(20 张原图信息 + 长描述),200Mbps 理论 QPS 只有十几个。改成分页列表 VO(~80KB)+ CDN + Gzip 后,P99 降到 180ms,带宽利用率 30% 以下。
排查顺序:业务耗时 → DB → CPU/内存 → 带宽。瓶颈不在代码上,就别在代码里死磕。
六、总结
- 先算:
带宽 ÷ 响应体得到 QPS 上限,心里有个数。 - 再看监控:逻辑和 DB 都正常、客户端却慢,查流出带宽。
- 先减 payload:分页、精简 VO、CDN、Gzip——多数情况比升计算规格管用。
参考
- RFC 9110(HTTP Content-Encoding):https://www.rfc-editor.org/rfc/rfc9110.html
- Spring Boot 响应压缩:https://docs.spring.io/spring-boot/reference/web/servlet.html#web.servlet.embedded-container.customizing
- 阿里云 ECS 网络监控:https://help.aliyun.com/document_detail/25482.html