1. 性能基准与目标:定义评分接口的延迟指标
性能优化不能靠感觉,必须从可量化的指标和可复现的压测开始。本章将定义评分接口的核心延迟指标(TP50、TP99、TP999),设定清晰的优化目标,并搭建标准化的压测环境。这是整个优化旅程的基准线,后续所有优化手段的效果都将以此对比验证。
1.1 核心延迟指标定义
在低延迟场景中,平均延迟(Avg)容易被极端值掩盖,无法真实反映用户体验。我们采用百分位延迟(Percentile)作为核心度量标准,重点关注以下三个分位值:
| 指标 | 含义 | 业务解读 | 典型优化目标 |
|---|---|---|---|
| TP50 | 50% 的请求延迟 ≤ 该值 | 半数用户的体验基线,反映系统正常负载下的响应速度 | ≤ 50ms |
| TP99 | 99% 的请求延迟 ≤ 该值 | 绝大多数用户的体验上限,反映系统在典型压力下的稳定性 | ≤ 150ms |
| TP999 | 99.9% 的请求延迟 ≤ 该值 | 极端尾部延迟,反映系统抗抖动能力和慢请求治理效果 | ≤ 500ms |
实践要点: 优先优化 TP99 和 TP999。TP50 容易通过缓存等手段快速达标,但尾部延迟往往隐藏着数据库连接池耗尽、GC 停顿、网络重传等深层问题,是性能优化的主战场。
1.2 设定量化优化目标
基于当前线上监控数据(假设基线:TP50=120ms, TP99=450ms, TP999=1200ms),我们设定分阶段优化目标:
- 第一阶段(快速见效): TP50 ≤ 50ms,TP99 ≤ 200ms。通过本地缓存、连接池调优、SQL 索引优化实现。
- 第二阶段(深度优化): TP99 ≤ 150ms,TP999 ≤ 500ms。通过异步化、预计算、热点分离、GC 调优实现。
- 第三阶段(极致稳定): TP999 ≤ 200ms,零抖动窗口 ≥ 99.99%。通过多级缓存、熔断降级、零拷贝、线程模型重构实现。
目标设定原则: 每个阶段的目标必须可测量、可验证,且与业务 SLA 对齐。避免设定“提升性能”这类模糊目标,应精确到“TP99 从 450ms 降至 150ms”。
1.3 搭建压测环境
我们选择 wrk 作为主要压测工具(轻量、高性能、适合 HTTP 接口),并辅以 Locust 进行复杂场景模拟。压测环境需与生产环境隔离,但硬件配置、网络拓扑应尽量接近。
1.3.1 压测环境拓扑
1.3.2 wrk 压测脚本
wrk 适合快速压测,使用 Lua 脚本可定制请求参数。以下是一个针对评分接口的压测脚本示例:
-- wrk_benchmark.lua
-- 使用方法: wrk -t8 -c200 -d60s -s wrk_benchmark.lua http://target-host/score
-- 请求参数模板
local request_body = '{"user_id": 12345, "item_id": 67890, "score_type": "comprehensive"}'
-- 初始化随机种子
math.randomseed(os.time())
-- 每个请求前调用
function request()
-- 动态替换 user_id 模拟真实用户
local uid = math.random(1, 100000)
local body = string.gsub(request_body, "12345", tostring(uid))
return wrk.format("POST", "/v1/score/evaluate",
{["Content-Type"] = "application/json"}, body)
end
-- 响应处理(可选,用于统计)
function response(status, headers, body)
if status ~= 200 then
-- 记录非 200 响应,用于错误率统计
-- 实际生产可写入日志
end
end
执行压测命令:
# 8 线程,200 并发连接,持续 60 秒
wrk -t8 -c200 -d60s -s wrk_benchmark.lua http://localhost:8080/v1/score/evaluate
1.3.3 Locust 压测脚本
Locust 适合模拟更复杂的用户行为,如登录、评分、查询混合场景:
# locustfile.py
from locust import HttpUser, task, between
import random
class ScoreUser(HttpUser):
wait_time = between(0.5, 2.0) # 模拟用户思考时间
def on_start(self):
"""每个用户启动时执行一次"""
self.user_id = random.randint(1, 100000)
self.token = self._login()
def _login(self):
"""模拟登录获取 token"""
resp = self.client.post("/auth/login",
json={"username": f"user_{self.user_id}", "password": "test123"})
return resp.json().get("token", "")
@task(3) # 权重 3:评分接口
def evaluate_score(self):
"""评分接口压测"""
headers = {"Authorization": f"Bearer {self.token}"}
payload = {
"user_id": self.user_id,
"item_id": random.randint(1, 50000),
"score_type": random.choice(["comprehensive", "quality", "service"])
}
with self.client.post("/v1/score/evaluate",
json=payload, headers=headers, catch_response=True) as resp:
if resp.elapsed.total_seconds() > 0.5:
resp.failure(f"TP99 超标: {resp.elapsed.total_seconds():.3f}s")
@task(1) # 权重 1:查询历史评分
def query_history(self):
"""查询历史评分"""
headers = {"Authorization": f"Bearer {self.token}"}
self.client.get(f"/v1/score/history?user_id={self.user_id}",
headers=headers)
启动 Locust Web 界面:
# 启动 Web 界面,通过浏览器 http://localhost:8089 控制
locust -f locustfile.py --host=http://localhost:8080 --web-host=0.0.0.0 --web-port=8089
1.4 压测执行与数据采集
压测执行需遵循以下步骤,确保数据可复现:
- 预热阶段: 以 50% 目标并发运行 30 秒,让 JIT、连接池、缓存达到稳定状态。
- 正式压测: 以目标并发(如 200、500、1000)运行 60~120 秒,记录完整延迟分布。
- 数据采集: 收集 wrk/Locust 输出的延迟分布、错误率、吞吐量(RPS)。
- 资源监控: 同步采集 CPU、内存、GC、网络 IO、DB 连接池等指标,用于后续瓶颈分析。
常见陷阱:
- 压测客户端自身成为瓶颈(CPU 打满导致请求发不出去)。建议使用多台压测机,或 wrk 的线程数不超过 CPU 核心数。
- 忽略网络延迟差异。压测机应与目标服务器部署在同一机房或同一 VPC 内,避免公网抖动干扰结果。
- 只压一次就下结论。至少压测 3 次取中位数,排除偶然抖动。
1.5 基准数据记录模板
每次压测后,将结果记录到统一的基准表中,便于后续对比:
| 压测日期 | 并发数 | TP50 (ms) | TP99 (ms) | TP999 (ms) | Avg (ms) | RPS | 错误率 | 备注 |
|---|---|---|---|---|---|---|---|---|
| 2025-03-01 | 200 | 118 | 445 | 1180 | 95 | 2100 | 0.02% | 基线版本 v1.0 |
| 2025-03-01 | 500 | 245 | 890 | 2100 | 210 | 3800 | 0.15% | 基线版本 v1.0 |
| — | — | — | — | — | — | — | — | 优化后填入 |
1.6 延迟分布可视化
为了直观理解延迟分布,我们绘制一个典型的评分接口延迟分布图(基于基线数据):