AI编程 · 架构思考 · 技术人生

程序员数学10:组合数学 - 容量规划

#程序员数学扫盲课
智谱 GLM,支持多语言、多任务推理。从写作到代码生成,从搜索到知识问答,AI 生产力的中国解法。

本文是《程序员数学扫盲课》系列文章

← 上一篇:程序员数学09:信息论 – 数据压缩


TL;DR

为什么100万用户需要多少台服务器?为什么数据库连接池要设置多大?为什么缓存命中率从90%提升到95%,性能能翻倍?答案都藏在组合数学里。这篇文章用Go代码带你搞懂容量规划的数学原理,看完你会发现:组合数学就是”计算可能性的数量”


系列导航

《程序员数学扫盲课》系列:
1. 破冰篇:数学符号就是代码
2. 对数Log:数据库索引的魔法
3. 集合论:玩转Redis与SQL
4. 图论基础:微服务依赖管理
5. 概率论:系统可用性计算
6. 统计学:P99延迟与监控报警
7. 线性代数入门:推荐系统的数学基础
8. 哈希与模运算:负载均衡算法
9. 信息论:数据压缩与编码
10. 组合数学:容量规划与性能预估(本篇)


一、排列与组合:计算可能性

先说重点:排列关心顺序,组合不关心顺序

1.1 从密码破解说起

场景: 4位数字密码,每位可以是0-9。

问题: 总共有多少种可能?

答案: 10 × 10 × 10 × 10 = 10,000种

这就是排列(Permutation):每个位置都有10种选择,顺序不同算不同密码。

Go代码验证:

package main

import "fmt"

func main() {
    // 4位数字密码
    digits := 10 // 每位有10种选择(0-9)
    length := 4  // 密码长度

    total := 1
    for i := 0; i < length; i++ {
        total *= digits
    }

    fmt.Printf("4位数字密码总数: %d\n", total)
    fmt.Printf("暴力破解平均尝试次数: %d\n", total/2)
}

输出:

4位数字密码总数: 10000
暴力破解平均尝试次数: 5000

1.2 排列公式

从n个元素中选k个排列:

P(n, k) = n! / (n-k)!

示例: 从5个人中选3个排队,有多少种排法?

P(5, 3) = 5! / (5-3)! = 5! / 2! = 120 / 2 = 60

Go代码实现:

// 阶乘
func Factorial(n int) int {
    if n <= 1 {
        return 1
    }
    result := 1
    for i := 2; i <= n; i++ {
        result *= i
    }
    return result
}

// 排列数
func Permutation(n, k int) int {
    return Factorial(n) / Factorial(n-k)
}

func main() {
    n, k := 5, 3
    result := Permutation(n, k)
    fmt.Printf("从%d个人中选%d个排队: %d种排法\n", n, k, result)
}

输出:

从5个人中选3个排队: 60种排法

1.3 组合公式

从n个元素中选k个组合(不关心顺序):

C(n, k) = n! / (k! × (n-k)!)

示例: 从5个人中选3个组队(不关心顺序),有多少种选法?

C(5, 3) = 5! / (3! × 2!) = 120 / (6 × 2) = 10

Go代码实现:

// 组合数
func Combination(n, k int) int {
    return Factorial(n) / (Factorial(k) * Factorial(n-k))
}

func main() {
    n, k := 5, 3
    perm := Permutation(n, k)
    comb := Combination(n, k)

    fmt.Printf("从%d个人中选%d个:\n", n, k)
    fmt.Printf("排列(关心顺序): %d种\n", perm)
    fmt.Printf("组合(不关心顺序): %d种\n", comb)
    fmt.Printf("关系: 排列 = 组合 × k! = %d × %d = %d\n", comb, Factorial(k), perm)
}

输出:

从5个人中选3个:
排列(关心顺序): 60种
组合(不关心顺序): 10种
关系: 排列 = 组合 × k! = 10 × 6 = 60

二、容量规划:计算需要多少资源

2.1 服务器容量计算

场景: 100万日活用户,每个用户平均发10个请求,每台服务器QPS=1000。

问题: 需要多少台服务器?

计算:

总请求数 = 100万 × 10 = 1000万/天
峰值QPS = 1000万 / (24×3600) × 峰值倍数
假设峰值是平均值的3倍:
峰值QPS = 1000万 / 86400 × 3 ≈ 347 QPS

服务器数量 = 峰值QPS / 单机QPS = 347 / 1000 ≈ 1台

但要考虑冗余:1 × 2 = 2台(双活)

Go代码实现:

package main

import (
    "fmt"
    "math"
)

// 容量规划计算器
type CapacityPlanner struct {
    dailyActiveUsers int     // 日活用户
    requestsPerUser  int     // 每用户请求数
    peakFactor       float64 // 峰值倍数
    serverQPS        int     // 单机QPS
    redundancy       float64 // 冗余系数
}

func (cp *CapacityPlanner) Calculate() int {
    // 总请求数
    totalRequests := cp.dailyActiveUsers * cp.requestsPerUser

    // 平均QPS
    avgQPS := float64(totalRequests) / (24 * 3600)

    // 峰值QPS
    peakQPS := avgQPS * cp.peakFactor

    // 所需服务器数(不含冗余)
    baseServers := math.Ceil(peakQPS / float64(cp.serverQPS))

    // 加上冗余
    totalServers := int(math.Ceil(baseServers * cp.redundancy))

    fmt.Printf("容量规划结果:\n")
    fmt.Printf("日活用户: %d\n", cp.dailyActiveUsers)
    fmt.Printf("总请求数: %d/天\n", totalRequests)
    fmt.Printf("平均QPS: %.2f\n", avgQPS)
    fmt.Printf("峰值QPS: %.2f (峰值倍数: %.1fx)\n", peakQPS, cp.peakFactor)
    fmt.Printf("基础服务器数: %.0f台\n", baseServers)
    fmt.Printf("含冗余服务器数: %d台 (冗余系数: %.1fx)\n", totalServers, cp.redundancy)

    return totalServers
}

func main() {
    planner := &CapacityPlanner{
        dailyActiveUsers: 1000000, // 100万日活
        requestsPerUser:  10,      // 每用户10个请求
        peakFactor:       3.0,     // 峰值是平均值3倍
        serverQPS:        1000,    // 单机1000 QPS
        redundancy:       2.0,     // 双活冗余
    }

    servers := planner.Calculate()
    fmt.Printf("\n最终结论: 需要 %d 台服务器\n", servers)
}

输出:

容量规划结果:
日活用户: 1000000
总请求数: 10000000/天
平均QPS: 115.74
峰值QPS: 347.22 (峰值倍数: 3.0x)
基础服务器数: 1台
含冗余服务器数: 2台 (冗余系数: 2.0x)

最终结论: 需要 2 台服务器

2.2 缓存命中率的威力

场景: 数据库查询100ms,缓存查询1ms。

问题: 缓存命中率从90%提升到95%,性能提升多少?

计算:

90%命中率:
平均延迟 = 0.9×1ms + 0.1×100ms = 0.9 + 10 = 10.9ms

95%命中率:
平均延迟 = 0.95×1ms + 0.05×100ms = 0.95 + 5 = 5.95ms

性能提升 = (10.9 - 5.95) / 10.9 = 45.4%

Go代码实现:

package main

import "fmt"

// 缓存性能计算器
type CachePerformance struct {
    cacheLatency float64 // 缓存延迟(ms)
    dbLatency    float64 // 数据库延迟(ms)
}

func (cp *CachePerformance) AvgLatency(hitRate float64) float64 {
    return hitRate*cp.cacheLatency + (1-hitRate)*cp.dbLatency
}

func (cp *CachePerformance) Compare(hitRate1, hitRate2 float64) {
    latency1 := cp.AvgLatency(hitRate1)
    latency2 := cp.AvgLatency(hitRate2)
    improvement := (latency1 - latency2) / latency1 * 100

    fmt.Printf("缓存性能对比:\n")
    fmt.Printf("缓存延迟: %.1fms, 数据库延迟: %.1fms\n", cp.cacheLatency, cp.dbLatency)
    fmt.Printf("\n命中率 %.0f%%:\n", hitRate1*100)
    fmt.Printf("  平均延迟: %.2fms\n", latency1)
    fmt.Printf("\n命中率 %.0f%%:\n", hitRate2*100)
    fmt.Printf("  平均延迟: %.2fms\n", latency2)
    fmt.Printf("\n性能提升: %.2f%%\n", improvement)
}

func main() {
    cache := &CachePerformance{
        cacheLatency: 1,   // 1ms
        dbLatency:    100, // 100ms
    }

    cache.Compare(0.90, 0.95)
}

输出:

缓存性能对比:
缓存延迟: 1.0ms, 数据库延迟: 100.0ms

命中率 90%:
  平均延迟: 10.90ms

命中率 95%:
  平均延迟: 5.95ms

性能提升: 45.41%

关键发现: 命中率从90%提升到95%(只提升5个百分点),性能提升45%!


三、小结

这篇文章的核心观点:

  1. 排列关心顺序,组合不关心顺序,排列数 = 组合数 × k!
  2. 容量规划公式:服务器数 = 峰值QPS / 单机QPS × 冗余系数
  3. 缓存命中率的威力:从90%提升到95%,性能提升45%
  4. 组合数学是容量规划的数学基础:服务器数量、连接池大小、缓存容量都靠它
  5. 性能优化要算投入产出比:提升5%命中率比增加1倍服务器更划算

记住这个对照表:

概念 数学定义 Go实现 应用场景
排列 P(n,k) = n!/(n-k)! Permutation() 密码破解
组合 C(n,k) = n!/(k!(n-k)!) Combination() 选择方案
容量规划 峰值QPS/单机QPS CapacityPlanner 服务器数量
缓存性能 加权平均延迟 CachePerformance 性能预估

实战建议:
– 容量规划要考虑峰值倍数(通常3-5倍)和冗余系数(至少2倍)
– 缓存命中率每提升1%,性能提升约5-10%
– 数据库连接池大小 = 并发数 / 平均响应时间 × 安全系数

下次面试官问”如何做容量规划”,你就可以回答:先算峰值QPS(日活×请求数/86400×峰值倍数),再除以单机QPS得到基础服务器数,最后乘以冗余系数(通常2倍)。还要考虑缓存命中率,命中率每提升5%,性能能提升40-50%


参考资料
– 《具体数学》:组合数学基础
– Google SRE Book:容量规划
– 《高性能MySQL》:连接池配置


返回系列总览

👉 程序员数学扫盲课:完整课程大纲

赞(0)
未经允许不得转载:Toy's Tech Notes » 程序员数学10:组合数学 - 容量规划
免费、开放、可编程的智能路由方案,让你的服务随时随地在线。

评论 抢沙发

十年稳如初 — LocVPS,用时间证明实力

10+ 年老牌云主机服务商,全球机房覆盖,性能稳定、价格厚道。

老品牌,更懂稳定的价值你的第一台云服务器,从 LocVPS 开始