第354篇:网络流量建模与容量规划实战案例

关键词

流量建模、容量规划、流量矩阵、趋势预测、峰值分析、利用率、带宽规划、同比增长


一、案例背景

1.1 为什么需要流量建模

某数据中心流量建模需求:

痛点:

❌ 带宽扩容凭经验("感觉快满了") ❌ 扩容后不久又满(低估增长) ❌ 有些链路常年空闲(资源浪费) ❌ 故障时才知道哪里容量不足 ❌ 缺乏数据支撑决策

目标:

✅ 建立流量基线 ✅ 预测未来 6-12 个月带宽需求 ✅ 识别瓶颈链路 ✅ 优化链路利用率 ✅ 数据驱动的容量规划

1.2 数据采集

流量数据采集基础:

采集点:

采集对象 频率 方法 接口利用率 5 分钟 SNMP 接口流量 5 分钟 SNMP/Telemetry 端口错误包 15 分钟 SNMP 流(Flow) 5 分钟 NetFlow/sFlow CPU/内存 5 分钟 SNMP

数据存储: ┌──────────────────────────────────────────┐ │ | 时序数据库:InfluxDB(5 分钟粒度) │ │ | 长期存储: 30 天全量 + 1 年聚合 │ │ | 聚合策略: 1 小时 → 1 天 → 1 周 │ └──────────────────────────────────────────┘


二、流量模型建立

2.1 关键指标定义

流量建模关键指标:

基础指标:

Avg(平均利用率): 5 分钟粒度的平均值,反映整体负载 P95(95 百分位): 排除了 5% 的极端峰值 比 Max 更稳定,用于容量规划 P99(99 百分位): 接近峰值,用于评估突发能力 Max(峰值利用率): 最高点,用于过载判断 可能受异常流量影响

派生命名:

日峰值:每天 P95 值 周峰值:本周日峰值的 P95 月峰值:本月日峰值的 P95 增长趋势:月峰值环比增长率 同比增长:今年 vs 去年同月

2.2 流量建模工具

#!/usr/bin/env python3
"""
网络流量建模与容量规划工具
"""

from dataclasses import dataclass
from typing import List, Dict, Tuple
from datetime import datetime, timedelta
import math
import json
import random


@dataclass
class LinkSample:
    """链路采样数据"""
    timestamp: datetime
    in_bps: float
    out_bps: float
    in_util_pct: float
    out_util_pct: float


@dataclass
class LinkStats:
    """链路统计"""
    link_name: str
    capacity_gbps: int
    samples: List[LinkSample] = None

    def __post_init__(self):
        if self.samples is None:
            self.samples = []

    def add_sample(self, sample: LinkSample):
        self.samples.append(sample)

    def get_daily_p95(self, date: datetime) -> float:
        """获取每日 P95 利用率"""
        day_samples = [
            s for s in self.samples
            if s.timestamp.date() == date.date()
        ]
        if not day_samples:
            return 0.0

        utils = sorted([s.in_util_pct for s in day_samples])
        idx = int(len(utils) * 0.95)
        return utils[idx]

    def get_monthly_p95(self, year: int, month: int) -> float:
        """获取月度 P95"""
        month_samples = [
            s for s in self.samples
            if s.timestamp.year == year
            and s.timestamp.month == month
        ]
        if not month_samples:
            return 0.0

        utils = sorted([s.in_util_pct for s in month_samples])
        idx = int(len(utils) * 0.95)
        return utils[idx]


