数学重学 - 29 费米估算实战

这是 数学重学路线图 阶段六的子页面

费米估算实战

什么是费米估算

以物理学家恩里科·费米命名,核心能力:把一个看似不可能回答的问题,拆解成一系列可以合理估算的小问题

比喻:你不知道大海里有多少条鱼,但你可以估算海洋面积、平均深度、每立方米的鱼密度,然后乘起来

费米估算不追求精确,追求 数量级正确(差10倍以内就算成功)

关键心法:

把大问题拆成小因子

每个因子独立估算

误差会部分抵消(有的偏高有的偏低)

最终结果通常在真实值的2-10倍范围内

系统设计估算模板

面试和实际工作中最常用的费米估算场景:系统设计中的容量估算

四大核心公式

存储估算

总存储 = 用户数 × 每用户数据量 × 保留天数

变体:总存储 = 每日新增数据 × 保留天数

QPS 估算(每秒查询量)

QPS = DAU × 每用户日均请求数 / 86400

峰值QPS通常是平均QPS的 2~5 倍

峰值QPS = QPS × 峰值系数

带宽估算

带宽 = QPS × 平均响应大小

注意单位转换:Bytes/s → Mbps 需要 ×8/1000000

机器数估算

机器数 = 总QPS / 单机QPS能力

Web服务器单机通常能抗 1000~10000 QPS

数据库单机通常能抗 1000~5000 QPS(看查询复杂度)

常用数量级速查表

| 量级 | 约等于 |
| 2^10 | ≈ 1000(1K) |
| 2^20 | ≈ 100万(1M) |
| 2^30 | ≈ 10亿(1G) |
| 2^40 | ≈ 1万亿(1T) |
| 1天 | ≈ 10^5 秒(86400秒) |
| 1年 | ≈ 3 × 10^7 秒(31536000秒) |
| 1MB | = 10^6 字节 |
| 1GB | = 10^9 字节 |
| 1TB | = 10^12 字节 |

实战案例

案例1:短链接服务存储估算

问题:设计一个类似 bit.ly 的短链接服务,估算存储需求

拆解推算

假设日活用户:1亿(10^8)

每个用户每天创建短链接:0.1个(大部分人只是点击,少数人创建)

每天新增短链接:10^8 × 0.1 = 10^7 = 1000万条/天

每条短链接存储:

短码:7字节

原始URL:平均100字节

创建时间:8字节

用户ID:8字节

总计约 200 字节

每天新增存储:10^7 × 200B = 2 × 10^9 B = 2GB/天

保留5年:2GB × 365 × 5 = 3.65TB

结论:约4TB存储,一台大内存机器就能搞定

哈希碰撞概率估算(数学重学/26-概率论基础 生日问题的应用)

短码6位(Base62):62^6 ≈ 568亿种可能

5年总链接数:10^7 × 365 × 5 ≈ 1.8 × 10^10

碰撞概率 ≈ n^2 / (2 × N) = (1.8×10^10)^2 / (2 × 5.68×10^10) ≈ 285%

6位不够!需要7位:62^7 ≈ 3.5万亿,碰撞概率降到约4.6%

用8位更安全:62^8 ≈ 218万亿,碰撞概率可忽略

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
# 短链接服务估算
import math

# 基本参数
dau = 1e8 # 日活用户
creates_per_user = 0.1 # 每用户每天创建数
daily_new = dau * creates_per_user
bytes_per_link = 200 # 每条记录大小
retention_years = 5

# 存储估算
daily_storage = daily_new * bytes_per_link
total_storage = daily_storage * 365 * retention_years
print(f"每天新增链接: {daily_new:.0e}")
print(f"每天新增存储: {daily_storage / 1e9:.1f} GB")
print(f"{retention_years}年总存储: {total_storage / 1e12:.2f} TB")

# 哈希碰撞概率(生日问题近似)
total_links = daily_new * 365 * retention_years
for length in [6, 7, 8]:
space = 62 ** length
# 生日问题近似: P ≈ 1 - e^(-n^2 / (2*N))
collision_prob = 1 - math.exp(-(total_links ** 2) / (2 * space))
print(f"\n短码{length}位: 空间={space:.2e}")
print(f" 碰撞概率: {collision_prob * 100:.2f}%")

案例2:日志收集系统存储估算

问题:1000台服务器的日志收集系统,估算存储和集群规模

拆解推算

服务器数量:1000台

每台每秒产生日志:100条

每条日志大小:约1KB(时间戳+级别+消息+上下文)

每秒数据量:1000 × 100 × 1KB = 100MB/s

