容量评估报告里最常出现的结论不是“容量充足”而是“当前容量在增长模型下不够或者差一点就不够”。真正负责过线上系统的人会理解这种感觉业务流量还在涨机器配置看着不低但每一次大促、版本发布、外部流量导入都会把一个或多个环节打穿。数据库连接数、下游接口超时、宿主机带宽、内存上限、线程池饱和任何一个短板都会让原先“够用”的结论变成“不够”。本文围绕容量评估与扩容决策这条主线展开讲解如何从指标采集、压力测试、趋势预测到扩容迁移和验证报告形成一套可复用的流程。适合需要负责服务稳定性、性能优化或基础设施规划的后端开发、SRE 和运维工程师。读完可以按本文搭出一套最小容量评估环境并把“感觉不够”变成可以量化、可以复查、可以做扩容决策的数据结论。1. 容量评估先要理解“不够”是怎么被定义的1.1 容量不是“能不能跑”而是“有没有余量”系统能启动、接口能返回 200不代表容量充足。容量评估关注的是在可接受延迟、可接受错误率的前提下系统能同时支撑多少业务量以及当业务量继续增长时系统还能支撑多久。一个常见误区是只看当前峰值负载。比如某服务当前 CPU 使用率 70%看起来不高但如果业务量三个月后翻一倍CPU 就会冲过 85%再叠加一次流量抖动延迟就会快速恶化。容量评估要先明确“可接受范围”是什么再判断当前资源位置离这个范围还有多远。容量评估的时间粒度也很重要。秒级数据能反映瞬时峰值分钟级数据能反映平均负载小时级和天级数据才能反映趋势。很多人只看监控大盘的 max 值或 avg 值却忽略了 P95、P99 这类分位数导致评估结果和真实用户体验不一致。1.2 为什么系统几乎永远被认为“不够”真实业务里几乎没有任何系统会在评审时被判定为“非常充足”。原因有三类第一类是需求侧波动。业务量不是均匀增长的大促、广告投放、节假日流量、新功能上线都会带来脉冲式流量。即使按全年平均增长算够用也会被某个事件型波峰击穿。第二类是资源侧的约束。扩容不是简单地加机器还可能受到实例规格、配额、机房容量、预算和采购流程制约。即使技术上可以加机器也可能来不及或者成本预算不允许。第三类是评估方法本身的问题。很多团队没有建立容量模型只凭历史最高峰乘以安全系数来留余量。这个系数一旦取得不科学要么造成资源浪费要么在突发流量到来时仍然缺容量。所以当一个评估报告得出“当前不足或接近不足”的结论时并不一定是系统很差而可能是评估视角从“能不能跑”换成了“够不够稳”。这是容量管理走向成熟的标志。1.3 把“够不够”转成可量化的容量模型容量评估落地前要给“够”下一个可执行的定义。定义至少包含三层容量指标用什么衡量容量例如 QPS、并发数、吞吐量、存储水位。容量阈值达到多少算健康达到多少算危险达到多少算不可用。趋势窗口看多长周期的数据来预测未来例如未来 30 天、90 天、180 天。一个常见的容量模型是围绕目标利用率展开的。目标利用率不是 100%而是留出安全缓冲。例如某服务的 CPU 目标利用率设为 60%当预测到未来 30 天 CPU 会超过 60% 时就触发扩容评估。这种做法比“等 CPU 到 90% 再扩容”更从容因为扩容、发布、验证都需要时间。本节提到的容量模型是整个评估流程的基础。后续所有压测、监控和预测都是为了向这个模型填充数据。2. 搭建最小容量评估环境监控、压测和演示服务2.1 演示环境拓扑为了把容量评估讲清楚下面用一个最小可运行项目演示。项目包含三个部分一个演示业务服务暴露一个简单接口并输出 CPU、内存、QPS 等指标。Prometheus 作为指标采集端定期拉取服务指标。k6 作为压测工具向服务发送模拟请求。这台演示环境的目的是验证容量评估流程不是替代真实压测平台。实际生产环境需要接入公司内部监控、链路追踪和日志平台但流程是一致的。在本地执行前需要先确认以下环境环境项要求说明Docker20.10 及以上用于运行服务、Prometheus 等容器Docker ComposeV2 版本用于编排演示环境k60.44 及以上压测工具也可用 wrk、JMeter 替代Python3.9 及以上用于趋势预测脚本可选注意以下目录结构、镜像版本和配置仅用于演示落地前要结合自己团队的基础设施和版本策略调整。2.2 用 Docker Compose 拉起服务、Prometheus 和业务容器先创建项目目录mkdir capacity-lab cd capacity-lab目录结构建议这样组织capacity-lab/ ├── app.py ├── Dockerfile ├── docker-compose.yml ├── prometheus.yml ├── k6/ │ └── scenario.js ├── analysis/ │ └── forecast.py └── data/写一个简单的 Flask 服务接口模拟一次常见的计算型操作并暴露 Prometheus 指标。这里选择 Flask 是因为容易演示实际项目中的 Spring Boot、Go、Node.js 服务也可以使用对应指标库。# app.py from flask import Flask, jsonify from prometheus_client import start_http_server, Summary, Gauge import time app Flask(__name__) REQUEST_COST Summary(request_cost_seconds, Request cost in seconds) INFLIGHT Gauge(inflight_requests, Current inflight requests) app.route(/health) def health(): return jsonify({status: ok}) app.route(/compute) INFLIGHT.track_inprogress() REQUEST_COST.time() def compute(): # 模拟一次轻量计算大约占用 5 到 10 毫秒 CPU result sum(i * i for i in range(200_000)) return jsonify({result: result, cost: REQUEST_COST._childs.get()._value}) if __name__ __main__: start_http_server(9100) app.run(host0.0.0.0, port8000)这里有两个端口需要注意。8000 是业务接口端口9100 是 Prometheus 指标端口。start_http_server(9100)会在独立线程启动指标服务业务线程继续处理普通请求。Dockerfile 保持简单FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY app.py . EXPOSE 8000 9100 CMD [python, app.py]requirements.txt 内容如下flask3.0.2 prometheus-client0.20.0再写 docker-compose.yml。这里给业务容器设置了 CPU 和内存上限目的是让演示更贴近真实资源受限的场景。version: 3.8 services: app: build: . ports: - 8000:8000 - 9100:9100 deploy: resources: limits: cpus: 1.0 memory: 512M reservations: cpus: 0.5 memory: 256M prometheus: image: prom/prometheus:v2.45.0 ports: - 9090:9090 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.ymlprometheus.yml 内容如下global: scrape_interval: 5s scrape_configs: - job_name: demo-app metrics_path: /metrics static_configs: - targets: [app:9100]注意 Prometheus 在容器网络里访问业务服务要用服务名app:9100而不是localhost:9100。启动环境并确认指标可达docker compose up -d --build curl http://localhost:8000/health curl http://localhost:9100/metrics | head -20如果能看到带request_cost_seconds前缀的指标说明指标链路已经打通。2.3 压测工具选择用 k6 脚本模拟业务流量压测工具可以选 wrk、JMeter、locust 或 k6。这里使用 k6因为脚本语法简单适合做分阶段压力测试。演示脚本设计三个流量阶段平稳低并发、快速抬升并发、回落后平稳。这样模拟一个业务系统从日常负载走向峰值再回落的过程。// k6/scenario.js import http from k6/http; import { check, sleep } from k6; export const options { stages: [ { duration: 30s, target: 20 }, // 低并发 { duration: 30s, target: 80 }, // 峰值并发 { duration: 30s, target: 20 }, // 回落 ], }; const BASE_URL __ENV.BASE_URL || http://localhost:8000; export default function () { const res http.get(${BASE_URL}/compute); check(res, { status is 200: (r) r.status 200, response time 200ms: (r) r.timings.duration 200, }); sleep(0.1); }执行压测前先不加过高并发避免把本机或 Docker 环境打挂。先用 20、40、80 三组跑一遍观察资源变化。k6 run k6/scenario.js2.4 采集指标落库从 Prometheus 查询到本地 CSV压测过程中可以通过 Prometheus HTTP API 拉取指标。下面的脚本按时间区间查询 CPU 使用率、内存使用率和请求耗时保存到 CSV 文件供后续预测脚本使用。#!/bin/bash # collect_metrics.sh END$(date %s) START$((END - 120)) curl -s -G \ --data-urlencode querymax_over_time(rate(process_cpu_seconds_total[1m])[120s:1m]) \ --data-urlencode start$START \ --data-urlencode end$END \ --data-urlencode step5 \ http://localhost:9090/api/v1/query_range实际演示时更稳妥的方式是在应用内直接记录指标或者通过 Prometheus 的查询接口把多个指标导出到 CSV。这里不贴完整导出代码关键点是要统一时间戳和采样步长否则后续趋势预测会失真。注意学习环境和压测环境的结论不能直接替代生产容量评估。本地压测主要确认方法可行生产环境还要考虑真实用户流量模型、缓存命中率、下游依赖和网络延迟。3. 从指标数据到容量结论趋势预测与缺口计算3.1 先确定容量评估的目标利用率在做预测之前要回答一个问题容量阈值定多少每个系统的健康水位不同但一般可以参考以下原则CPU 目标利用率建议控制在 50% 到 70% 之间留出突发流量的缓冲。内存使用率要高一些但也要考虑 GC 或垃圾回收机制带来的抖动。连接池、线程池、文件句柄等资源同样需要设置水位上限。指标危险线建议目标线说明CPU 使用率85% 以上60% 左右超过后延迟会快速上升抢占调度明显内存使用率接近容器限制70% 以下避免触发 OOM 或 GC 抖动P99 延迟超过业务要求低于目标值 80%预留排队和重试空间错误率超过 0.1%趋近 0压测中错误率升高常是容量不足的前兆目标利用率不是固定值。核心链路和中台链路、在线服务和离线任务容忍度完全不同。关键是要在评估前把目标写清楚否则后续所有结论都会失真。3.2 用 Python 做 CPU 和内存趋势预测拿到一份带时间戳的指标 CSV 后可以用简单线性回归预测未来 30 天的资源趋势。这里不追求复杂的时序模型因为对于容量评估来说线性趋势已经能暴露大多数问题。# analysis/forecast.py import pandas as pd from sklearn.linear_model import LinearRegression import numpy as np df pd.read_csv(metrics.csv) df[ts] pd.to_datetime(df[ts]) df[hour] (df[ts] - df[ts].min()).dt.total_seconds() / 3600 features df[[hour]] model LinearRegression() model.fit(features, df[cpu_usage]) # 预测未来 30 天720 小时 last_hour df[hour].max() future_hours np.arange(last_hour, last_hour 720, 24).reshape(-1, 1) predicted model.predict(future_hours) for h, p in zip(future_hours.flatten(), predicted): print(f{h:.1f}h - cpu {p:.2f}%)运行脚本后会看到一条斜率逐渐上升的预测线。如果预测值在 30 天内接近目标利用率就应该准备扩容。这里使用线性回归是为了让方法可理解和可复现不代表所有场景都适用。业务存在明显周期性时需要按周趋势、月趋势做季节性分解或者使用更复杂的时序模型。3.3 计算扩容触发点和缺口有了预测值和目标利用率可以倒推扩容触发时间。最简单的方法是求直线交点扩容触发时间 (目标利用率 - 当前利用率) / 每日利用率增长幅度例如当前 CPU 利用率是 45%目标利用率是 60%每天上涨 0.5%那么(60 - 45) / 0.5 30 天也就是说如果业务保持当前增长速度30 天后需要扩容。这个计算适合快速沟通真正落地还要结合置信区间和不确定性。容量缺口计算则是另一个维度。假设当前单实例能够支撑的峰值 QPS 是 2000未来 30 天预测峰值 QPS 是 5000那么需要的实例数至少要5000 / 2000 2.5个。考虑到集群内单实例故障通常还要额外留一个副本最终数量至少是 3 到 4 个。3.4 输出容量评估表从“感觉不够”到“量化不够”评估结论建议用一张表汇总而不是只写一段文字。表格里至少包含当前水位、目标水位、预测触发时间、建议动作和负责人。服务模块当前资源水位目标水位预测触达时间建议动作优先级订单服务 CPU55%60%21 天后水平扩容到 3 实例高订单服务内存48%70%60 天后暂不处理低用户服务连接池70%60%7 天后调整连接池上限并扩容高网关带宽62%70%45 天后评估带宽包和 CDN中这张表的目的是让决策者可以快速知道哪些资源必须动哪些可以继续观察哪些已经逼近危险线。4. 执行一次完整评估压测、观察、扩容、再压测4.1 准备一个单副本服务的基线数据先让服务以单副本运行使用小并发压测 5 分钟采集“当前无压力”的基线数据。这一阶段要做三件事确认接口正常错误率为 0。记录 CPU、内存、QPS、P99 延迟。保存一份基线 CSV作为后续扩容效果对比。执行命令可以写成BASE_URLhttp://localhost:8000 k6 run k6/scenario.js如果压测过程中出现连接拒绝或请求超时优先检查服务是否因为资源限制被 OOM 或进入频繁 GC。这本身就是容量不足的表现。4.2 执行压测低并发、峰值并发、持续陡增实际的容量评估要做多轮压测而不是只压一次。至少包含三个场景日常流量场景模拟正常业务量例如 20 个并发持续 10 分钟。峰值场景模拟大促或热点事件例如 80 个并发持续 10 分钟。陡增场景从 20 个并发在 1 分钟内拉到 120 个并发观察系统是否快速崩溃。陡增场景最容易暴露容量短板。很多系统在持续高负载下也能撑住但扛不住“突然打过来的一波流量”因为连接池、线程池、缓存预热都没有准备好。4.3 定位短板CPU、内存、连接池、下游压测完成后不要只看“挂了没有”要定位瓶颈在哪里。通常按以下顺序排查看 CPU是否接近容器或宿主机上限。如果 CPU 持续 90% 以上说明计算资源不足。看内存是否持续增长是否出现 GC 频繁或 OOM 日志。看连接池和线程池是否出现连接等待、线程排队、超时异常。看下游依赖如果服务本身资源不高但下游接口变慢瓶颈在下游。如果 CPU 过高优先考虑水平扩容或优化计算。如果内存过高优先考虑内存限制、缓存淘汰策略和日志输出量。如果线程池排队除了扩容还要确认线程池参数是否合理。4.4 扩容方式横向对比垂直扩容、水平扩容、缓存和异步扩容不是只有“加机器”一种办法。不同瓶颈适用的扩容手段不同。扩容手段适用场景优点限制垂直扩容CPU 单核能力不足、内存不足改动小、操作快受单机规格上限限制成本递增水平扩容无状态服务 QPS 增长可线性扩展、便于容灾需要负载均衡、会话和缓存策略配合缓存重复查询多、热点数据多显著降低数据库和计算压力加重缓存一致性、过期和热点问题异步化非核心链路、可延迟操作降低峰值压力增加队列、消费能力和失败重试复杂度在演示服务中/compute是无状态接口最常见的扩容方式是水平扩容也就是增加副本数前面加负载均衡。但在真实业务里如果数据库连接数、分布式锁、幂等表等共享资源没有扩容水平扩容可能导致连接数直接被打满。5. 扩容后的验证与复评报告不能只做一次5.1 对比压测指标扩容后必须用相同脚本、相同参数重新压测否则无法证明扩容有效。对比项至少要包括同并发下的 QPS 和 P99 延迟。错误率是否从高位降回目标区间。CPU 和内存使用率是否降到目标水位以下。扩容后负载均衡和各实例是否均匀分摊流量。如果扩容后指标没有明显改善需要回退变更并重新排查瓶颈是单实例资源还是共享资源。5.2 确认新的容量余量扩容后要重新计算容量余量而不是只看瞬间峰值。例如从单实例扩到三实例理论承载能力提升三倍但共享数据库如果只支持两倍流量真实余量仍受数据库限制。容量评估报告里要写明“本次扩容解决了哪一层瓶颈下一层瓶颈在哪里”。这是最容易忽略的部分。很多人扩容后看到 P99 下降了就认为结束结果下一次流量再涨瓶颈出现在数据库。5.3 把容量评估纳入发布和架构评审容量评估不应该是线上告警后才启动的工作而应该进入常规流程。比较有效的做法是新服务上线前做一次容量评估写明容量模型和触发扩容的条件。核心服务每季度或每半年度做一次容量复评。业务大促、版本发布前做针对性压测并输出容量评估表。架构变更后重跑容量评估确认引入新依赖后容量结论是否变化。6. 容量评估常见的坑和排查路径6.1 压测数据看起来高但生产一启动就撑不住现象压测时 QPS 和 P99 都很好发布到生产后很快出现超时和错误率上升。可能原因压测请求模型和生产流量模型不一致。生产流量有缓存命中、慢客户端、重试风暴、长尾请求等特征。压测没有覆盖下游依赖的真实耗时压测环境的下游往往被 mock 或简化。压测工具所在机器本身成为瓶颈导致施加的压力不够。排查方式对比压测请求和生产请求的请求体大小、HTTP 头、连接复用方式。使用生产流量回放或影子流量验证。检查压测机 CPU、网络和文件句柄是否成为瓶颈。预防建议压测环境尽量与生产环境同规格至少压测机不能先被打满。6.2 监控指标与压测结果对不上现象压测时记录到高 QPS但监控面板显示 QPS 很低或者监控显示 CPU 很高但压测工具报错无法请求。可能原因监控指标抓取周期过长漏掉了瞬时峰值。压测请求没有真正打满服务而是被负载均衡、防火墙或压测工具拦截。指标口径不一致例如把请求数除以了错误的时间窗口。排查方式缩短 Prometheus 拉取周期从 30 秒调整到 5 到 10 秒。同时观察服务端日志、接入层访问日志和压测端统计数据。确认时间戳时区一致统一使用 UTC 或北京时间。6.3 扩容后效果不明显现象从 1 个实例扩到 3 个实例P99 延迟没有下降甚至比扩容前更差。可能原因瓶颈不在计算资源而在数据库、Redis、外部接口等共享服务。负载均衡策略导致流量没有均匀分发一部分实例过载。新实例启动后缓存是空的初期大量请求穿透到下游造成缓存击穿。排查方式观察扩容后每个实例的资源使用率是否均衡。查看下游数据库和缓存的 QPS 与耗时指标。检查新实例启动后的内存和缓存命中情况。预防建议水平扩容前先确认共享依赖的容量并为新实例增加预热步骤。6.4 评估报告只写结论不写前提现象评估报告写着“容量充足”或“容量不足”但没有说明目标利用率、时间窗口、预测模型和使用的压测参数导致上线前无法复核。原因很多评估是临时响应缺乏统一模板。排查方式检查报告是否包含数据来源、目标水位、预测函数、风险假设和复查时间。预防建议使用统一的容量评估表把每次评估的原始数据和脚本归档方便下次复评。7. 容量评估的清单和工程建议7.1 容量评估前置检查清单在开始压测和趋势预测之前先用下面的清单确认准备工作是否到位[ ] 是否定义了目标利用率例如 CPU 60%、内存 70%、P99 200ms。[ ] 是否确认了业务量增长模型例如日均 5% 增长还是固定周期脉冲。[ ] 是否接入统一监控指标是否按秒级或分钟级保存。[ ] 是否记录过基线数据包括无压测、日常流量、历史峰值。[ ] 是否对下游依赖、数据库、缓存、消息队列做了容量摸底。[ ] 是否准备好压测环境和生产环境隔离方案。[ ] 是否明确扩容触发人和审批流程。[ ] 是否计划扩容后的复压和报告归档。这个清单可以在评估前发给相关团队避免到了评审会才发现指标口径不一致。7.2 生产环境建议学习环境里可以只跑一个 Flask 服务但生产环境做容量评估至少要补上以下内容配置外置化压测参数、目标水位、告警阈值不要硬编码在服务里。接入统一的指标平台而不是依赖单机脚本采集。设置容量告警例如 CPU 连续 10 分钟超过目标利用率时触发。区分业务量预测和资源容量预测先预测 QPS再映射到机器规格。保留历史压测报告方便复盘每次扩容决策是否正确。扩容时准备好回滚方案避免配置错误或新增副本导致流量分配异常。7.3 从容量评估向容量成本优化扩展容量评估的下一步不是“评估完就结束”而是把容量管理和成本优化联系起来。一个实例的内存使用率长期只有 10%说明规格偏高一个服务在扩容后 CPU 依然打满说明可能遇到了单线程瓶颈或锁竞争需要进一步做代码优化而不是继续加机器。通过定期复评可以逐步形成一套容量基线库。每个服务的历史目标利用率、实例规格、预测模型和真实峰值都记录在案后续做容量规划时就不需要从零开始。容量评估的本质是建立“数据 - 判断 - 行动 - 验证”的循环。只要循环转起来系统就不会在业务增长面前总是被动响应而是能在“不够”真正发生之前提前给出扩容方案和风险说明。