class TrafficModel:
    """流量模型"""

    def __init__(self, history_months: int = 12):
        self.links: Dict[str, LinkStats] = {}
        self.history_months = history_months

    def add_link(self, link: LinkStats):
        self.links[link.link_name] = link

    def forecast_linear(self, monthly_util: List[float],
                        forecast_months: int = 6) -> List[float]:
        """线性预测"""
        if len(monthly_util) < 3:
            return []

        # 简单线性回归
        n = len(monthly_util)
        x = list(range(n))
        y = monthly_util

        x_mean = sum(x) / n
        y_mean = sum(y) / n

        # 斜率
        numerator = sum((xi - x_mean) * (yi - y_mean)
                       for xi, yi in zip(x, y))
        denominator = sum((xi - x_mean) ** 2 for xi in x)
        slope = numerator / denominator if denominator != 0 else 0

        # 截距
        intercept = y_mean - slope * x_mean

        # 预测
        forecasts = []
        for i in range(1, forecast_months + 1):
            predicted = slope * (n + i - 1) + intercept
            forecasts.append(max(0, predicted))

        return forecasts

    def forecast_exponential(self, monthly_util: List[float],
                             forecast_months: int = 6) -> List[float]:
        """指数预测(增长更快时用)"""
        if len(monthly_util) < 3:
            return []

        # 计算平均增长率
        growth_rates = []
        for i in range(1, len(monthly_util)):
            if monthly_util[i - 1] > 0:
                rate = monthly_util[i] / monthly_util[i - 1]
                growth_rates.append(rate)

        if not growth_rates:
            return []

        avg_growth = sum(growth_rates) / len(growth_rates)

        # 预测
        forecasts = []
        last_value = monthly_util[-1]
        for i in range(forecast_months):
            predicted = last_value * (avg_growth ** (i + 1))
            forecasts.append(min(predicted, 100))  # 上限 100%

        return forecasts

    def generate_capacity_plan(self,
                               warning_threshold: float = 70,
                               critical_threshold: float = 85,
                               forecast_months: int = 6):
        """生成容量规划"""
        plan = []

        for link_name, link_stats in self.links.items():
            # 获取历史月度数据
            now = datetime.now()
            monthly_utils = []

            for i in range(self.history_months):
                m = now.month - i
                y = now.year
                if m <= 0:
                    m += 12
                    y -= 1
                util = link_stats.get_monthly_p95(y, m)
                monthly_utils.insert(0, util)

            # 预测
            linear_forecast = self.forecast_linear(
                monthly_utils, forecast_months
            )
            exp_forecast = self.forecast_exponential(
                monthly_utils, forecast_months
            )

            # 判断是否需要扩容
            current_util = monthly_utils[-1] if monthly_utils else 0
            peak_forecast = max(
                linear_forecast[-1] if linear_forecast else 0,
                exp_forecast[-1] if exp_forecast else 0
            )

            needs_upgrade = False
            urgency = "OK"
            recommendation = "无需变更"

            if current_util >= critical_threshold:
                needs_upgrade = True
                urgency = "紧急"
                recommendation = "立即扩容"
            elif current_util >= warning_threshold:
                needs_upgrade = True
                urgency = "计划"
                recommendation = f"在未来 {forecast_months} 个月内扩容"
            elif peak_forecast >= warning_threshold:
                needs_upgrade = True
                urgency = "预警"
                recommendation = f"建议在 {forecast_months // 2} 个月后评估"

            # 建议扩容大小
            suggested_capacity = link_stats.capacity_gbps
            if needs_upgrade:
                # 扩容到预测峰值的 1.5 倍
                target_util = 50  # 目标利用率 50%
                needed_bps = (
                    peak_forecast / 100 * link_stats.capacity_gbps
                    * 1e9 * (100 / target_util)
                )
                suggested_capacity = math.ceil(
                    needed_bps / 1e9 / 10
                ) * 10  # 按 10GE 取整

            plan.append({
                "link": link_name,
                "capacity_gbps": link_stats.capacity_gbps,
                "current_util_pct": round(current_util, 1),
                "trend": (
                    f"{monthly_utils[-1] - monthly_utils[0]:.1f}%"
                    if len(monthly_utils) >= 2
                    else "N/A"
                ),
                "forecast_6m_pct": round(peak_forecast, 1),
                "urgency": urgency,
                "needs_upgrade": needs_upgrade,
                "recommendation": recommendation,
                "suggested_capacity_gbps": suggested_capacity,
                "monthly_history": [
                    round(u, 1) for u in monthly_utils
                ],
                "forecast": [
                    round(f, 1) for f in linear_forecast
                ]
            })

        return plan

    def print_plan(self, plan):
        """打印规划报告"""
        print(f"""
容量规划报告
{'=' * 70}

报告时间: {datetime.now().isoformat()}
预测周期: 6 个月
告警阈值: 70% (预警) / 85% (紧急)

链路容量规划:
""")

        for item in plan:
            urgency_map = {
                "OK": "✅",
                "预警": "🟡",
                "计划": "🟠",
                "紧急": "🔴"
            }
            symbol = urgency_map.get(item["urgency"], "❓")

            print(f"  {symbol} {item['link']}")
            print(f"     当前: {item['capacity_gbps']}GE, "
                  f"利用率: {item['current_util_pct']}%")
            print(f"     趋势: {item['trend']}")
            print(f"     预测 6 月后: {item['forecast_6m_pct']}%")
            print(f"     建议: {item['recommendation']}")
            if item["needs_upgrade"]:
                print(f"     建议扩容至: {item['suggested_capacity_gbps']}GE")

        # 统计
        urgent = sum(1 for p in plan if p["urgency"] == "紧急")
        planned = sum(1 for p in plan if p["urgency"] == "计划")
        warning = sum(1 for p in plan if p["urgency"] == "预警")
        ok = sum(1 for p in plan if p["urgency"] == "OK")

        total = len(plan)
        print(f"""
统计:
  总链路数: {total}
  紧急扩容: {urgent} ({urgent / total * 100:.0f}%)
  计划扩容: {planned} ({planned / total * 100:.0f}%)
  预警评估: {warning} ({warning / total * 100:.0f}%)
  正常链路: {ok} ({ok / total * 100:.0f}%)
""")