每天数据量:100MB/s × 86400s = 8.64TB/天 ≈ 8.6TB/天

保留30天:8.6TB × 30 = 258TB ≈ 260TB

压缩比通常 5:1~10:1,取 7:1

压缩后:260TB / 7 ≈ 37TB

加副本(3副本):37TB × 3 = 111TB

单个Elasticsearch节点建议数据量:2~5TB

需要节点数:111TB / 3TB ≈ 37个节点

再加20%缓冲:37 × 1.2 ≈ 45个节点

QPS估算

写入QPS:1000 × 100 = 100,000 条/秒

查询QPS(假设每秒10个搜索请求,每个扫描大量数据)

需要考虑索引建立的CPU开销

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
# 日志系统容量估算
servers = 1000
logs_per_sec_per_server = 100
log_size_kb = 1
retention_days = 30
compression_ratio = 7
replicas = 3
node_capacity_tb = 3
buffer_factor = 1.2

# 计算
total_logs_per_sec = servers * logs_per_sec_per_server
data_per_sec_mb = total_logs_per_sec * log_size_kb / 1024
data_per_day_tb = data_per_sec_mb * 86400 / 1024 / 1024
raw_total_tb = data_per_day_tb * retention_days
compressed_tb = raw_total_tb / compression_ratio
with_replicas_tb = compressed_tb * replicas
nodes_needed = math.ceil(with_replicas_tb / node_capacity_tb * buffer_factor)

print("=== 日志系统容量估算 ===")
print(f"每秒日志量: {total_logs_per_sec:,} 条/s")
print(f"每秒数据量: {data_per_sec_mb:.1f} MB/s")
print(f"每天数据量: {data_per_day_tb:.1f} TB/天")
print(f"原始总量({retention_days}天): {raw_total_tb:.0f} TB")
print(f"压缩后(1:{compression_ratio}): {compressed_tb:.0f} TB")
print(f"含{replicas}副本: {with_replicas_tb:.0f} TB")
print(f"需要节点数(含缓冲): {nodes_needed} 个")

案例3:暴力破解时间估算

问题:估算不同密码复杂度的暴力破解时间

这是安全工程师必须掌握的估算

基本假设

攻击者算力:10^9 次哈希/秒(GPU集群,如hashcat)

如果用bcrypt等慢哈希:降到约 10^4 次/秒

8位纯数字密码

空间:10^8 = 1亿

时间:10^8 / 10^9 = 0.1秒

结论:瞬间破解,毫无安全性

8位复杂密码(大小写+数字+特殊符号,约95种字符):

空间:95^8 ≈ 6.6 × 10^15

时间:6.6 × 10^15 / 10^9 = 6.6 × 10^6 秒 ≈ 76天

如果用bcrypt:6.6 × 10^15 / 10^4 = 6.6 × 10^11 秒 ≈ 2万年

12位复杂密码

空间:95^12 ≈ 5.4 × 10^23

时间:5.4 × 10^23 / 10^9 = 5.4 × 10^14 秒 ≈ 1700万年

结论:12位复杂密码 + 慢哈希 = 不可能暴力破解

安全启示

密码长度比复杂度更重要(指数增长的底数 vs 指数)

服务端必须用慢哈希(bcrypt/scrypt/argon2)

每增加1位,破解时间 ×95

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
38
39
40
41
# 暴力破解时间估算器
def estimate_crack_time(length, charset_size, hash_rate):
"""估算暴力破解时间"""
space = charset_size ** length
seconds = space / hash_rate

if seconds < 1:
return f"{seconds*1000:.1f} 毫秒"
elif seconds < 60:
return f"{seconds:.1f} 秒"
elif seconds < 3600:
return f"{seconds/60:.1f} 分钟"
elif seconds < 86400:
return f"{seconds/3600:.1f} 小时"
elif seconds < 86400 * 365:
return f"{seconds/86400:.0f} 天"
else:
return f"{seconds/(86400*365):.0f} 年"

# 不同场景
configs = [
("8位纯数字", 8, 10),
("8位小写字母", 8, 26),
("8位大小写+数字", 8, 62),
("8位全字符", 8, 95),
("10位全字符", 10, 95),
("12位全字符", 12, 95),
("16位全字符", 16, 95),
]

hash_rates = {
"MD5 (GPU)": 1e10,
"SHA256 (GPU)": 1e9,
"bcrypt": 1e4,
}

for name, length, charset in configs:
print(f"\n{name} (空间: {charset**length:.1e}):")
for hash_name, rate in hash_rates.items():
time_str = estimate_crack_time(length, charset, rate)
print(f" {hash_name}: {time_str}")

