第2474篇:AI驱动的容量预测——用历史数据预测未来资源需求
2026/4/30大约 6 分钟
第2474篇:AI驱动的容量预测——用历史数据预测未来资源需求
适读人群:SRE、运维工程师、架构师 | 阅读时长:约15分钟 | 核心价值:结合时序预测模型和LLM业务分析,实现精准的资源容量规划
容量规划一直是个让人头疼的问题。
资源多买了,浪费钱。资源少买了,高峰期服务挂掉。这中间的平衡点很难把握,而且业务增长是非线性的,今天的数据不一定能代表三个月后的情况。
我们公司之前的容量规划方式是:看去年同期的数据,乘以一个"增长系数"(通常是1.3-1.5,拍脑袋的数字),再加20%的安全余量。这个方法简单粗暴,大多数时候是够用的,但对于有业务快速增长或者有明显促销节点的服务,这个方法就完全失效了。
容量预测的几个层次
层次一:趋势外推。用最近的增长趋势线性外推。简单,但假设太强(增长是线性的)。
层次二:季节性分解。把时序数据分解为趋势+季节性+随机三部分,预测时分别处理。经典的STL分解法,对有规律性周期的业务效果不错。
层次三:机器学习预测。用LSTM、Prophet等模型,能捕获更复杂的非线性规律。
层次四:LLM+业务洞察。在时序预测基础上,引入LLM来分析业务变化对资源的影响——比如"下个季度要搞双十一,同时要上线新功能,这会怎么影响资源需求"。
我们的方案是层次二+层次四的结合:用Prophet做时序预测,用LLM来融入业务规划的影响。
核心实现
1. 历史数据收集和预处理
@Service
public class CapacityDataCollector {
private final PrometheusClient prometheusClient;
private final DataCleaner dataCleaner;
/**
* 收集过去一年的资源使用数据
* 关键指标:CPU使用率、内存使用率、网络IO、QPS
*/
public CapacityDataset collectHistoricalData(
String serviceId,
Duration historicalPeriod) {
Instant end = Instant.now();
Instant start = end.minus(historicalPeriod);
// 按小时采样(对于1年的数据,1小时粒度足够做容量规划)
Map<String, List<TimeSeriesPoint>> metrics = new HashMap<>();
metrics.put("cpu_usage", collectMetric(
"avg(container_cpu_usage_seconds_total{service=\"" + serviceId + "\"})",
start, end, Duration.ofHours(1)
));
metrics.put("memory_usage_gb", collectMetric(
"avg(container_memory_usage_bytes{service=\"" + serviceId + "\"}) / 1073741824",
start, end, Duration.ofHours(1)
));
metrics.put("qps", collectMetric(
"sum(rate(http_requests_total{service=\"" + serviceId + "\"}[5m]))",
start, end, Duration.ofHours(1)
));
// 数据清洗:填充空值,去除明显的异常点(维护窗口期的0值)
Map<String, List<TimeSeriesPoint>> cleanedMetrics = dataCleaner.clean(metrics);
return CapacityDataset.of(serviceId, cleanedMetrics, start, end);
}
private List<TimeSeriesPoint> collectMetric(
String promQuery,
Instant start, Instant end,
Duration step) {
PrometheusRangeQueryResult result = prometheusClient.queryRange(
promQuery, start, end, step
);
return result.getData().getResult().stream()
.flatMap(series -> series.getValues().stream())
.map(v -> TimeSeriesPoint.of(
Instant.ofEpochSecond((long) v[0]),
Double.parseDouble(v[1].toString())
))
.sorted(Comparator.comparing(TimeSeriesPoint::getTimestamp))
.collect(toList());
}
}2. 时序预测(集成Prophet)
用Prophet做时序预测,Java通过Python process调用:
@Service
public class ProphetForecastService {
private final PythonProcessExecutor pythonExecutor;
public ForecastResult forecast(
List<TimeSeriesPoint> historicalData,
int forecastDays) {
// 把数据转换为Prophet需要的格式(CSV)
String csvData = convertToCSV(historicalData);
// 调用Python/Prophet进行预测
String pythonScript = buildProphetScript(forecastDays);
ProcessResult result = pythonExecutor.execute(
pythonScript,
Map.of("input_data", csvData)
);
if (!result.isSuccess()) {
throw new ForecastException("Prophet预测失败: " + result.getError());
}
return parseForecastResult(result.getOutput());
}
private String buildProphetScript(int forecastDays) {
return """
import pandas as pd
from prophet import Prophet
import json
import sys
# 读取输入数据
df = pd.read_csv(sys.stdin)
df.columns = ['ds', 'y']
df['ds'] = pd.to_datetime(df['ds'])
# 配置Prophet模型
m = Prophet(
yearly_seasonality=True,
weekly_seasonality=True,
daily_seasonality=True,
interval_width=0.95 # 95%置信区间
)
m.fit(df)
# 生成未来预测
future = m.make_future_dataframe(periods=%d, freq='H')
forecast = m.predict(future)
# 只返回预测部分
future_forecast = forecast[forecast['ds'] > df['ds'].max()]
result = {
'forecast': future_forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].to_dict('records'),
'components': {
'trend': float(forecast['trend'].iloc[-1]),
'weekly': forecast['weekly'].mean(),
'yearly': forecast['yearly'].mean()
}
}
print(json.dumps(result, default=str))
""".formatted(forecastDays * 24); // 转换为小时数
}
}3. LLM业务影响分析
预测模型只能基于历史数据,但未来的业务变化是预测模型无法知道的:
@Service
public class BusinessImpactAnalyzer {
private final ChatClient chatClient;
/**
* 结合业务计划,调整资源预测
*
* @param baselineForecast 纯时序预测的基线结果
* @param businessPlans 已知的业务计划(促销、新功能上线等)
* @param historicalComparableEvents 历史上类似事件的实际影响数据
*/
public AdjustedForecast adjustWithBusinessContext(
ForecastResult baselineForecast,
List<BusinessPlan> businessPlans,
List<HistoricalEvent> historicalComparableEvents) {
String analysisPrompt = buildAnalysisPrompt(
baselineForecast, businessPlans, historicalComparableEvents
);
ChatResponse response = chatClient.call(new Prompt(
List.of(
new SystemMessage(CAPACITY_ANALYST_PROMPT),
new UserMessage(analysisPrompt)
),
OpenAiChatOptions.builder()
.withModel("gpt-4o")
.withTemperature(0.2f)
.withResponseFormat(new ResponseFormat(ResponseFormat.Type.JSON_OBJECT))
.build()
));
return parseAdjustedForecast(
response.getResult().getOutput().getContent(),
baselineForecast
);
}
private String buildAnalysisPrompt(
ForecastResult baseline,
List<BusinessPlan> plans,
List<HistoricalEvent> historicalEvents) {
StringBuilder sb = new StringBuilder();
sb.append("## 基线预测摘要\n");
sb.append("预测周期: ").append(baseline.getForecastDays()).append("天\n");
sb.append("预测高峰资源需求:\n");
sb.append("- CPU核心数: ").append(baseline.getPeakCpu()).append("\n");
sb.append("- 内存(GB): ").append(baseline.getPeakMemoryGb()).append("\n");
sb.append("- 预测QPS峰值: ").append(baseline.getPeakQPS()).append("\n\n");
sb.append("## 已知业务计划\n");
plans.forEach(p -> sb.append(String.format(
"- [%s到%s] %s: 预计影响类型=%s\n",
p.getStartDate(), p.getEndDate(), p.getDescription(), p.getImpactType()
)));
sb.append("\n## 历史相似事件的实际影响\n");
historicalEvents.forEach(e -> sb.append(String.format(
"- [%s] %s: 流量增长%.0f%%, CPU增长%.0f%%, 持续%d天\n",
e.getDate(), e.getDescription(),
e.getTrafficIncrease() * 100, e.getCpuIncrease() * 100,
e.getDurationDays()
)));
sb.append("\n请分析:\n");
sb.append("1. 每个业务计划预计带来的资源增量(基于历史相似事件)\n");
sb.append("2. 调整后的资源需求峰值(包含业务计划的影响)\n");
sb.append("3. 建议的资源准备时间(提前多久扩容)\n");
sb.append("4. 风险提示(哪些不确定因素可能导致实际超出预测)\n");
return sb.toString();
}
private static final String CAPACITY_ANALYST_PROMPT = """
你是一个有10年经验的系统容量规划专家。
分析业务计划对资源影响时:
1. 参考历史相似事件的实际数据,给出合理的影响估算
2. 考虑叠加效应(多个活动同时进行时,影响可能不是简单相加)
3. 区分流量峰值(短期冲击)和流量增长(长期趋势)
4. 给出保守估计(上界),因为容量不足比容量过剩代价更大
返回JSON:
{
"adjustments": [
{
"plan": "业务计划名称",
"estimatedTrafficIncrease": 0.3,
"estimatedCpuIncrease": 0.25,
"peakDate": "预测峰值日期",
"confidence": 0.7,
"basis": "参考依据"
}
],
"adjustedPeakCpu": 调整后的CPU峰值,
"adjustedPeakMemoryGb": 调整后的内存峰值,
"recommendedScalingTime": "建议扩容时间(距离峰值提前多久)",
"risks": ["风险因素列表"],
"safetyMarginRecommendation": "建议的安全余量百分比"
}
""";
}4. 容量规划报告
@Service
public class CapacityPlanReportGenerator {
public CapacityPlanReport generate(
String serviceId,
ForecastResult baseline,
AdjustedForecast adjusted) {
// 计算当前资源配置是否够用
CurrentCapacity current = getCurrentCapacity(serviceId);
boolean isAdequate = current.getCpuCores() >= adjusted.getAdjustedPeakCpu() * 1.2; // 留20%余量
// 计算建议的资源配置
int recommendedCpuCores = (int) Math.ceil(adjusted.getAdjustedPeakCpu() * 1.3); // 留30%余量
double recommendedMemoryGb = adjusted.getAdjustedPeakMemoryGb() * 1.3;
return CapacityPlanReport.builder()
.serviceId(serviceId)
.analysisDate(LocalDate.now())
.planningHorizon(adjusted.getForecastDays())
.currentCpuCores(current.getCpuCores())
.recommendedCpuCores(recommendedCpuCores)
.currentMemoryGb(current.getMemoryGb())
.recommendedMemoryGb(recommendedMemoryGb)
.isAdequate(isAdequate)
.adjustments(adjusted.getAdjustments())
.risks(adjusted.getRisks())
.forecastConfidence(baseline.getConfidenceLevel())
.build();
}
}预测准确率的实际情况
经过6个月的运行,我们统计了一下预测准确率:
- 30天预测:误差在±15%以内的概率约75%
- 90天预测:误差在±25%以内的概率约65%
- 带业务计划调整的预测比纯时序预测准确率提升了约20%
大促活动(双十一、618)的预测准确率相对差一些,因为每年大促的运营策略不同,历史数据的参考价值有限。对于这类事件,我们的做法是提前做专项的流量压测,而不是只依赖历史数据预测。