def generate_mock_data():
    """生成模拟数据"""
    now = datetime.now()
    links = []

    for link_name, base_util, capacity in [
        ("Spine-1_to_Leaf-1", 60, 100),
        ("Spine-1_to_Leaf-2", 45, 100),
        ("Spine-2_to_Leaf-1", 75, 100),
        ("Spine-2_to_Leaf-2", 35, 100),
        ("Core_to_ISP-1", 80, 100),
        ("Core_to_ISP-2", 40, 100),
    ]:
        stats = LinkStats(link_name, capacity)
        # 生成 12 个月的数据
        for month_offset in range(12):
            # 模拟增长趋势
            growth = month_offset * 3  # 每月增长 3%
            for day in range(1, 29, 2):  # 每 2 天一个样本
                for hour in range(0, 24, 4):
                    # 业务波动(白天高,晚上低)
                    hourly_factor = 1.0 + 0.3 * math.sin(
                        hour * math.pi / 12
                    )
                    # 随机波动
                    random_factor = 1.0 + random.uniform(-0.1, 0.1)
                    util = (
                        base_util + growth + hourly_factor * 10
                    ) * random_factor
                    util = min(max(util, 5), 98)  # 限制范围

                    ts = now - timedelta(
                        days=(12 - month_offset) * 30 - day
                    )
                    util_bps = util / 100 * capacity * 1e9

                    stats.add_sample(LinkSample(
                        timestamp=ts,
                        in_bps=util_bps,
                        out_bps=util_bps * 0.8,
                        in_util_pct=util,
                        out_util_pct=util * 0.8
                    ))
        links.append(stats)

    return links


def main():
    """主函数"""
    model = TrafficModel(history_months=12)

    # 使用模拟数据
    links = generate_mock_data()
    for link in links:
        model.add_link(link)

    # 生成容量规划
    plan = model.generate_capacity_plan(
        warning_threshold=70,
        critical_threshold=85
    )

    # 打印报告
    model.print_plan(plan)

    # 详细输出一条链路的历史和预测
    print("\n链路详情示例 (Spine-2_to_Leaf-1):")
    print("-" * 50)
    for item in plan:
        if item["link"] == "Spine-2_to_Leaf-1":
            print("历史月度 P95 利用率 (%):")
            for i, util in enumerate(item["monthly_history"]):
                bar = "█" * int(util / 2)
                print(f"  M-{12-i:02d}: {bar} {util:.1f}%")
            print("\n未来 6 月预测 (%):")
            for i, f in enumerate(item["forecast"]):
                bar = "█" * int(f / 2)
                print(f"  M+{i+1:02d}: {bar} {f:.1f}%")


if __name__ == "__main__":
    main()

三、容量规划决策

3.1 扩容决策矩阵

扩容决策矩阵:

当前利用率 6 月预测 增长趋势 决策 < 50% < 60% 平稳 无需扩容 < 50% > 70% 快速增长 计划 6 个月后扩容 50-70% < 70% 平稳 监控,不扩容 50-70% > 80% 快速增长 计划 3 个月后扩容 70-85% > 85% 快速增长 月内扩容 > 85% > 90% 快速增长 立即扩容

扩容原则: ┌──────────────────────────────────────────┐ │ 扩容后目标利用率:50%(留 50% 余量) │ │ 扩容粒度:10GE/25GE/100GE │ │ 提前期:从决策到上线 4-8 周 │ │ 预留资源:10% 的端口用于突发和冗余 │ └──────────────────────────────────────────┘

3.2 成本优化

容量规划成本优化:

链路利用率优化:

利用率 < 20% 的链路: └─ 考虑合并或降速(100GE → 40GE) └─ 释放端口给高利用率链路 └─ 或关闭端口节能 利用率 > 80% 的链路: └─ 优先扩容 └─ 考虑负载均衡优化 └─ 或增加 ECMP 成员

分阶段扩容策略:

Year 1:先扩容利用率 > 80% 的链路 Year 2:扩容利用率 60-80% 的链路 Year 3:整体架构升级 每个阶段预留 10% 的预算用于不可预见需求


四、最佳实践

流量建模与容量规划最佳实践:

数据基础:

□ 至少采集 6 个月以上的数据 □ 5 分钟粒度,保留 30 天 □ 1 小时聚合,保留 1 年 □ 1 天聚合,永久保留

分析方法:

□ 使用 P95 而不是 Avg/Max □ 按业务类型分类分析 □ 关注增长趋势(环比、同比) □ 结合业务规划(新业务上线的流量)

决策流程:

□ 月度:生成流量报告 □ 季度:容量规划评审 □ 年度:整体架构评估 □ 持续:利用率达到阈值触发扩容流程


五、总结

流量建模与容量规划关键要点:

  1. 数据驱动
     └─ 基于实际流量数据,不凭感觉
     └─ 使用 P95 百分位,排除异常
     └─ 持续采集和分析

  2. 趋势预测
     └─ 线性预测(稳定增长)
     └─ 指数预测(快速增长)
     └─ 结合业务规划调整预测

  3. 分级决策
     └─ 紧急(立即扩容):利用率 > 85%
     └─ 计划(月内扩容):利用率 70-85%
     └─ 预警(季度评估):增长趋势明显

  4. 成本优化
     └─ 低利用率链路降速或合并
     └─ 扩容后目标利用率 50%
     └─ 预留弹性扩展空间

下篇预告:第355篇《多云网络互联与混合云架构设计案例》——以混合云架构为例,讲解多云网络互联的方案设计和实施要点。


下篇预告:第355篇《多云网络互联与混合云架构设计案例》——以混合云架构为例,讲解多云网络互联的方案设计和实施要点。