案例4:Kafka集群存储估算

问题:电商平台消息队列Kafka集群需要多少存储

拆解推算

消息来源:

订单事件:每天500万单 × 平均5条消息/单 = 2500万条

用户行为日志:DAU 5000万 × 每人50条 = 25亿条

系统监控:1000台 × 每秒10条 × 86400 = 8.64亿条

每天总消息数:约 34亿条 ≈ 3.4 × 10^9

每条消息平均大小:500字节(0.5KB)

每天原始数据量:3.4 × 10^9 × 0.5KB = 1.7TB/天

副本因子:3

每天含副本:1.7TB × 3 = 5.1TB/天

保留7天:5.1TB × 7 = 35.7TB

单个Kafka broker建议存储:不超过 10TB

Broker数量(按存储):35.7 / 10 ≈ 4个

还需考虑吞吐量:

每秒消息数:3.4 × 10^9 / 86400 ≈ 4万条/秒

峰值(×5):20万条/秒

单broker吞吐:约5万条/秒

Broker数量(按吞吐):20万 / 5万 = 4个

取两者较大值 + 冗余:建议6~8个broker

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
38
39
40
# Kafka集群估算
import math

# 消息量估算
order_msgs = 5e6 * 5 # 订单相关
user_msgs = 5e7 * 50 # 用户行为
monitor_msgs = 1000 * 10 * 86400 # 监控
daily_msgs = order_msgs + user_msgs + monitor_msgs

msg_size_kb = 0.5
replicas = 3
retention_days = 7
broker_storage_tb = 10
broker_throughput = 50000 # 条/秒
peak_factor = 5

# 存储计算
daily_raw_tb = daily_msgs * msg_size_kb / 1e9
daily_with_replicas = daily_raw_tb * replicas
total_storage = daily_with_replicas * retention_days

# 吞吐计算
avg_qps = daily_msgs / 86400
peak_qps = avg_qps * peak_factor

# 节点数
nodes_by_storage = math.ceil(total_storage / broker_storage_tb)
nodes_by_throughput = math.ceil(peak_qps / broker_throughput)
recommended = max(nodes_by_storage, nodes_by_throughput)

print("=== Kafka 集群估算 ===")
print(f"每天消息量: {daily_msgs:.2e} 条")
print(f"每天原始数据: {daily_raw_tb:.1f} TB")
print(f"含副本: {daily_with_replicas:.1f} TB/天")
print(f"保留{retention_days}天总存储: {total_storage:.1f} TB")
print(f"平均QPS: {avg_qps:,.0f} 条/秒")
print(f"峰值QPS: {peak_qps:,.0f} 条/秒")
print(f"按存储需: {nodes_by_storage} 个broker")
print(f"按吞吐需: {nodes_by_throughput} 个broker")
print(f"建议: {recommended + 2} ~ {recommended + 4} 个broker (含冗余)")

案例5:中国程序员数量估算

问题:中国大约有多少程序员?

拆解推算

中国总人口:约14亿

劳动年龄人口(15-60岁)比例:约65%

劳动人口:14亿 × 0.65 = 9.1亿

实际就业人口:约 7.5亿

IT/互联网行业从业者比例:约3%~4%

IT从业者:7.5亿 × 3.5% = 2625万

其中程序员(写代码的)比例:约30%(其余为运维、产品、设计、测试、管理等)

程序员人数:2625万 × 30% ≈ 800万

交叉验证:

GitHub中国用户约1000万(很多不是职业程序员)

CSDN注册用户3000万+(很多非活跃/非职业)

各种估计在 500万~1000万 之间

结论:约700800万,数量级 10^610^7

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 费米估算:中国程序员数量
population = 14e8
working_age_ratio = 0.65
employment_ratio = 0.82 # 就业率
it_ratio = 0.035 # IT行业比例
programmer_ratio = 0.30 # 程序员占IT比例

working_age = population * working_age_ratio
employed = working_age * employment_ratio
it_workers = employed * it_ratio
programmers = it_workers * programmer_ratio

print("=== 中国程序员数量估算 ===")
print(f"总人口: {population/1e8:.0f} 亿")
print(f"劳动年龄人口: {working_age/1e8:.1f} 亿")
print(f"就业人口: {employed/1e8:.1f} 亿")
print(f"IT从业者: {it_workers/1e4:.0f} 万")
print(f"程序员: {programmers/1e4:.0f} 万")
print(f"数量级: ~10^{len(str(int(programmers)))-1}")

