压测记录(脱敏版)

  • 接口路径: GET /api/mp/article/detail?articleUid=<redacted>
  • 鉴权: Authorization: <redacted>(统一使用脱敏 Token 集)
  • 环境: 某 SIT 网络环境(Host/IP 已脱敏)
  • 工具与方法:
    • 客户端:asyncio + aiohttp(Keep-Alive),单进程事件循环
    • 令牌:120 个用户 Token 轮询(脱敏)
    • 场景:基线、爬坡(50→200),高并发(500/800)
    • 指标口径:错误率=HTTP错误/总请求;失败率=客户端异常(连接/超时)/总请求;RPS=总请求/阶段耗时

结果摘要

  • 稳态并发在 100–150 时仍为亚秒级;并发 200 时 P95 ≈ 790ms,整体可接受。
  • 并发 500/800 时长尾显著上升,且出现少量客户端异常(失败率>0)。

指标明细

  • 字段:总请求数、每秒请求数、错误率、失败率、平均响应时间ms、最小响应时间ms、最大响应时间ms、90th响应时间ms、95th响应时间ms、99th响应时间ms
场景 并发 总请求数 每秒请求数 错误率 失败率 平均(ms) 最小(ms) 最大(ms) P90(ms) P95(ms) P99(ms)
基线 20 400 254.69 0.0000 0.0000 74.07 16.17 621.98 137.25 168.26 296.30
爬坡-1 50 1,250 294.20 0.0000 0.0000 164.53 27.34 904.63 249.62 286.85 406.64
爬坡-2 100 2,500 282.53 0.0000 0.0000 345.40 31.17 1,010.93 442.46 503.42 793.78
爬坡-3 150 3,750 301.03 0.0000 0.0000 486.85 155.63 1,530.12 537.52 596.96 1,259.74
爬坡-4 200 5,000 296.42 0.0000 0.0000 660.44 69.68 1,979.16 741.07 790.10 1,577.33
高并发 500 15,000 262.87 0.0000 1.1133% 1,871.58 15.16 10,930.84 1,854.54 2,877.70 10,143.01
高并发(复测) 800 24,000 258.37 0.0000 0.0542% 3,054.24 506.22 10,946.27 3,578.24 3,954.73 6,589.38
高并发(复测2) 800 24,000 273.90 0.0000 2.0750% 2,879.97 15.80 10,969.91 3,223.63 5,056.36 10,127.88

结论与建议

  • 建议“稳定并发”设为 ≈150;并发 200 亦可,但长尾略升高。
  • 并发 ≥ 500 时出现非零失败率与较大长尾,若需更高并发,建议:
    • 采用预热+分阶段爬坡,并开启/优化连接复用;如可能,升级到 HTTP/2 多路复用以降低连接数。
    • 与服务端/网关确认限流与排队策略,优化队列与线程/连接池;关注下游依赖瓶颈。
    • 在网络路径存在代理/NAT 时,建议受控 RPS 压测,避免客户端过载引起的排队放大。

数据合规与脱敏说明

  • 已隐藏 Host、IP 与 Token,文中仅展示相对路径与示例参数。
  • 所有报告中涉及 Authorization、域名与账号信息均已脱敏处理。

代码与说明(脱敏示例)

  • 代码位置(本地工程):
    • Python 压测主脚本:scripts/aiohttp_load_test.py
    • 运行脚本:scripts/run_aiohttp_load_test.sh
  • 运行依赖:
    • Python 3.9+,pip install aiohttp uvloop
  • 运行示例(固定并发与爬坡):
# 固定并发
CONCURRENCY=200 TOTAL=10000 REPORT_DIR=./reports \
python3 scripts/aiohttp_load_test.py

# 爬坡(每步 30s,从 50→200)
RAMP=1 RAMP_START=50 RAMP_STEPS=4 RAMP_INC=50 STEP_DURATION=30 \
REPORT_DIR=./reports \
bash scripts/run_aiohttp_load_test.sh
脱敏版 Python 代码(核心片段)

说明:默认 API_URL 中的 Host 已脱敏,请替换为你的环境;脚本会从 CSV(首行为表头)读取 120 个 Token 并轮询使用。

#!/usr/bin/env python3
import asyncio, csv, json, os, time
from dataclasses import dataclass
from statistics import mean
from typing import List, Optional, Dict

try:
    import uvloop  # 可选:更高性能事件循环
    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
except Exception:
    pass

import aiohttp

def read_tokens(csv_path: str) -> List[str]:
    tokens: List[str] = []
    with open(csv_path, 'r', encoding='utf-8') as f:
        rows = [row for row in csv.reader(f) if any(col.strip() for col in row)]
        if not rows:
            return []
        header_like = any('auth' in c.lower() or 'token' in c.lower() for c in rows[0])
        for row in rows[1 if header_like else 0:]:
            if row and row[0].strip():
                tokens.append(row[0].strip())
    return tokens

def percentile(values: List[float], p: float) -> float:
    if not values: return 0.0
    a = sorted(values); k = (len(a) - 1) * (p / 100.0)
    f = int(k); c = min(f + 1, len(a) - 1)
    return float(a[f] * (c - k) + a[c] * (k - f))

@dataclass
class Result:
    ok: bool
    status: int
    latency_ms: float
    err: Optional[str]

async def fetch(session: aiohttp.ClientSession, url: str, token: str, insecure: bool) -> Result:
    start = time.perf_counter()
    try:
        async with session.get(url, headers={'Authorization': token, 'Accept': 'application/json'},
                               ssl=False if insecure else None) as resp:
            await resp.read()
            return Result(resp.status == 200, resp.status, (time.perf_counter() - start) * 1000.0, None)
    except Exception as e:
        return Result(False, 0, (time.perf_counter() - start) * 1000.0, str(e))

