促销开始前一周,老板要求“这次绝对不能崩”。你看着那台跑着 Nginx + PHP + MySQL 的单机,心里没底。加配置是最直觉的做法——把 4 核 8G 升到 16 核 32G,确实能扛一阵,但只要所有组件还在同一台机器上,瓶颈会从一个组件转移到另一个组件:CPU 空了,磁盘先扛不住;磁盘换了 SSD,数据库连接数又爆了;最后永远有那一个“拖后腿的”。
真正能线性扩展的做法是分层:把 Web、缓存、数据库、存储拆开,各自按自己的瓶颈点扩容。本文给出一套从“单机全能”到“分层集群”的演进路径,包含容量换算方法、各层的部署要点、不同流量级别的推荐配置,以及一份可执行的迁移清单。所有推荐值都给出判断依据,方便你按自己的业务特征调整。
一、先把需求量化:从 PV 到并发再到机器数
架构规划的第一步不是画拓扑图,而是算清楚到底要扛多少量。很多人张口就是“日活百万”,但真正决定机器数量的是峰值每秒请求数(QPS)。
1.1 换算公式
基础参数 DAU(日活)= 100,000 人均日请求数 = 20(页面 + 接口 + 静态资源) 日总请求数 = 100,000 × 20 = 2,000,000 峰值集中系数 经验值:约 60%-80% 的流量集中在 20% 的时间内(晚 8-10 点) 峰值小时请求数 = 2,000,000 × 0.6 = 1,200,000 峰值 QPS = 1,200,000 ÷ 3600 ≈ 333 QPS 考虑秒杀/抢购的尖峰倍数 普通场景峰值 QPS × 2~3 作为设计目标 秒杀场景峰值 QPS × 10 以上,且必须靠缓存和队列削峰 Web 层机器数估算 单台 4 核 Nginx 静态+反代约 5000-10000 QPS 单台 4 核 PHP-FPM 动态接口约 200-800 QPS(视业务复杂度) 所需机器 = 峰值 QPS ÷ 单机能力 × 1.5(冗余系数)
这里的关键洞察是:静态资源和动态接口的承载能力差了一个数量级以上。把它们混在一台机器上,等于用最贵的资源做最便宜的事。
1.2 不同量级的推荐架构
| 阶段 | 日 PV | 峰值 QPS | 推荐架构 | 大致机器数 |
|---|---|---|---|---|
| 起步 | < 5 万 | < 50 | 单机:Nginx + PHP + MySQL 一体 | 1 台 2-4 核 |
| 成长 | 5-50 万 | 50-500 | Web 与数据库分离,引入 Redis 与 CDN | 2-3 台 |
| 规模 | 50-500 万 | 500-3000 | Web 集群 + 负载均衡 + Redis 主从 + MySQL 主从读写分离 | 5-10 台 |
| 大型 | > 500 万 | > 3000 | 多层 LB + 服务拆分 + Redis 集群 + 分库分表 + 对象存储 | 10 台以上 |
注意表格里的机器数是按业务复杂度中估算的参考值,实际要以上面压测出来的单机能力为准。千万不要照抄。
二、分层架构总览
一个成熟的高流量网站典型拓扑如下,每一层都可以独立扩容:
用户
│
┌────▼────┐
│ CDN │ 静态资源(图片/CSS/JS/视频)
└────┬────┘ 承担 70%-90% 的请求量
│
┌────▼────┐
│ SLB/LVS │ 四层负载均衡 + 高可用
└────┬────┘
┌─────────────┼─────────────┐
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Nginx-1 │ │ Nginx-2 │ │ Nginx-3 │ Web 接入层(可横向扩展)
└────┬────┘ └────┬────┘ └────┬────┘
└─────────────┼─────────────┘
│
┌─────────────┼─────────────┐
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ App-1 │ │ App-2 │ │ App-3 │ 应用层(PHP/Go/Java)
└────┬────┘ └────┬────┘ └────┬────┘
└─────────────┼─────────────┘
┌────────┼────────┐
┌────▼───┐ ┌──▼───┐ ┌──▼──────┐
│ Redis │ │ MQ │ │ MySQL │ 数据层
│ 主从 │ │ 队列 │ │ 主从读写│
└────────┘ └──────┘ │ 分离 │
└─────────┘
三条核心原则:
- 无状态化:应用层不存会话、不存上传文件,才能随意加减机器。会话放 Redis,文件放对象存储。
- 能缓存的不要计算:CDN 缓存静态资源,Redis 缓存热点数据与查询结果,Nginx 可以做页面级缓存。
- 能异步的不要同步:下单后的发短信、加积分、生成报表全部丢进消息队列。
三、Web 层规划:Nginx 与应用服务器
3.1 Nginx 基础调优
# /etc/nginx/nginx.conf 核心参数
user nginx;
worker_processes auto; # 等于 CPU 核数
events {
worker_connections 65535; # 单 worker 最大连接数
multi_accept on;
use epoll; # Linux 下性能最好的事件模型
}
http {
sendfile on; # 零拷贝发送静态文件
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
server_tokens off;
client_max_body_size 20m;
# 打开静态资源缓存(文件描述符缓存)
open_file_cache max=100000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
gzip on;
gzip_comp_level 5;
gzip_types text/plain text/css application/json application/javascript image/svg+xml;
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_req_zone $binary_remote_addr zone=qps:10m rate=20r/s;
}
# 配套的系统参数 /etc/sysctl.conf net.core.somaxconn = 65535 # 默认 128,高并发必改 net.ipv4.tcp_max_syn_backlog = 262144 net.ipv4.ip_local_port_range = 1024 65535 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 15 net.core.netdev_max_backlog = 262144 fs.file-max = 2000000 # 系统级文件句柄上限 # 生效 sysctl -p # 用户级句柄限制 /etc/security/limits.conf nginx soft nofile 100000 nginx hard nofile 200000
3.2 应用层:PHP-FPM 与 PFM 池的合理配置
Web 层最容易低估的就是 PHP-FPM 进程数与内存的换算。公式很简单,但很多人没算过:
可用内存 = 物理内存 × 0.7(留 30% 给系统页缓存与其他进程)
pm.max_children ≈ 可用内存 ÷ 单个 PHP 进程平均内存占用
实测单进程占用(RSS,单位 KB):
ps --no-headers -o rss -C php-fpm | awk '{s+=$1;n++} END {print "进程数:",n,"平均RSS(KB):",s/n}'
示例:8G 内存机器,单进程 40MB
pm.max_children = 8192 × 0.7 ÷ 40 ≈ 143 → 保守取 100
对应的池配置参考:
# /etc/php/8.1/fpm/pool.d/www.conf pm = static # 高流量场景推荐 static,避免动态创建进程的抖动 pm.max_children = 100 pm.start_servers = 20 # dynamic 模式下生效 pm.min_spare_servers = 20 pm.max_spare_servers = 40 pm.max_requests = 1000 # 处理 1000 个请求后重启子进程,防内存缓慢泄漏 request_terminate_timeout = 30s request_slowlog_timeout = 3s slowlog = /var/log/php8.1-fpm.slow.log listen.backlog = 2048 # 与 net.core.somaxconn 配套调整 pm.status_path = /fpm-status # 状态页,务必限制访问来源
3.3 Web 层的横向扩展要点
- 所有 Web 节点使用完全相同的配置,建议用 Ansible 或镜像批量下发,避免“某台机器配置不一样”的诡异问题。
- 代码发布采用软链接切换(
/data/www/current -> /data/www/release-20241014),回滚只需改软链,秒级完成。 - 上传目录、会话、日志不要放本地——会话进 Redis,文件进对象存储或 NFS,日志集中采集。
- 每台节点都要配好健康检查接口(如
/health返回 200),供负载均衡器探活。 - Nginx
access_log在高 QPS 下会成为磁盘 IO 瓶颈,静态资源access_log off,动态日志用 buffer 写入。
access_log /var/log/nginx/access.log main buffer=32k flush=5s;
location ~* \.(jpg|jpeg|png|gif|css|js|woff2)$ {
access_log off;
expires 30d;
}
四、缓存层:挡住 80% 的请求
缓存是分层架构里性价比最高的一层。按顺序做四级缓存,每一级都能挡掉大量请求:
| 层级 | 位置 | 缓存内容 | 典型命中率 | 生效范围 |
|---|---|---|---|---|
| 浏览器缓存 | 客户端 | CSS/JS/图片 | 高 | 单用户 |
| CDN 缓存 | 边缘节点 | 静态资源、整页 | 70%-90% | 全国/全球 |
| Nginx 缓存 | Web 机本地 | 代理响应、热点页 | 中 | 单节点 |
| Redis | 独立缓存服务 | 查询结果、会话、计数 | 取决于键设计 | 全站共享 |
4.1 Redis 部署与容量要点
# 生产建议:主从 + 哨兵,或 Redis Cluster redis-cli info Replication redis-cli info Memory # used_memory_human / maxmemory_policy redis-cli info Stats | grep -E 'keyspace_hits|keyspace_misses' redis-cli info Clients | grep connected_clients # 命中率计算: # 命中率 = hits ÷ (hits + misses) # 低于 90% 说明键设计或过期策略有问题,缓存形同虚设
- 必须设置 maxmemory(建议物理内存的 50%-60%)与淘汰策略(
maxmemory-policy allkeys-lru或volatile-lru),否则内存会被写满。 - 禁止在生产使用
KEYS *,用SCAN替代;避免大 key(单 value 超过 10KB 就要警惕,超过 1MB 必须拆)。 - 热 key(如首页配置)可以做本地缓存 + 短 TTL,避免单分片被打爆。
- 缓存三大问题要提前防:穿透(查不存在的 key)用布隆过滤器或空值缓存;击穿(热点 key 过期)用互斥锁或逻辑过期;雪崩(大批 key 同时过期)给 TTL 加随机抖动。
4.2 Nginx 代理缓存(可选的一层)
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=pagecache:100m
inactive=60m max_size=10g use_temp_path=off;
server {
location / {
proxy_cache pagecache;
proxy_cache_key $scheme$host$request_uri;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://backend;
}
}
X-Cache-Status 返回 HIT / MISS / BYPASS,是验证缓存是否生效最快的方法。
五、数据库层:最贵的瓶颈
数据库是分层里最难水平扩展的一层,所以规划时要格外保守。
# 关键监控指标与判断标准 mysql -e "SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';" # Threads_connected 持续 > max_connections × 0.8 → 需要加连接池或扩容 mysql -e "SHOW STATUS LIKE 'Slow_queries'; SHOW VARIABLES LIKE 'long_query_time';" # Slow_queries 每分钟增长超过 10 个 → 必须优化 SQL 或索引 mysql -e "SHOW ENGINE INNODB STATUS\G" | grep -A5 'BUFFER POOL AND MEMORY' # Buffer pool hit rate 应 > 99%,低于则 innodb_buffer_pool_size 偏小 mysql -e "SELECT * FROM information_schema.processlist WHERE command!='Sleep' AND time>5;" # 长事务是锁等待与连接耗尽的元凶
5.1 数据库层演进建议
- 先优化再扩容:加索引、改慢 SQL、拆大表、避免
SELECT *和深分页(LIMIT 100000,20改为基于主键的游标分页)。 - 读写分离:主库写,多个从库读,应用层或中间件(ProxySQL、ShardingSphere-Proxy)做路由。
- 连接池:数据库侧的连接数是有限资源,用连接池控制,不要每个请求都新建连接。
- 垂直拆分:把日志表、统计表拆到独立实例,避免拖垮主业务库。
- 水平分片:单表超过 2000 万行或单库超过 500GB 才考虑,过早分库分表会显著增加复杂度。
| 方案 | 解决的问题 | 引入的复杂度 | 建议时机 |
|---|---|---|---|
| 索引/SQL 优化 | 慢查询、CPU 高 | 无 | 永远第一步 |
| 读写分离 | 读压力大 | 低(主从延迟) | 读 QPS 超过单机 50% |
| 连接池 | 连接数耗尽 | 低 | Threads_connected 长期高位 |
| 垂直拆分 | 大表拖累主库 | 中 | 单库表过多、业务边界清晰 |
| 分库分表 | 单库容量/写入瓶颈 | 高(分布式事务、跨片查询) | 最后手段 |
六、存储层与运维配套
- 静态文件:图片、视频、附件一律走对象存储(S3/OSS/COS),不占本地磁盘,也不受 Web 节点增减影响;配合 CDN 回源。
- 会话共享:PHP 的
session.save_handler = redis,配置session.save_path = "tcp://redis-ip:6379?auth=密码"。 - 日志集中:ELK 或 Loki 采集,单机
tail在多节点时代毫无意义。 - 监控告警:至少覆盖 CPU、内存、磁盘、网络、Nginx 5xx 比例、Redis 命中率、MySQL 慢查询数、连接数。
- 压测:上线前用
wrk或ab做基线压测,知道自己的单机上限是多少。
# 基线压测示例(在业务低峰、非生产机器上先做) wrk -t4 -c200 -d60s --latency http://www.example.com/api/list ab -n 10000 -c 200 http://www.example.com/ # 压测时同时观察瓶颈在哪 top -b -n 1 | head -15 # CPU iostat -x 1 3 # 磁盘 await > 20ms 说明 IO 瓶颈 free -m # 内存与 swap ss -s # 连接数 curl http://127.0.0.1/fpm-status # PHP 进程池是否打满
七、实施清单:从单机走向分层
- 压测拿到单机基线 QPS,明确当前瓶颈是 CPU、内存、磁盘还是数据库。
- 先把 Web 与数据库分离到两台机器(投入最小、收益最大)。
- 接入 Redis,优先缓存会话和热点查询结果。
- 静态资源迁移到对象存储并接入 CDN,让 CDN 吃掉大部分流量。
- 应用层无状态化改造,为横向扩展做准备。
- Web 层加节点,前面放负载均衡(Nginx upstream 或云 SLB)。
- 数据库做主从读写分离,配置监控与慢查询告警。
- 引入消息队列,把可异步的逻辑剥离出去。
- 完善监控、日志、备份、灰度发布和回滚机制。
- 每次大促前重新压测,更新容量模型。
小结
高流量架构的核心不是“买多大的机器”,而是让每一层都能独立扩容、让绝大部分请求不落到最贵的那一层。顺序永远是:先量化 → 再缓存 → 再横向扩展 → 最后才考虑复杂的拆分。很多团队一上来就要搞微服务和分库分表,结果复杂度上去了,SQL 还是没加索引。
避坑清单:
- 不要迷信配置升级,单机始终有上限,且存在单点风险。
- 应用层必须无状态,否则加多少台机器都没用。
- MySQL 的
max_connections默认 151,连接池参数要和应用线程池对齐,不是越大越好。 - Redis 一定要设
maxmemory和淘汰策略,否则内存满会导致写失败甚至被 OOM。 - 缓存 TTL 要加随机抖动,避免同一时刻大面积过期引发雪崩。
net.core.somaxconn默认 128,高并发下不改会出现连接被丢弃,表现为“明明没到上限却有超时”。- 一切优化都要有压测基线,凭感觉调参等于赌博。
关键词回顾:高流量网站架构、服务器架构规划、分层部署、Nginx 调优、Redis 缓存、MySQL 读写分离、容量规划与压测。
企业QQ咨询




