服务器性能优化怎么做

结论:服务器性能优化必须按"测—定位—改—再测"的闭环来做,先用 wrk/ab 压出基线 QPS,再用 USE 方法定位瓶颈在哪一层(CPU、内存、磁盘 IO、网络、数据库),然后从最贵的那个瓶颈改起。多数网站的实际瓶颈在数据库和磁盘 IO,而不是 CPU,盲目加机器是最贵的优化方式。

服务器性能优化(Performance Tuning)最常见的失败姿势是"抄参数":从网上扒一份 sysctl.conf 粘上去,机器确实看起来"配置很高",QPS 却纹丝不动。原因在于性能永远由最慢的那一环决定(木桶效应),不在瓶颈点上的优化都是无用功。截至 2026 年,一台普通 4 核 8 GB 的云主机,跑一个优化过的动态站点,做到 2000~5000 QPS 是完全现实的。本文按系统层、Web 层、数据库层、应用层四层给出可落地的调优清单。

优化前如何确定性能基线?

没有基线的优化等于不知道自己在往哪走。先建立三个可量化的指标:单机极限 QPS(每秒请求数)、平均响应时间 RT(Response Time)、P99 延迟(99% 请求的完成时间)。注意 P99 比平均值重要得多——平均值好看而 P99 高达数秒,意味着确实有一部分用户一直在卡。

用 wrk 做 HTTP 压测:

bash
# 安装(Ubuntu 24.04 / Debian 12)
apt install -y wrk

# 压测:12 线程、400 并发连接、持续 30 秒
wrk -t12 -c400 -d30s --latency http://127.0.0.1/

# 带 POST body 的压测(需 lua 脚本)
wrk -t8 -c200 -d30s -s post.lua --latency http://127.0.0.1/api/order

# 简易替代工具 ab(ApacheBench),适合单次快速验证
ab -n 10000 -c 200 http://127.0.0.1/

压测时务必注意:压测机不能和被压测程序在同一台机器上(会互相抢 CPU);并发要从低到高阶梯式增加(50/100/200/400),找到 QPS 不再增长而 RT 开始飙升的那个拐点,那就是容量上限。

系统层怎么优化内核参数与资源限制?

系统层优化的目标是让内核有能力承接更高的并发连接,并避免在高负载下出现莫名其妙的丢包与连接失败。主要改动三处:文件描述符上限、TCP 栈参数、磁盘 IO 调度。

先看瓶颈在哪。用 USE 方法(Utilization 使用率、Saturation 饱和度、Errors 错误数)逐项确认:

bash
top -H                  # CPU 与单线程热点,-H 显示线程便于定位 Java 线程
vmstat 1                # 看 r(运行队列)与 si/so(swap 交换),si/so 长期非 0 说明内存不足
iostat -x 1             # 看 %util(磁盘使用率)与 await(IO 等待毫秒)
sar -n DEV 1            # 看网络吞吐与丢包
ss -s                   # 查看连接总数与各状态分布
dmesg -T | tail -50     # 检查是否有 OOM kill 或 TCP 丢包告警

确认后修改 /etc/sysctl.d/99-tuning.conf(CentOS/RHEL/Ubuntu 通用写法):

conf
# 文件描述符与本地端口范围,支撑高并发短连接
fs.file-max = 2000000
fs.nr_open  = 2000000
net.ipv4.ip_local_port_range = 1024 65535

# TCP 连接复用与回收,应对大量 TIME_WAIT
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_max_tw_buckets = 262144

# 连接队列,防止突发流量丢弃握手包
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_syncookies = 1        # 防 SYN Flood,一律开启

# 缓冲区自适应(现代内核推荐交给自动调优)
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864

# swap 使用倾向,数据库机器建议设为 1 或直接关闭 swap
vm.swappiness = 1
vm.dirty_ratio = 20
vm.dirty_background_ratio = 5

应用后立即生效并持久化:

bash
sysctl -p /etc/sysctl.d/99-tuning.conf