async def run_fixed_load(api_url: str, article_uid: str, tokens: List[str],
                         concurrency: int, total: int, timeout_s: float,
                         insecure: bool, dns_ttl: int) -> Dict:
    started = time.perf_counter()
    connector = aiohttp.TCPConnector(limit=concurrency, limit_per_host=concurrency,
                                     ttl_dns_cache=dns_ttl, enable_cleanup_closed=True,
                                     force_close=False, keepalive_timeout=30)
    timeout = aiohttp.ClientTimeout(total=timeout_s)
    url = f"{api_url}?articleUid={article_uid}"
    sem = asyncio.Semaphore(concurrency)
    lat: List[float] = []; succ = 0; fail = 0; codes: Dict[str, int] = {}
    async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
        async def worker(i: int):
            nonlocal succ, fail
            async with sem:
                r = await fetch(session, url, tokens[i % len(tokens)], insecure)
                lat.append(r.latency_ms); codes[str(r.status)] = codes.get(str(r.status), 0) + 1
                succ += 1 if r.ok else 0; fail += 0 if r.ok else 1
        await asyncio.gather(*[asyncio.create_task(worker(i)) for i in range(total)])
    elapsed = max(1e-9, time.perf_counter() - started)
    exc = codes.get("0", 0); http_err = max(0, fail - exc)
    return {
        'url': url, 'concurrency': concurrency, 'total_requests': total,
        'successes': succ, 'failures': fail, 'error_rate': round(http_err/total, 6),
        'failure_rate': round(exc/total, 6), 'rps': total/elapsed, 'status_count': codes,
        'latency_ms': {'avg': mean(lat) if lat else 0, 'min': min(lat) if lat else 0,
                       'p50': percentile(lat, 50), 'p90': percentile(lat, 90),
                       'p95': percentile(lat, 95), 'p99': percentile(lat, 99),
                       'max': max(lat) if lat else 0}
    }
关键实现说明
  • Token 读取:支持首行表头并自动跳过空行,保证 CSV 兼容性。
  • 连接器配置limit/limit_per_host 设为并发值;keepalive_timeout=30 维持长连;ttl_dns_cache 缓存 DNS 降低开销。
  • 请求与断言:最小化客户端逻辑,仅检查 HTTP 200 并完整读取响应;异常归入状态码 0
  • 度量口径
    • error_rate: 非 2xx 的 HTTP 错误比率(此接口以 200 为准)
    • failure_rate: 客户端异常(连接错误/超时等)比率
    • rps: 总请求数 / 实际阶段耗时
    • 延迟分位:p50/p90/p95/p99avg/min/max
  • 爬坡模式:通过阶段性并发与时长组合,避免队列无限膨胀,便于定位拐点。
  • 报告输出:支持 JSON/CSV;CSV 字段即“指标明细”表中的各列。

脱敏提示:示例代码默认 API_URLhttps://<redacted-host>/api/mp/article/detail,请在本地替换为你的环境域名;Token 文件请使用脱敏数据或受控环境凭证。

脚本设计思路

  • 目标与原则:在单机条件下以最低开销复现真实业务访问模式,快速定位稳定并发与性能拐点;保持脚本简洁、可读、可扩展。
  • 输入与配置
    • Token 管理:从 CSV 读取,自动跳过表头/空行,轮询分配到请求,避免单 Token 限流与缓存偏置。
    • 参数化:支持 API_URLARTICLE_UID、并发/总量/超时、DNS TTL、是否忽略 TLS 校验等,既可命令行也可环境变量。
    • 场景模式:固定负载(并发×总量)与爬坡模式(起始并发、步数、步进、步长时间)。
  • 并发与连接策略
    • 事件循环:优先使用 uvloop(如可用),降低调度与系统调用成本。
    • 连接复用:aiohttp 单 ClientSession + TCPConnector,设置 limit/limit_per_host=并发keepalive_timeout=30s,充分使用 Keep‑Alive。
    • DNS 缓存ttl_dns_cache 降低 DNS 解析频率,稳定长测表现。
    • 限速与排队:通过信号量与任务批量创建控制队列规模,爬坡模式防止瞬时爆量导致拥塞。
  • 请求与容错
    • 轻断言:以 HTTP 200 为成功标准,完整读取响应避免半关闭引发误判。
    • 异常分类:将连接/超时等客户端异常计入状态码 “0”,与服务端错误(非 200)分离,便于分别优化。
    • 最小化开销:禁用多余日志,计算与聚合在内存中完成,降低对结果的干扰。
  • 度量与报告
    • 时延口径:收集 avg/min/maxp50/p90/p95/p99,覆盖“中心趋势+长尾”。
    • 质量口径:区分 error_rate(HTTP 错误)与 failure_rate(客户端异常),定位瓶颈侧(服务端/客户端/网络)。
    • RPS 计算:以阶段实际耗时计算真实吞吐(总请求/阶段耗时)。
    • 落盘:输出 JSON(全量字段)与 CSV(概览字段),方便二次分析与平台展示。
  • 可扩展性
    • 多接口/多参数:可扩展为从用例集/CSV 读取不同路径与参数,模拟混合流量。
    • 采样保存:为慢请求/错误请求保留样本(headers/片段化 body),辅助链路排障。
    • 协议升级:按需接入 HTTP/2 客户端或受控 RPS(令牌桶)模式,验证限流与排队策略。
  • 安全与合规
    • 脱敏输出:屏蔽 Host、IP 与 Token,不在报告中写入敏感字段。
    • 资源保护:默认分阶段爬坡与短时预热,避免对共享环境造成冲击。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