本文是《程序员数学扫盲课》系列文章
← 上一篇:程序员数学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)
...
四、小结
这篇文章的核心观点:
- 概率就是”可能性的数字化”,用0到1之间的数字表示
- 串联系统:概率相乘,依赖越多整体越脆弱
- 并联系统:用互补事件计算,备份能显著提升可用性
- 熔断器用概率保护系统,失败率超阈值就拒绝请求
- 概率论是架构设计的数学基础:高可用、容灾、降级都靠它
记住这个对照表:
| 场景 | 概率公式 | 含义 | 实战建议 |
|---|---|---|---|
| 串联依赖 | 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:可用性计算













程序员数学扫盲课
AI周刊:大模型、智能体与产业动态追踪
Claude Code 全体系指南:AI 编程智能体实战
Karpathy神经网络零基础课程
最新评论
开源的AI对话监控面板很实用,正好团队在找这类工具。准备试用一下。
折叠屏市场确实在升温,不过售罄也可能是备货策略。期待看到实际销量数据。
从磁盘I/O角度解释B树的设计动机,这个切入点很好。终于理解为什么数据库不用二叉树了。
IT术语转换确实是个痛点,之前用搜狗总是把技术词汇转成奇怪的词。智谱这个方向值得期待。
这个工具结合LLM和搜索API的思路很有意思,正好解决了我在做知识管理时遇到的问题。请问有没有部署文档?
这个漏洞确实严重,我们团队上周刚遇到类似问题。建议补充一下如何检测现有项目是否受影响的方法。
从简单规则涌现复杂性这个思路很有意思,让我想起元胞自动机。不过数字物理学在学术界争议还挺大的。
我也遇到了指令跟随变差的问题,特别是多轮对话时容易跑偏。不知道是模型退化还是负载优化导致的。