# 提升进程资源限制,/etc/security/limits.conf 或 systemd 服务单元
cat >> /etc/security/limits.conf <<'EOF'
* soft nofile 1000000
* hard nofile 1000000
* soft nproc  65535
* hard nproc  65535
EOF

# systemd 服务的限制要单设,limits.conf 对它不生效
mkdir -p /etc/systemd/system/nginx.service.d
cat > /etc/systemd/system/nginx.service.d/override.conf <<'EOF'
[Service]
LimitNOFILE=1000000
EOF
systemctl daemon-reload && systemctl restart nginx

磁盘 IO 调度器:NVMe SSD 建议用 none(完全公平电梯 mq-deadline 也可),SATA SSD 用 mq-deadline,机械盘用 bfq。

bash
# 查看当前调度器
cat /sys/block/nvme0n1/queue/scheduler

# 临时切换(NVMe 推荐 none,避免调度开销)
echo none > /sys/block/nvme0n1/queue/scheduler

# 永久生效:GRUB 加 elevator=none 或写 udev 规则

Web 层 Nginx 怎么调?

Nginx 调优的第一原则是:worker 进程数等于 CPU 核数,并把连接数上限放大。默认情况下 worker_processes 1、worker_connections 1024,这意味着一台 16 核机器只用上了 1 核,白白浪费 15 核。

/etc/nginx/nginx.conf 主上下文的关键配置:

nginx
user www-data;
worker_processes auto;              # 或者写死为 CPU 核数,如 16
worker_cpu_affinity auto;           # CPU 亲和性,减少进程跨核迁移
worker_rlimit_nofile 1000000;       # 必须 >= worker_connections

events {
    use epoll;                      # Linux 下必须使用 epoll
    worker_connections 65535;
    multi_accept on;
}

http {
    sendfile        on;             # 静态文件零拷贝发送
    tcp_nopush      on;             # 配合 sendfile,减少小包
    tcp_nodelay     on;             # keepalive 连接上禁用 Nagle,降低延迟
    keepalive_timeout 65;
    keepalive_requests 1000;        # 单连接可承载的请求数,长连接场景可放大

    # 压缩:文本类收益巨大,注意 gzip 对已压缩的图片/视频无效
    gzip on;
    gzip_comp_level 5;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript
               application/xml image/svg+xml;
    gzip_vary on;

    # 静态资源缓存头,配合 CDN 效果最佳
    map $sent_http_content_type $expires {
        default                    off;
        ~image/                    30d;
        ~text/css                  7d;
        ~application/javascript    7d;
    }
    expires $expires;

    # 关闭不必要的日志 IO( healthz 与静态资源)
    access_log off;
}

三个容易被忽略但收益明显的点:

  • 静态资源分离:图片、JS、CSS 交给 CDN 或独立的 Nginx location 直接返回,不要转发给 Tomcat/PHP-FPM,这一步通常能省掉 60% 以上的后端负载。
  • 开启 HTTP/2:listen 443 ssl http2;(Nginx 1.25.1+ 起语法改为独立的 http2 on;),多路复用显著降低高并发延迟。
  • open_file_cache:缓存文件描述符,避免静态文件每次都 stat,命中率高的站点收益明显。

数据库层为什么是主要瓶颈?

因为绝大多数动态请求最终都要查库,而数据库的每一次磁盘随机 IO 都比应用层计算贵几个数量级。一条未加索引的 SELECT * FROM orders WHERE phone='138...' 在百万行表上要走全表扫描,耗时几百毫秒至数秒;加上索引后可以降到 1 毫秒以内。这是投入产出比最高的优化手段。

先用慢查询日志找出真正的坏 SQL:

sql
-- 临时开启(MySQL 8.x),注意 long_query_time 单位为秒,0.5 表示 500 毫秒
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.5;
SET GLOBAL log_queries_not_using_indexes = 'ON';

-- 查看当前是否有锁等待
SHOW PROCESSLIST;

-- 用 EXPLAIN 分析单个 SQL 的执行计划,重点关注 type 与 rows
EXPLAIN SELECT * FROM orders WHERE user_id = 10086 AND status = 1\G