估算常用近似技巧

2的幂次与10的幂次对照

核心关系:2^10 ≈ 10^3(1024 ≈ 1000)

由此推导:

2^20 ≈ 10^6(百万)

2^30 ≈ 10^9(十亿)

2^32 ≈ 4 × 10^9(IPv4地址数)

2^40 ≈ 10^12(万亿)

2^64 ≈ 1.8 × 10^19(long类型范围)

时间近似

1分钟 = 60秒

1小时 = 3600秒 ≈ 4 × 10^3 秒

1天 = 86400秒 ≈ 10^5 秒

1周 ≈ 6 × 10^5 秒

1月 ≈ 2.6 × 10^6 秒

1年 ≈ 3.15 × 10^7 秒 ≈ π × 10^7 秒(有趣的巧合)

网络延迟数量级

L1缓存:1ns

L2缓存:4ns

内存访问:100ns

SSD随机读:100μs

HDD随机读:10ms

同机房网络:0.5ms

同城网络:2ms

跨国网络:100~300ms

面试估算技巧

技巧1:先说数量级,再细化

先快速判断结果是百、千、百万还是十亿级别

面试官关心的是思路,不是精确数字

技巧2:用对照物锚定

“微信DAU约5亿,我们的产品大概是它的1/100”

“Twitter每天5亿条推文,我们的消息量大概是这个量级的1/1000”

技巧3:反向验证

算完存储后,检查:这个存储量合理吗?一台机器能放下吗?

算完QPS后,检查:这个QPS需要几台机器?成本合理吗?

技巧4:写下来

面试时在白板上列出所有假设和计算步骤

这样面试官可以看到你的思路,也方便讨论修正

技巧5:知道常见服务的量级

一台Nginx:1~5万 QPS

一台MySQL:1000~5000 QPS

一台Redis:10万+ QPS

一台Kafka broker:50万消息/秒

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
"""
系统设计费米估算计算器
通用工具:输入基本参数,输出容量规划
"""
import math

def format_bytes(b):
"""字节数格式化为可读字符串"""
units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
for unit in units:
if abs(b) < 1024:
return f"{b:.1f} {unit}"
b /= 1024
return f"{b:.1f} EB"

def format_time(seconds):
"""秒数格式化为可读字符串"""
if seconds < 0.001:
return f"{seconds*1e6:.1f} 微秒"
elif seconds < 1:
return f"{seconds*1000:.1f} 毫秒"
elif seconds < 60:
return f"{seconds:.1f} 秒"
elif seconds < 3600:
return f"{seconds/60:.1f} 分钟"
elif seconds < 86400:
return f"{seconds/3600:.1f} 小时"
elif seconds < 86400 * 365:
return f"{seconds/86400:.0f} 天"
elif seconds < 86400 * 365 * 1e6:
return f"{seconds/(86400*365):.0f} 年"
else:
return f"{seconds/(86400*365):.1e} 年"

def format_number(n):
"""大数字格式化"""
if n < 1000:
return f"{n:.0f}"
elif n < 1e6:
return f"{n/1e3:.1f}K"
elif n < 1e9:
return f"{n/1e6:.1f}M"
elif n < 1e12:
return f"{n/1e9:.1f}B"
else:
return f"{n:.1e}"

class SystemEstimator:
"""系统设计估算器"""

def __init__(self, name="未命名系统"):
self.name = name
self.params = {}

def estimate_storage(self, daily_new_bytes, retention_days,
compression_ratio=1, replicas=1):
"""存储估算"""
raw_total = daily_new_bytes * retention_days
compressed = raw_total / compression_ratio
with_replicas = compressed * replicas

print(f"\n=== {self.name} 存储估算 ===")
print(f"每天新增: {format_bytes(daily_new_bytes)}")
print(f"保留 {retention_days} 天原始: {format_bytes(raw_total)}")
if compression_ratio > 1:
print(f"压缩后(1:{compression_ratio}): {format_bytes(compressed)}")
if replicas > 1:
print(f"含 {replicas} 副本: {format_bytes(with_replicas)}")
return with_replicas

def estimate_qps(self, dau, requests_per_user, peak_factor=3):
"""QPS估算"""
avg_qps = dau * requests_per_user / 86400
peak_qps = avg_qps * peak_factor

print(f"\n=== {self.name} QPS估算 ===")
print(f"DAU: {format_number(dau)}")
print(f"每用户日均请求: {requests_per_user}")
print(f"平均QPS: {format_number(avg_qps)}")
print(f"峰值QPS(x{peak_factor}): {format_number(peak_qps)}")
return avg_qps, peak_qps

