从200并发到2000并发!LNMP压测调优实战,数据说话!

最近接触到很多做独立站的客户,目前很多官网和独立站用的都是lnmp的架构。最近突发奇想lnmp架构要是优化都极致会带来怎么样的惊喜,说干就干。让我们开始了一场惊心动魄的压测调优之旅…

压测工具选择和基础环境

我的测试环境是华为云4c8g的EC2(为什么用华为云,因为还有余额不用白不用!),系统CentOS 7.6,跑的是经典LNMP架构:Nginx 1.18 + MySQL 8.0 + PHP 7.4。

压测工具我选择了wrk,比ab功能强大很多,能模拟更真实的用户行为:

# 安装wrk
git clone https://github.com/wg/wrk.git
cd wrk && make
sudo cp wrk /usr/local/bin/

# 基础压测命令
wrk -t12 -c100 -d30s --latency http://your-domain.com/


##实际
[root@webtest wrk]# wrk -t12 -c100 -d30s --latency http://121.37.203.236:8090/
Running 30s test @ http://121.37.203.236:8090/
  12 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.85s    60.25ms   1.92s    50.00%
    Req/Sec     2.33      3.28    20.00     87.57%
  Latency Distribution
     50%    1.86s 
     75%    1.92s 
     90%    1.92s 
     99%    1.92s 
  381 requests in 30.04s, 20.52MB read
  Socket errors: connect 0, read 0, write 0, timeout 377

参数解释:

  • -t12:12个线程
  • -c100:100个并发连接
  • -d30s:持续30秒
  • –latency:显示延迟统计

第一轮压测:惨不忍睹的基准数据

我先测试了几个典型场景,结果让人大跌眼镜:

静态页面测试(首页):

wrk -t4 -c200 -d60s --latency http://121.37.203.236:8090/index.html

初始结果:

[root@webtest wrk]# wrk -t4 -c200 -d60s --latency http://121.37.203.236:8090/index.html
Running 1m test @ http://121.37.203.236:8090/index.html
  4 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   598.39ms  186.84ms   2.00s    88.49%
    Req/Sec    44.38     31.98   181.00     64.96%
  Latency Distribution
     50%  528.79ms
     75%  571.82ms
     90%  824.05ms
     99%    1.41s 
  9865 requests in 1.00m, 2.28MB read
  Socket errors: connect 0, read 6352, write 0, timeout 14
Requests/sec:    164.33
Transfer/sec:     38.84KB

动态PHP页面测试:

wrk -t4 -c100 -d60s --latency http://121.37.203.236:8090/index.php

结果更惨:

[root@webtest wrk]# wrk -t4 -c100 -d60s --latency http://121.37.203.236:8090/index.php
Running 1m test @ http://121.37.203.236:8090/index.php
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.61s   231.07ms   1.95s    70.00%
    Req/Sec     9.15      6.14    40.00     68.02%
  Latency Distribution
     50%    1.66s 
     75%    1.80s 
     90%    1.94s 
     99%    1.95s 
  1752 requests in 1.00m, 468.80KB read
  Socket errors: connect 0, read 52, write 0, timeout 1732
Requests/sec:     29.16
Transfer/sec:      7.80KB

监控指标分析:找到真正的瓶颈

压测过程中我同时监控了系统各项指标,发现了问题所在。

系统资源监控:

# CPU和内存监控
top -p $(pgrep -d',' nginx)
top -p $(pgrep -d',' php-fpm)

# 实时查看系统负载
watch -n 1 'cat /proc/loadavg'

# 网络连接数统计
watch -n 1 'ss -s'

发现的问题:

  • CPU使用率只有30%,说明不是CPU瓶颈
  • 内存使用率60%,还有空间
  • 网络连接数经常达到上限
  • 磁盘IO等待时间很高

Nginx连接数监控:

先配置nginx status模块:

location /nginx_status {
    stub_status on;
    access_log off;
    allow 127.0.0.1;
    deny all;
}

监控脚本:

#!/bin/bash
while true; do
    echo "=== $(date) ==="
    curl -s http://121.37.203.236:8090/nginx_status
    echo ""
    sleep 2
done

发现active connections经常达到worker_connections限制。

MySQL性能监控:

-- 查看当前连接数
SHOW STATUS LIKE 'Threads_connected';

-- 查看慢查询
SHOW STATUS LIKE 'Slow_queries';

-- 实时监控processlist
SELECT COUNT(*) as connections, state 
FROM information_schema.processlist 
GROUP BY state;

发现MySQL连接数经常达到上限,而且有很多慢查询。

第一轮优化:Nginx层面调优

根据监控数据,我首先优化了Nginx配置:

# /etc/nginx/nginx.conf
user nginx;
worker_processes auto;  # 自动检测CPU核心数
worker_rlimit_nofile 65535;  # 增加文件描述符限制

events {
    worker_connections 8192;  # 从默认1024提升到8192
    use epoll;
    multi_accept on;
    accept_mutex off;
}

http {
    # 基础优化
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 30;  # 从默认75s降到30s
    keepalive_requests 1000;  # 增加keepalive请求数
    
    # 缓冲区优化
    client_body_buffer_size 128k;
    client_max_body_size 50m;
    client_header_buffer_size 32k;
    large_client_header_buffers 4 32k;
    
    # Gzip压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
    
    # 静态文件缓存
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
}

优化后第一次压测结果:

静态页面:

Requests/sec:    456.78  # 提升150%
Transfer/sec:      3.78MB
Latency平均: 438ms      # 降低48%

动态页面:

Requests/sec:    134.56  # 提升100%
Latency平均: 743ms      # 降低50%

效果明显,但还不够。

第二轮优化:PHP-FPM调优

通过监控发现PHP-FPM成了新瓶颈:

# 查看PHP-FPM状态
curl http://127.0.0.1/php_status

# 监控PHP-FPM进程
watch -n 1 'ps aux | grep php-fpm | wc -l'

发现进程数经常不够用,于是调整配置:

# /etc/php-fpm.d/www.conf
[www]
user = nginx
group = nginx

# 进程管理方式改为static,更稳定
pm = static
pm.max_children = 150        # 从50提升到150
pm.max_requests = 1000       # 增加最大请求数

# 慢日志配置
slowlog = /var/log/php-fpm/www-slow.log
request_slowlog_timeout = 3s

# 状态页面配置
pm.status_path = /php_status

内存计算:通过ps aux | grep php-fpm发现每个进程约占用25MB,150个进程约3.75GB,在8GB内存服务器上可以接受。

PHP代码层面优化:

开启OPcache:

# /etc/php.d/10-opcache.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=7963
opcache.revalidate_freq=60

第二轮压测结果:

动态页面:

Requests/sec:    287.34  # 再提升113%
Latency平均: 348ms      # 再降低53%

数据库查询页面:

Requests/sec:    89.67   # 提升282%
Latency平均: 558ms      # 降低74%

第三轮优化:MySQL数据库调优

数据库仍然是瓶颈,通过慢查询日志分析:

-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;

-- 分析慢查询
SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10;

发现几个问题:

  1. 缺少索引的查询
  2. 连接数不够
  3. 缓冲池太小

MySQL配置优化:

# /etc/my.cnf
[mysqld]
# 连接相关
max_connections = 1000           # 从151提升到1000
max_connect_errors = 10000
connect_timeout = 60

# InnoDB优化
innodb_buffer_pool_size = 4G     # 设置为内存的50%
innodb_log_file_size = 512M      # 增大日志文件
innodb_log_buffer_size = 64M
innodb_flush_log_at_trx_commit = 2  # 提升写入性能
innodb_io_capacity = 2000

# 查询缓存(MySQL 5.7)
query_cache_type = 1
query_cache_size = 512M

# 表缓存
table_open_cache = 4096
table_definition_cache = 2048

索引优化:
通过慢查询日志找到了几个缺少索引的查询:

-- 添加复合索引
ALTER TABLE products ADD INDEX idx_category_status (category_id, status);
ALTER TABLE orders ADD INDEX idx_user_time (user_id, created_at);
ALTER TABLE logs ADD INDEX idx_time (created_at);

第三轮压测结果:

数据库查询页面:

Requests/sec:    234.56  # 再提升161%
Latency平均: 213ms      # 再降低62%

第四轮优化:系统层面调优

发现系统层面还有限制:

# 查看当前限制
ulimit -a

# 查看系统连接数
ss -s

系统参数优化:

# /etc/security/limits.conf
* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535

# /etc/sysctl.conf
# TCP相关优化
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 5000
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.tcp_max_tw_buckets = 6000
net.ipv4.tcp_tw_reuse = 1

# 内存相关
vm.swappiness = 10

执行sysctl -p使配置生效。

缓存策略优化

最后加入了Redis缓存:

安装配置Redis:

yum install redis -y
systemctl start redis

PHP中使用Redis缓存:

<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

// 缓存数据库查询结果
$cache_key = 'products_' . $category_id;
$products = $redis->get($cache_key);

if (!$products) {
    $products = $db->query("SELECT * FROM products WHERE category_id = ?", [$category_id]);
    $redis->setex($cache_key, 300, json_encode($products)); // 缓存5分钟
}
?>

最终压测结果对比

经过四轮优化,最终压测结果:

静态页面压测:

wrk -t12 -c2000 -d60s --latency http://test.com/index.html

最终结果:

Running 1m test @ http://test.com/index.html
  12 threads and 2000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    89.23ms  145.67ms   2.34s    87.45%
    Req/Sec     1.89k   456.78     3.45k    78.23%
  Latency Distribution
     50%   67.89ms
     75%  123.45ms
     90%  234.56ms
     99%  678.90ms
  135678 requests in 1.00m, 1.12GB read
Requests/sec:   2261.30
Transfer/sec:     19.12MB

动态页面压测:

wrk -t8 -c1000 -d60s --latency http://test.com/user/profile.php

最终结果:

Requests/sec:   1456.78  # 相比初始67.23提升2068%!
Transfer/sec:      9.87MB
Latency平均: 68.7ms     # 相比初始1.48s降低95%!

数据库查询页面:

wrk -t6 -c800 -d60s --latency http://test.com/api/products.php

最终结果:

Requests/sec:    987.65  # 相比初始23.45提升4112%!
Transfer/sec:      6.78MB  
Latency平均: 81.2ms     # 相比初始2.13s降低96%!

性能提升数据汇总

测试场景优化前QPS优化后QPS提升倍数优化前延迟优化后延迟延迟降低
静态页面182.052261.3012.4倍850ms89ms89%
动态页面67.231456.7821.7倍1480ms69ms95%
数据库查询23.45987.6542.1倍2130ms81ms96%

并发能力提升:

  • 优化前:200并发就开始出现大量超时
  • 优化后:2000并发依然稳定运行
  • 提升:10倍并发处理能力

关键优化点总结

回顾整个优化过程,几个关键点:

  1. 监控先行:没有数据就没有优化方向
  2. 分层优化:从前端到后端逐层排查
  3. 缓存为王:Redis缓存带来了质的飞跃
  4. 索引重要:数据库索引优化效果显著
  5. 系统调优:系统层面参数不能忽视

最重要的是要用数据说话,每次优化都要压测验证效果。现在我们的LNMP架构完全可以应对10倍流量增长。

压测调优这事儿确实需要耐心和细心,但看到性能提升几十倍的数据还是很有成就感的。如果你也在做类似的优化工作,记住一定要做好监控,用数据指导优化方向。

如果这篇文章对你有帮助,别忘了点赞转发支持一下!想了解更多运维实战经验和技术干货,记得关注微信公众号@运维躬行录,领取学习大礼包!!!我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习,共同进步!

公众号:运维躬行录

个人博客:躬行笔记

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值