永久写入配置文件 /etc/mysql/conf.d/tuning.cnf:

ini
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.5
log_queries_not_using_indexes = 1

# InnoDB 缓冲池:专用数据库机器设为物理内存的 60%~70%
innodb_buffer_pool_size = 6G
innodb_buffer_pool_instances = 4
innodb_log_file_size = 1G
innodb_flush_log_at_trx_commit = 2      # 折中:每秒刷盘,宕机最多丢 1 秒
innodb_flush_neighbors = 0              # SSD 上关闭相邻页刷写
innodb_io_capacity = 2000               # 按实际磁盘 IOPS 调整
innodb_io_capacity_max = 4000

max_connections = 1000
thread_cache_size = 64
table_open_cache = 4096
tmp_table_size = 256M
max_heap_table_size = 256M

用 mysqldumpslow 汇总慢查询,找出 Top SQL 再逐个加索引:

bash
# 按平均耗时排序,取前 10 条最慢的 SQL 模式
mysqldumpslow -s at -t 10 /var/log/mysql/slow.log

# 添加了索引后验证效果
mysql -uroot -p -e "CREATE INDEX idx_orders_user_status ON orders(user_id, status);"
mysql -uroot -p -e "ANALYZE TABLE orders;"

应用层有哪些立竿见影的优化?

应用层优化的三个关键词是:缓存、异步、批量。任何被重复计算或重复查询的结果都应该缓存;任何不需要同步返回的操作都应该丢进消息队列异步处理;任何循环里的单条 SQL 都应该改成批量操作。

典型收益点清单:

  • Redis 缓存热点数据:首页、配置表、字典类数据,命中率可达 90% 以上,数据库压力直降一个数量级。注意设置随机过期时间防止缓存雪崩。
  • 连接池:数据库连接建立一次要三次握手加认证,约需 5~20 毫秒。用连接池(HikariCP、Druid)复用连接,maximumPoolSize 一般按 CPU 核数 × 2 + 磁盘数估算。
  • 异步化:发短信、写日志、生成缩略图、数据统计这类操作丢到 Redis 队列或 MQ(截至 2026 年主流选择是 RabbitMQ、Kafka、云原生 RocketMQ),接口 RT 可下降 50% 以上。
  • 批量写入:把 1000 条 INSERT 合并成一次多行 INSERT 或使用 LOAD DATA,性能差距可达 20 倍。
  • 避免 N+1 查询:循环中逐条查库是新手最常见的性能杀手,改成 WHERE id IN (...) 一次性取出。

优化后务必回到第一步重新压测,对比基线数据确认收益,并把结果记录下来形成容量档案。

四层优化的收益与优先级怎么排?

结论:按"数据库 → 应用 → Web → 系统"的顺序自上而下做,前两层拿走 80% 的收益。 下面这张表把各层的典型动作、预期收益与实施成本放在一起,便于直接排优先级。

优化层次典型动作预期收益实施成本优先级
数据库层补索引、改写慢 SQL、开慢查询日志、调 innodb_buffer_pool_size5~10 倍低最高
应用层Redis 缓存热点、连接池、异步化、批量写入3~5 倍中高
Web 层worker_processes 对齐核数、gzip、静态分离、HTTP/220%~50%低中
系统层sysctl 内核参数、ulimit、IO 调度器10%~30%中,误操作风险高低
架构层横向扩容、读写分离、CDN 分担近似线性高瓶颈固化后再做

使用方法是自上而下逐层执行,每做完一层就回到第一步重新压测一次:如果 QPS 已达业务目标即可停止,没必要为了"参数看起来专业"去改动低收益的系统层配置。判断是否需要继续往下的标准只有一个——当前瓶颈是否已经在上一层被消除。