def estimate_bandwidth(self, qps, avg_response_bytes):
"""带宽估算"""
bps = qps * avg_response_bytes * 8
mbps = bps / 1e6
gbps = bps / 1e9

print(f"\n=== {self.name} 带宽估算 ===")
print(f"QPS: {format_number(qps)}")
print(f"平均响应: {format_bytes(avg_response_bytes)}")
if gbps >= 1:
print(f"带宽需求: {gbps:.1f} Gbps")
else:
print(f"带宽需求: {mbps:.0f} Mbps")
return mbps

def estimate_machines(self, total_qps, single_machine_qps,
buffer_factor=1.3):
"""机器数估算"""
raw = total_qps / single_machine_qps
with_buffer = math.ceil(raw * buffer_factor)

print(f"\n=== {self.name} 机器数估算 ===")
print(f"总QPS: {format_number(total_qps)}")
print(f"单机能力: {format_number(single_machine_qps)} QPS")
print(f"理论需要: {raw:.1f} 台")
print(f"含 {int((buffer_factor-1)*100)}% 缓冲: {with_buffer} 台")
return with_buffer

# 使用示例:估算一个社交Feed系统
if __name__ == "__main__":
est = SystemEstimator("社交Feed系统")

# 基本参数
dau = 1e7 # 1000万DAU

# QPS
avg_qps, peak_qps = est.estimate_qps(
dau=dau, requests_per_user=20, peak_factor=3
)

# 存储
daily_posts = dau * 0.1 # 10%的人发帖
bytes_per_post = 500 # 纯文本
est.estimate_storage(
daily_new_bytes=daily_posts * bytes_per_post,
retention_days=365, replicas=3
)

# 带宽
est.estimate_bandwidth(peak_qps, avg_response_bytes=5000)

# 机器数
est.estimate_machines(peak_qps, single_machine_qps=5000)

练习题

练习1:估算微信每天的消息量

提示:DAU约5亿,每人每天发多少条消息?

总消息量是多少?需要多少存储?
参考思路:
DAU 5亿,平均每人每天50条消息

每天 250亿条消息 = 2.5 × 10^10

平均每条 100字节(纯文本)

文本存储:2.5TB/天

加上图片视频(假设10%发图,每图200KB):5亿 × 5条图 × 200KB = 500TB/天

文字小,图片视频才是大头

练习2:估算全球每天发送的邮件数

提示:全球网民多少?商务邮件vs个人邮件?垃圾邮件比例?
参考思路:
全球网民约50亿

有邮箱的约40亿

平均每天每人 0.5封正常邮件 + 3封垃圾邮件

总计约 40亿 × 3.5 = 140亿封/天 ≈ 10^10

实际数据(2024):约3300亿封/天,垃圾邮件占85%

练习3:一个10万DAU的API网关需要几台机器?

提示:估算QPS,考虑峰值,每台Nginx能抗多少?
参考思路:
10万DAU × 100次请求/天 / 86400 ≈ 116 QPS

峰值 ×5 = 580 QPS

一台Nginx轻松抗5万QPS

结论:1台就够,但为了高可用至少2台

练习4:破解WiFi密码(8位纯数字)需要多久?

提示:WPA2的握手包离线破解速度是多少?
参考思路:
8位纯数字:10^8 = 1亿种

WPA2离线破解(GPU):约50万次/秒

时间:10^8 / 5×10^5 = 200秒 ≈ 3分钟

如果用PMKID攻击可能更快

教训:WiFi密码一定要长且复杂

练习5:估算Redis能存多少用户Session

提示:单机Redis内存上限?每个Session多大?
参考思路:
单机Redis最大内存:通常配 64GB~128GB

取 64GB

每个Session:用户ID(8B) + token(32B) + 过期时间(8B) + 额外数据(200B) ≈ 250B

Redis额外开销(指针、哈希表):约 ×2

实际每个Session占 500B

可存:64GB / 500B = 1.28 × 10^8 ≈ 1.3亿个Session

绰绰有余应对大部分场景

本页小结

费米估算的核心是 拆解数量级思维

系统设计四大估算:存储、QPS、带宽、机器数

常用近似:2^10 ≈ 10^3,1天 ≈ 10^5 秒,1年 ≈ π × 10^7 秒

安全领域最实用的估算:密码破解时间

练习时养成习惯:任何数字先想数量级

下一篇 → 30-博弈论入门


上一章 目录 下一章
28-密码学数学进阶 数学重学路线图 30-博弈论入门