这是 数学重学路线图 阶段六的子页面
费米估算实战 什么是费米估算 以物理学家恩里科·费米命名,核心能力:把一个看似不可能回答的问题,拆解成一系列可以合理估算的小问题
比喻:你不知道大海里有多少条鱼,但你可以估算海洋面积、平均深度、每立方米的鱼密度,然后乘起来
费米估算不追求精确,追求 数量级正确 (差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 mathdau = 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:.0 e} " )print (f"每天新增存储: {daily_storage / 1e9 :.1 f} GB" )print (f"{retention_years} 年总存储: {total_storage / 1e12 :.2 f} TB" )total_links = daily_new * 365 * retention_years for length in [6 , 7 , 8 ]:space = 62 ** length collision_prob = 1 - math.exp(-(total_links ** 2 ) / (2 * space)) print (f"\n短码{length} 位: 空间={space:.2 e} " )print (f" 碰撞概率: {collision_prob * 100 :.2 f} %" )
案例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:.1 f} MB/s" )print (f"每天数据量: {data_per_day_tb:.1 f} TB/天" )print (f"原始总量({retention_days} 天): {raw_total_tb:.0 f} TB" )print (f"压缩后(1:{compression_ratio} ): {compressed_tb:.0 f} TB" )print (f"含{replicas} 副本: {with_replicas_tb:.0 f} 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 :.1 f} 毫秒" elif seconds < 60 : return f"{seconds:.1 f} 秒" elif seconds < 3600 : return f"{seconds/60 :.1 f} 分钟" elif seconds < 86400 : return f"{seconds/3600 :.1 f} 小时" elif seconds < 86400 * 365 : return f"{seconds/86400 :.0 f} 天" else : return f"{seconds/(86400 *365 ):.0 f} 年" 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:.1 e} ):" )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 import mathorder_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:.2 e} 条" )print (f"每天原始数据: {daily_raw_tb:.1 f} TB" )print (f"含副本: {daily_with_replicas:.1 f} TB/天" )print (f"保留{retention_days} 天总存储: {total_storage:.1 f} TB" )print (f"平均QPS: {avg_qps:,.0 f} 条/秒" )print (f"峰值QPS: {peak_qps:,.0 f} 条/秒" )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 programmer_ratio = 0.30 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 :.0 f} 亿" )print (f"劳动年龄人口: {working_age/1e8 :.1 f} 亿" )print (f"就业人口: {employed/1e8 :.1 f} 亿" )print (f"IT从业者: {it_workers/1e4 :.0 f} 万" )print (f"程序员: {programmers/1e4 :.0 f} 万" )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 mathdef format_bytes (b ):"""字节数格式化为可读字符串""" units = ['B' , 'KB' , 'MB' , 'GB' , 'TB' , 'PB' ] for unit in units: if abs (b) < 1024 : return f"{b:.1 f} {unit} " b /= 1024 return f"{b:.1 f} EB" def format_time (seconds ):"""秒数格式化为可读字符串""" if seconds < 0.001 : return f"{seconds*1e6 :.1 f} 微秒" elif seconds < 1 : return f"{seconds*1000 :.1 f} 毫秒" elif seconds < 60 : return f"{seconds:.1 f} 秒" elif seconds < 3600 : return f"{seconds/60 :.1 f} 分钟" elif seconds < 86400 : return f"{seconds/3600 :.1 f} 小时" elif seconds < 86400 * 365 : return f"{seconds/86400 :.0 f} 天" elif seconds < 86400 * 365 * 1e6 : return f"{seconds/(86400 *365 ):.0 f} 年" else : return f"{seconds/(86400 *365 ):.1 e} 年" def format_number (n ):"""大数字格式化""" if n < 1000 : return f"{n:.0 f} " elif n < 1e6 : return f"{n/1e3 :.1 f} K" elif n < 1e9 : return f"{n/1e6 :.1 f} M" elif n < 1e12 : return f"{n/1e9 :.1 f} B" else : return f"{n:.1 e} " 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:.1 f} Gbps" ) else : print (f"带宽需求: {mbps:.0 f} 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:.1 f} 台" ) print (f"含 {int ((buffer_factor-1 )*100 )} % 缓冲: {with_buffer} 台" ) return with_buffer if __name__ == "__main__" :est = SystemEstimator("社交Feed系统" ) dau = 1e7 avg_qps, peak_qps = est.estimate_qps( dau=dau, requests_per_user=20 , peak_factor=3 ) daily_posts = dau * 0.1 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-博弈论入门