常见误区 / 排错提示

  • 误区一:一上来就改内核参数。 绝大多数性能问题的答案在 SQL 和索引里,而不是 sysctl.conf。建议顺序永远是:先查慢 SQL 与索引 → 再看锁等待 → 再查磁盘 IO → 最后才是内核参数。
  • 误区二:盲目关闭 swap。 虽然 swappiness=1 能避免数据库被换出,但完全禁用 swap 会让系统在内存耗尽时直接触发 OOM Killer,随机杀掉你最重要的那个进程。保留 1~2 GB 的 swap 作为安全垫更稳妥。
  • 误区三:innodb_buffer_pool_size 设得过大导致 OOM。 它不是孤立占用,连接缓存、临时表、排序缓冲区都会额外消耗内存。专用数据库机器设 60%~70%,混合部署的机器设 30%~40% 即可,务必留足余量。
  • 误区四:对已经压缩的资源开 gzip。 JPEG、PNG、MP4、ZIP 本身已是压缩格式,再开 gzip 只会白白消耗 CPU,收益为负甚至体积变大。只对文本类(HTML/CSS/JS/JSON/XML)开启。
  • 排错:压测时 QPS 上不去,ss -s 显示大量 TIME_WAIT。 高并发短连接耗尽了本地端口。解决方式是开启 tcp_tw_reuse、调大 ip_local_port_range,或在压测工具侧改用长连接(wrk 默认就是长连接)。
  • 排错:CPU 不高但 RT 很长。 通常是 IO 阻塞或等待下游服务。iostat -x 1 看 %util 是否接近 100%,await 是否超过 10 ms;若都正常,检查是否在同步调用外部 HTTP 接口或 DNS 解析。

常见问题(FAQ)

服务器性能优化应该先优化哪一层?

结论:从数据库的慢查询和索引开始,这里通常是收益最高的地方。实践中的经验比例大概是:数据库索引与 SQL 优化能带来 5~10 倍提升,应用层加 Redis 缓存能再带来 3~5 倍,Nginx 与内核参数调优通常在 20%~50% 的量级。只有当这三层都做到位后,才轮到考虑加机器或做架构拆分。

4 核 8 GB 的服务器能支撑多少并发?

结论:静态资源可以轻松上万并发,动态接口一般在 500~3000 QPS,具体取决于单请求耗时。粗略公式为:理论 QPS ≈ 并发数 ÷ 平均响应时间(秒)。若平均 RT 为 50 毫秒,400 并发下 QPS 约 8000;若 RT 变成 500 毫秒,同样并发只有 800 QPS。因此优化响应时间比堆配置更有效。

gzip 压缩级别设多少合适?

结论:设 4~5 即可,不要追求最高压缩比。gzip 从 1 到 9 的变化中,1→5 压缩率提升明显而 CPU 增长平缓,6→9 几乎只增加 CPU 开销而不怎么减小体积。对于静态资源,更推荐先用 gzip 预压缩生成 .gz 文件并开启 gzip_static on,或者在 Nginx 1.26+ 上直接启用 Brotli 模块(brotli_comp_level 5)。

加了索引为什么查询还是很慢?

结论:多半是索引没被用上。常见原因有五类:在索引列上用了函数或隐式类型转换(如 WHERE phone = 13800138000,phone 是 varchar);使用了违反最左前缀原则的联合索引;OR 连接导致索引失效;返回行数超过表的 20%~30% 被优化器判定全表扫描更快;以及统计数据过期需用 ANALYZE TABLE 重新收集。

压测结果和真实用户体验不一致怎么办?

结论:先看压测场景是否覆盖了真实的请求混合比与数据分布。只压首页得到的数字毫无意义:真实流量是首页、列表、详情、下单、支付按一定比例混合的,而且还包含不同大小的图片请求和带不同参数的搜索。改进方法是用 Nginx 日志或 APM 工具统计真实请求比例,再用脚本构造与之匹配的混合压测场景,同时把 P99/P999 延迟而非平均 RT 作为验收指标。

什么时候该加机器而不是继续优化?

结论:当瓶颈固化在单机物理上限且已无明显浪费时。判断标准有三条:慢 SQL 已清理、热点已入缓存、静态资源已上 CDN,但 top 显示 CPU 依然长期超过 70% 且 iostat 的 %util 接近饱和。此时横向扩容比继续抠优化更划算。注意先确认应用已经无状态化,否则加了机器也无法分摊流量。