第 17 章 · 性能压测与容量规划
本章目标:掌握微服务场景下的性能基线建立方法;使用 k6 或 JMeter 对 svc-spring-demo 关键链路(Gateway → BFF → order-svc)做压测;结合 ch09 Zipkin 与 ch05 熔断定位瓶颈;理解 Little 定律与容量估算公式;制定 HPA 扩容指标(预习 ch19);建立压测环境隔离与数据造数规范。
学时建议:6~7 小时(含 3 小时压测实验与报告)
前置:spring-cloud-web ch01~ch16;spring-boot-web ch17 JPA 性能概念;ch09 链路追踪;ch12 毕业项目可运行。
17.1 为什么微服务更需要压测
单体 shop-spring-demo 压测只测一个进程;svc-spring-demo 一次下单可能经过 6+ 跳:
k6 ──► Gateway ──► BFF ──► order-svc ──► Feign ──► product-svc
│ └──► inventory-svc
└──► Stream ──► RabbitMQ
| 风险 | 无压测 | 有压测基线 |
|---|---|---|
| Gateway 成为瓶颈 | 上线后才发现 | 提前调线程池/限流 |
| Feign 雪崩 | 下游慢拖垮上游 | 配合 Resilience4j 阈值 |
| DB 连接池打满 | 502 难复现 | 压测复现 + 调 HikariCP |
| MQ 积压 | 订单延迟小时级 | 观察 lag 与消费 TPS |
原则:先单服务压测定位上限,再全链路压测验证协作;禁止直接对生产环境施压。
17.2 压测环境与数据准备
17.2.1 环境隔离
压测专用 staging(虚构域名)
├── api-staging.example.com → Gateway
├── 独立 MySQL / Redis / RabbitMQ(勿连开发库)
└── 造数脚本 scripts/seed-loadtest.sql
| 项 | 要求 |
|---|---|
| 数据量 | 商品 ≥1 万、用户 ≥5 千、可下单 SKU 有库存 |
| Token | 预生成 100+ JWT,k6 轮询避免单用户限流 |
| 监控 | Zipkin + Actuator 已开(ch09) |
| 配置 | 与生产拓扑一致、规格可缩小 |
17.2.2 造数示例
-- 虚构:为 product-svc 批量插入
INSERT INTO product (name, price, stock, status)
SELECT CONCAT('load-product-', n), 99.00, 10000, 'ON_SALE'
FROM seq_1_to_10000 n;
订单写路径压测前确认 inventory-svc 库存充足,避免业务失败干扰性能数据。
17.3 压测场景设计(svc-spring-demo)
| 场景 | 接口 | 目标 | 并发建议 |
|---|---|---|---|
| S1 商品浏览 | GET /api/v1/products | 读多写少基线 | 50~200 VU |
| S2 商品详情 | GET /api/v1/products/{id} | 缓存命中 vs 未命中 | 30~100 VU |
| S3 下单写路径 | POST /api/v1/orders | 核心交易 TPS | 10~50 VU |
| S4 BFF 聚合 | GET /bff/v1/orders/{id}/detail(与 ch08 一致) | 多 Feign 扇出 | 20~80 VU |
| S5 混合负载 | 70% 读 + 30% 写 | 接近真实流量 | 按目标 QPS |
SLA 参考(教学虚构,按业务调整):
| 指标 | 目标 |
|---|---|
| P95 延迟 | 读 < 300ms,写 < 800ms |
| 错误率 | < 0.1%(非 429 限流) |
| 下单 TPS | staging 单集群 ≥ 50(记录基线) |
17.4 k6 脚本示例
安装 k6 后,在 svc-spring-demo/scripts/load/ 创建:
// order-create.js — 虚构 api-staging.example.com
import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';
const tokens = new SharedArray('tokens', function () {
return JSON.parse(open('./tokens.json')); // 预生成 JWT 数组
});
export const options = {
scenarios: {
ramp_order: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 20 },
{ duration: '5m', target: 20 },
{ duration: '2m', target: 0 },
],
},
},
thresholds: {
http_req_failed: ['rate<0.01'],
http_req_duration: ['p(95)<800'],
},
};
export default function () {
const token = tokens[__VU % tokens.length];
const headers = {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'X-Tenant-Id': 'tenant-demo',
};
const payload = JSON.stringify({
items: [{ productId: 1001, quantity: 1 }],
});
const res = http.post(
'https://api-staging.example.com/api/v1/orders',
payload,
{ headers }
);
check(res, { 'status 2xx': (r) => r.status >= 200 && r.status < 300 });
sleep(1);
}
运行:
k6 run scripts/load/order-create.js
k6 run --out json=results.json scripts/load/order-create.js
JMeter 等价:线程组 + HTTP 取样器 + CSV 数据源(JWT);GUI 调试后 CLI -n -t plan.jmx 无界面跑。
17.5 指标解读与瓶颈定位
压测时同步观察: