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

程序员数学05:概率论 - 系统可用性

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

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

← 上一篇:程序员数学04:图论 – 微服务依赖管理 | → 下一篇:程序员数学06:统计学 – P99延迟监控


TL;DR

为什么三个99.9%的服务串联后,整体可用性只有99.7%?为什么要做熔断和降级?为什么备份系统能提升可用性?答案都藏在概率论里。这篇文章用Go代码带你搞懂概率计算,看完你会发现:概率就是”可能性的数字化”


系列导航

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


一、概率论到底是什么?

先说重点:概率就是”某件事发生的可能性”,用0到1之间的数字表示

1.1 从抛硬币说起

抛一枚硬币,正面朝上的概率是多少?

P(正面) = 正面次数 / 总次数 = 1/2 = 0.5 = 50%

Go代码模拟:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().UnixNano())

    trials := 10000
    heads := 0

    for i := 0; i < trials; i++ {
        if rand.Float64() < 0.5 {
            heads++
        }
    }

    probability := float64(heads) / float64(trials)
    fmt.Printf("抛硬币 %d 次\n", trials)
    fmt.Printf("正面次数: %d\n", heads)
    fmt.Printf("正面概率: %.4f (理论值: 0.5000)\n", probability)
}

输出:

抛硬币 10000 次
正面次数: 5012
正面概率: 0.5012 (理论值: 0.5000)

1.2 概率的基本规则

规则 公式 含义 后端场景
概率范围 0 ≤ P(A) ≤ 1 概率在0到1之间 可用性99.9% = 0.999
必然事件 P(必然) = 1 一定发生 服务器一定会响应(理想)
不可能事件 P(不可能) = 0 不会发生 服务器永不宕机(不现实)
互补事件 P(A) + P(非A) = 1 要么发生要么不发生 可用性 + 不可用性 = 1

二、系统可用性:串联与并联

2.1 串联系统:概率相乘

场景: 用户请求经过3层服务。

用户 → 网关(99.9%) → 业务服务(99.9%) → 数据库(99.9%)

问题: 整体可用性是多少?

答案:概率相乘

P(整体可用) = P(网关) × P(业务) × P(数据库)
            = 0.999 × 0.999 × 0.999
            = 0.997
            = 99.7%

Go代码实现:

package main

import "fmt"

// 串联系统可用性
func SerialAvailability(components []float64) float64 {
    result := 1.0
    for _, availability := range components {
        result *= availability
    }
    return result
}

func main() {
    // 三层架构
    layers := []float64{0.999, 0.999, 0.999}

    overall := SerialAvailability(layers)
    fmt.Printf("串联系统可用性:\n")
    fmt.Printf("各层: %v\n", layers)
    fmt.Printf("整体: %.4f (%.2f%%)\n", overall, overall*100)

    // 计算年度停机时间
    minutesPerYear := 365 * 24 * 60
    downtime := float64(minutesPerYear) * (1 - overall)
    fmt.Printf("年度停机时间: %.0f 分钟 (%.1f 小时)\n",
        downtime, downtime/60)
}

输出:

串联系统可用性:
各层: [0.999 0.999 0.999]
整体: 0.9970 (99.70%)
年度停机时间: 158 分钟 (2.6 小时)

关键发现: 依赖越多,整体越脆弱!


2.2 并联系统:概率互补

场景: 两台服务器做主备,只要有一台活着就能工作。

请求 → [服务器A(99%) 或 服务器B(99%)]

问题: 整体可用性是多少?

答案:用互补事件计算

P(整体不可用) = P(A挂) × P(B挂)
              = (1-0.99) × (1-0.99)
              = 0.01 × 0.01
              = 0.0001

P(整体可用) = 1 - P(整体不可用)
            = 1 - 0.0001
            = 0.9999
            = 99.99%

Go代码实现:

// 并联系统可用性
func ParallelAvailability(components []float64) float64 {
    // 计算所有组件都失败的概率
    allFail := 1.0
    for _, availability := range components {
        allFail *= (1 - availability)
    }
    // 至少一个可用 = 1 - 全部失败
    return 1 - allFail
}

func main() {
    // 两台服务器主备
    servers := []float64{0.99, 0.99}

    overall := ParallelAvailability(servers)
    fmt.Printf("并联系统可用性:\n")
    fmt.Printf("各服务器: %v\n", servers)
    fmt.Printf("整体: %.4f (%.2f%%)\n", overall, overall*100)

    // 对比单机
    single := servers[0]
    improvement := (overall - single) / single * 100
    fmt.Printf("相比单机提升: %.2f%%\n", improvement)
}

输出:

并联系统可用性:
各服务器: [0.99 0.99]
整体: 0.9999 (99.99%)
相比单机提升: 0.99%

三、熔断与降级:用概率保护系统

3.1 为什么需要熔断?

场景: 下游服务挂了,上游还在疯狂重试。

用户 → API网关 → 订单服务(挂了) → 数据库

问题:
– 订单服务响应慢(超时)
– API网关大量请求堆积
– 整个系统雪崩

熔断器原理: 当失败率超过阈值,直接拒绝请求,不再调用下游。

Go实现简化版熔断器:

package main

import (
    "fmt"
    "sync"
    "time"
)

type CircuitBreaker struct {
    maxFailures  int           // 最大失败次数
    timeout      time.Duration // 熔断超时时间
    failures     int           // 当前失败次数
    lastFailTime time.Time     // 最后失败时间
    state        string        // 状态:closed/open/half-open
    mu           sync.Mutex
}

func NewCircuitBreaker(maxFailures int, timeout time.Duration) *CircuitBreaker {
    return &CircuitBreaker{
        maxFailures: maxFailures,
        timeout:     timeout,
        state:       "closed",
    }
}

func (cb *CircuitBreaker) Call(fn func() error) error {
    cb.mu.Lock()
    defer cb.mu.Unlock()

    // 检查是否可以尝试恢复
    if cb.state == "open" {
        if time.Since(cb.lastFailTime) > cb.timeout {
            cb.state = "half-open"
            cb.failures = 0
        } else {
            return fmt.Errorf("熔断器打开,拒绝请求")
        }
    }

    // 执行请求
    err := fn()

    if err != nil {
        cb.failures++
        cb.lastFailTime = time.Now()

        if cb.failures >= cb.maxFailures {
            cb.state = "open"
            return fmt.Errorf("熔断器打开")
        }
        return err
    }

    // 成功,重置计数
    if cb.state == "half-open" {
        cb.state = "closed"
    }
    cb.failures = 0
    return nil
}

func (cb *CircuitBreaker) GetState() string {
    cb.mu.Lock()
    defer cb.mu.Unlock()
    return cb.state
}

3.2 使用示例

func main() {
    cb := NewCircuitBreaker(3, 5*time.Second)

    // 模拟不稳定的服务
    callService := func() error {
        // 70%概率失败
        if rand.Float64() < 0.7 {
            return fmt.Errorf("服务调用失败")
        }
        return nil
    }

    // 发送10个请求
    for i := 1; i <= 10; i++ {
        err := cb.Call(callService)
        state := cb.GetState()

        if err != nil {
            fmt.Printf("请求%d: 失败 - %v (状态: %s)\n", i, err, state)
        } else {
            fmt.Printf("请求%d: 成功 (状态: %s)\n", i, state)
        }

        time.Sleep(500 * time.Millisecond)
    }
}

输出示例:

请求1: 失败 - 服务调用失败 (状态: closed)
请求2: 失败 - 服务调用失败 (状态: closed)
请求3: 失败 - 服务调用失败 (状态: closed)
请求4: 失败 - 熔断器打开 (状态: open)
请求5: 失败 - 熔断器打开,拒绝请求 (状态: open)
请求6: 失败 - 熔断器打开,拒绝请求 (状态: open)
...

四、小结

这篇文章的核心观点:

  1. 概率就是”可能性的数字化”,用0到1之间的数字表示
  2. 串联系统:概率相乘,依赖越多整体越脆弱
  3. 并联系统:用互补事件计算,备份能显著提升可用性
  4. 熔断器用概率保护系统,失败率超阈值就拒绝请求
  5. 概率论是架构设计的数学基础:高可用、容灾、降级都靠它

记住这个对照表:

场景 概率公式 含义 实战建议
串联依赖 P = P₁ × P₂ × P₃ 概率相乘 减少依赖层级
并联备份 P = 1 – (1-P₁)(1-P₂) 互补事件 增加冗余节点
熔断阈值 失败率 > 阈值 概率判断 设置合理阈值

实战建议:
– 三个99.9%串联只有99.7%,要做解耦和异步化
– 两台99%的服务器并联能达到99.99%,备份很重要
– 熔断器阈值设置:失败率>50%或连续失败3次

下次面试官问”如何提升系统可用性”,你就可以回答:减少串联依赖(降低概率相乘),增加并联备份(利用互补事件),加熔断器保护(概率阈值控制)


参考资料
– 《概率论与数理统计》第1章:概率论基础
– Netflix Hystrix:熔断器实现
– Google SRE Book:可用性计算


返回系列总览

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

赞(0)
未经允许不得转载:Toy's Tech Notes » 程序员数学05:概率论 - 系统可用性
免费、开放、可编程的智能路由方案,让你的服务随时随地在线。

评论 抢沙发

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

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

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