这是 数学重学路线图 阶段一的子页面
05 - 基础统计思维 为什么要学这个 后端开发天天看监控面板,P99延迟、QPS均值、错误率——全是统计指标
安全领域:异常检测的本质就是”偏离正常分布多远”
大数据方向:数据质量检查、离群值识别、A/B测试——统计是基本功
不懂统计,看数据就是在看热闹;懂了统计,看数据就是在看门道
“我和马云平均资产500亿”——如果你觉得这句话哪里不对,恭喜你已经有统计直觉了
核心概念:三个”中心”指标 先讲故事 假设一个5人团队的月薪是:8K、10K、12K、15K、200K(最后一个是老板)
平均薪资 = (8+10+12+15+200)/5 = 49K
但其中4个人的薪资都远低于49K!
这就是平均数的陷阱:一个极端值就能拉飞整个均值
均值(Mean) 公式:mean = Σx_i / n(所有值加起来除以个数)
人话:把蛋糕平均切,每人分到多少
优点:计算简单,用到了每一个数据点
缺点:对极端值敏感 ,一个异常值就能把均值带偏
适用场景:数据分布比较均匀时
定义:把数据从小到大排列,取最中间那个值
人话:排队站中间的那个人
上面的例子:8、10、12 、15、200 → 中位数 = 12K
优点:不受极端值影响 ,反映”典型”水平
缺点:没有用到全部数据的信息
适用场景:数据有偏斜时(收入、房价等)
小知识:国家统计局公布的”居民收入”更多用中位数而非均值
众数(Mode) 定义:出现次数最多的值
人话:最流行的选项
例子:鞋码分布中,42码最多人穿 → 众数是42
适用场景:分类数据(最受欢迎的商品、最常见的错误码)
注意:可能有多个众数,也可能没有(每个值出现次数都一样)
三者的关系 正态分布(对称):均值 ≈ 中位数 ≈ 众数
右偏分布(如收入):众数 < 中位数 < 均值
左偏分布(如考试分数偏高):均值 < 中位数 < 众数
一条经验规则:如果均值和中位数差很多,说明数据分布有偏斜
分位数与百分位 概念 P50 = 中位数:50%的数据在此值以下
P90:90%的数据在此值以下
P95:95%的数据在此值以下
P99:99%的数据在此值以下
P99.9:99.9%的数据在此值以下(千分位)
直觉比喻 想象100个人排队:
P50 = 第50个人
P90 = 第90个人
P99 = 第99个人
P99的人”等得最久”,但不是最倒霉的那个(P100)
为什么后端监控看P99而不是看均值 场景:一个API的响应时间
均值 50ms(看起来很快)
P99 = 2000ms(1%的用户要等2秒!)
如果日请求量1000万次,1% = 10万次慢请求
这10万用户的体验是灾难性的
SLA/SLO通常用P99定义 :例如”P99延迟 < 200ms”
P99 vs P99.9 vs P99.99 | 指标 | 含义 | 1000万请求中的慢请求数 | | P99 | 99%请求在X ms内 | 10万次超标 | | P99.9 | 99.9%请求在X ms内 | 1万次超标 | | P99.99 | 99.99%请求在X ms内 | 1000次超标 |
每多一个9,难度和成本指数级上升
从99.9%到99.99%,可能要花10倍的钱
方差与标准差 先讲故事 两个班级,平均分都是70分:
A班:69, 70, 71, 70, 70(大家差不多)
B班:30, 50, 70, 90, 110(差距巨大)
均值一样,但分布天差地别
我们需要一个指标来衡量”数据有多分散”
方差(Variance) 公式:σ² = Σ(x_i - μ)² / n
人话翻译:
第一步:算出每个值和均值的差距
第二步:差距取平方(消除正负号,放大大的偏差)
第三步:所有平方差取平均
单位问题:如果原数据单位是”分”,方差的单位是”分²”,不直观
标准差(Standard Deviation) 公式:σ = √(σ²),也就是方差开根号
和原数据同单位 ,可以直接比较
直觉:标准差越大 = 数据越分散 = 越不稳定
A班标准差 ≈ 0.6,B班标准差 ≈ 28.3
样本方差 vs 总体方差 总体方差:除以 n(你有全部数据)
样本方差:除以 n-1(你只有部分数据,叫 “贝塞尔校正”)
为什么要 n-1:用样本估计总体时,除以 n 会系统性偏小
Python 中:np.var(data) 是总体方差,np.var(data, ddof=1) 是样本方差
正态分布直觉 什么是正态分布 又叫高斯分布、钟形曲线
自然界最常见的分布:身高、体重、测量误差…
两个参数完全确定:均值 μ(中心位置)和标准差 σ(胖瘦程度)
68-95-99.7 法则(记住这个就够了) μ ± 1σ 范围内:包含 68% 的数据
μ ± 2σ 范围内:包含 95% 的数据
μ ± 3σ 范围内:包含 99.7% 的数据
1 2 3 4 5 6 7 8 ___ / \ 68% 在 1σ 内 / \ / | | | \ 95% 在 2σ 内 / | | | \ / | | | \ 99.7% 在 3σ 内 _/____ |_ |_ |____\_ -3σ -2σ -1σ μ 1σ 2σ 3σ
3σ原则:超过3σ就是异常 正常数据落在 3σ 之外的概率只有 0.3%
也就是说,每1000个数据点,只有3个”应该”在3σ之外
如果你看到很多数据超过了3σ,要么数据不正态,要么出了异常
这就是异常检测的最基础方法
Z-Score z = (x - μ) / σ
人话:这个值离均值有几个标准差
z = 0:恰好在均值处
z = 2:比均值高2个标准差(比约97.5%的值大)
z = -1.5:比均值低1.5个标准差
把任意正态分布转换成标准正态分布(μ=0, σ=1),方便比较
公式与推导(附人话翻译) 均值公式 $\bar{x} = \frac{1}{n}\sum_{i=1}^{n}x_i$
人话:全加起来除以个数
方差公式 $\sigma^2 = \frac{1}{n}\sum_{i=1}^{n}(x_i - \bar{x})^2$
人话:每个值与均值的差的平方,取平均
标准差公式 $\sigma = \sqrt{\sigma^2}$
人话:方差开根号,回到原来的单位
分位数计算 把 n 个数据排序,第 k 百分位数的位置 = k/100 × (n+1)
如果位置不是整数,取两侧的加权平均
Python 代码 基础统计指标计算 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 """ 用 numpy 计算基础统计指标 对比均值和中位数在有极端值时的表现 """ import numpy as npnp.random.seed(42 ) normal_latencies = np.random.normal(loc=50 , scale=10 , size=9900 ) slow_latencies = np.random.uniform(500 , 2000 , size=100 ) latencies = np.concatenate([normal_latencies, slow_latencies]) latencies = np.clip(latencies, 1 , None ) print ("=== API 响应时间统计 ===" )print (f"数据量: {len (latencies)} " )print (f"均值: {np.mean(latencies):.1 f} ms" )print (f"中位数: {np.median(latencies):.1 f} ms" )print (f"标准差: {np.std(latencies):.1 f} ms" )print ()percentiles = [50 , 90 , 95 , 99 , 99.9 ] print ("=== 分位数 ===" )for p in percentiles:value = np.percentile(latencies, p) print (f"P{p:<5 } = {value:>8.1 f} ms" )print ()diff = np.mean(latencies) - np.median(latencies) print (f"均值 - 中位数 = {diff:.1 f} ms" )print ("均值被慢请求拉高了!这就是为什么监控要看 P99 而非均值" )
正态分布与 68-95-99.7 法则可视化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 """ 画出正态分布,标注 1σ/2σ/3σ 区域 """ import numpy as npimport matplotlib.pyplot as pltfrom scipy import statsmu, sigma = 100 , 15 x = np.linspace(mu - 4 *sigma, mu + 4 *sigma, 1000 ) y = stats.norm.pdf(x, mu, sigma) fig, ax = plt.subplots(figsize=(12 , 5 )) ax.plot(x, y, 'k-' , linewidth=2 ) colors = ['#2196F3' , '#64B5F6' , '#BBDEFB' ] labels = ['1σ (68.3%)' , '2σ (95.4%)' , '3σ (99.7%)' ] for i, (n_sigma, color, label) in enumerate (zip ([1 , 2 , 3 ], colors, labels)): x_fill = np.linspace(mu - n_sigma*sigma, mu + n_sigma*sigma, 500 ) y_fill = stats.norm.pdf(x_fill, mu, sigma) ax.fill_between(x_fill, y_fill, alpha=0.3 , color=color, label=label) ax.set_xlabel('延迟 (ms)' , fontsize=12 ) ax.set_ylabel('概率密度' , fontsize=12 ) ax.set_title('正态分布与 68-95-99.7 法则' , fontsize=14 ) ax.legend(fontsize=11 ) for n in range (-3 , 4 ):pos = mu + n * sigma ax.axvline(x=pos, color='gray' , linestyle='--' , alpha=0.3 ) label_text = f'μ{"+" if n > 0 else "" } {n} σ' if n != 0 else 'μ' ax.text(pos, -0.002 , label_text, ha='center' , fontsize=9 ) plt.tight_layout() plt.savefig("normal_distribution_rule.png" , dpi=150 ) plt.show()
异常检测:3σ 方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 """ 用 3σ 原则检测异常流量(简化版 DDoS 检测思路) """ import numpy as npnp.random.seed(42 ) normal_traffic = np.random.normal(1000 , 100 , size=7 *24 *60 ) attack_traffic = normal_traffic.copy() attack_start = 5000 attack_duration = 30 attack_traffic[attack_start:attack_start+attack_duration] += 800 baseline = attack_traffic[:4000 ] mu = np.mean(baseline) sigma = np.std(baseline) print (f"基线均值: {mu:.0 f} QPS" )print (f"基线标准差: {sigma:.0 f} QPS" )print (f"3σ 上界: {mu + 3 *sigma:.0 f} QPS" )print (f"3σ 下界: {mu - 3 *sigma:.0 f} QPS" )print ()threshold_upper = mu + 3 * sigma threshold_lower = mu - 3 * sigma anomalies = [] for i, qps in enumerate (attack_traffic):if qps > threshold_upper or qps < threshold_lower: anomalies.append((i, qps)) true_attacks = [a for a in anomalies if attack_start <= a[0 ] < attack_start + attack_duration] false_positives = [a for a in anomalies if not (attack_start <= a[0 ] < attack_start + attack_duration)] print (f"检测到的异常点总数: {len (anomalies)} " )print (f"其中真正攻击: {len (true_attacks)} " )print (f"误报: {len (false_positives)} " )print (f"\n注意: 3σ 方法简单但粗糙" )print ("实际生产中会用滑动窗口、指数加权移动平均(EWMA)等更精细的方法" )
分位数分布图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 """ 画出 API 延迟的分位数分布图(CDF) 直观理解 P50/P90/P95/P99 """ import numpy as npimport matplotlib.pyplot as pltnp.random.seed(42 ) fast = np.random.lognormal(mean=3.5 , sigma=0.5 , size=9500 ) slow = np.random.uniform(200 , 1000 , size=500 ) latencies = np.concatenate([fast, slow]) sorted_lat = np.sort(latencies) cdf = np.arange(1 , len (sorted_lat)+1 ) / len (sorted_lat) * 100 fig, ax = plt.subplots(figsize=(10 , 6 )) ax.plot(sorted_lat, cdf, linewidth=2 , color='steelblue' ) key_percentiles = [50 , 90 , 95 , 99 ] colors = ['green' , 'orange' , 'darkorange' , 'red' ] for p, c in zip (key_percentiles, colors):val = np.percentile(latencies, p) ax.axhline(y=p, color=c, linestyle='--' , alpha=0.5 ) ax.axvline(x=val, color=c, linestyle='--' , alpha=0.5 ) ax.plot(val, p, 'o' , color=c, markersize=8 ) ax.annotate(f'P{p} = {val:.0 f} ms' , xy=(val, p), xytext=(val+50 , p-5 ), fontsize=10 , color=c) ax.set_xlabel('延迟 (ms)' , fontsize=12 ) ax.set_ylabel('百分位 (%)' , fontsize=12 ) ax.set_title('API 延迟的 CDF(累积分布函数)' , fontsize=14 ) ax.set_xlim(0 , 1200 ) ax.grid(True , alpha=0.3 ) plt.tight_layout() plt.savefig("latency_cdf.png" , dpi=150 ) plt.show()
实际应用场景 安全方向 DDoS 检测 :监控流量的均值和标准差,突然偏离 3σ → 可能在被攻击
异常登录检测 :用户登录时间/地点的分布,偏离历史模式 → 告警
WAF 规则调优 :分析请求参数长度的分布,超过 P99.9 的 → 可疑
大数据方向 数据质量检查 :某列数据的均值/标准差突然变化 → 数据源可能出问题
离群值检测 :Z-Score 绝对值 > 3 的数据点 → 标记为异常
A/B 测试 :对比实验组和对照组的指标是否有统计显著差异
后端方向 SLA/SLO 定义 :P99 延迟 < 200ms、可用性 > 99.95%
告警阈值设定 :基于历史分布的 P99 + 缓冲区
容量规划 :根据流量的均值和峰值(P99)来预留资源
性能回归检测 :新版本上线后,P99 延迟是否变差了
常见误区 误区1:只看均值不看分布
“平均延迟50ms”听起来很好,但P99可能是2秒
永远要问:分布长什么样?有没有长尾?
误区2:样本量太小就下结论
3个用户中2个喜欢新界面 → “66%用户喜欢” → 这不靠谱
样本量太小,随机波动很大
经验法则:至少需要30个样本才有基本的统计意义
误区3:假设所有数据都是正态分布
收入分布、延迟分布、文件大小分布——这些通常是长尾分布(对数正态、幂律)
对非正态数据用3σ检测,会漏掉很多异常
误区4:混淆标准差和标准误
标准差:衡量数据的分散程度
标准误:衡量均值估计的不确定性 = 标准差 / √n
随着样本量增大,标准误变小但标准差不变
误区5:以为P99就是最差的那1%的均值
P99是一个阈值:99%的数据在这个值以下
不是”最差的1%的平均值”
练习题 题目1 :某API一天100万次请求,P95=100ms,P99=500ms,P99.9=2000ms。问:有多少次请求延迟超过500ms?有多少次超过2秒?
答案提示:超过500ms的 = 100万 × 1% = 1万次;超过2秒的 = 100万 × 0.1% = 1000次
题目2 :以下数据集:[1, 2, 3, 4, 5, 100],分别计算均值和中位数,体会极端值的影响
答案提示:均值 = 19.17,中位数 = 3.5
题目3 :某安全系统监控网络流量,历史均值1000 QPS,标准差100。今天突然出现1500 QPS,这个值的 Z-Score 是多少?是否应该告警?
答案提示:z = (1500-1000)/100 = 5,远超3σ,应该立刻告警
题目4 :为什么从P99优化到P99.9的成本远大于从P50优化到P99?用实际工程经验思考
提示:P99之后的长尾通常是GC、网络抖动、磁盘IO等不可控因素,需要从架构层面解决
题目5 (编程题):生成一组模拟数据(正态分布 + 5%的离群值),用Z-Score方法检测离群值,计算检测的准确率和误报率
小结 均值会骗人,多看中位数和分位数
P99 是后端工程师的核心指标
标准差衡量”稳不稳”,3σ之外就是异常
正态分布的 68-95-99.7 法则,记住这一个就够用了
下一节:06-生活中的数学