最近接触到很多做独立站的客户,目前很多官网和独立站用的都是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;
发现几个问题:
- 缺少索引的查询
- 连接数不够
- 缓冲池太小
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.05 | 2261.30 | 12.4倍 | 850ms | 89ms | 89% |
动态页面 | 67.23 | 1456.78 | 21.7倍 | 1480ms | 69ms | 95% |
数据库查询 | 23.45 | 987.65 | 42.1倍 | 2130ms | 81ms | 96% |
并发能力提升:
- 优化前:200并发就开始出现大量超时
- 优化后:2000并发依然稳定运行
- 提升:10倍并发处理能力
关键优化点总结
回顾整个优化过程,几个关键点:
- 监控先行:没有数据就没有优化方向
- 分层优化:从前端到后端逐层排查
- 缓存为王:Redis缓存带来了质的飞跃
- 索引重要:数据库索引优化效果显著
- 系统调优:系统层面参数不能忽视
最重要的是要用数据说话,每次优化都要压测验证效果。现在我们的LNMP架构完全可以应对10倍流量增长。
压测调优这事儿确实需要耐心和细心,但看到性能提升几十倍的数据还是很有成就感的。如果你也在做类似的优化工作,记住一定要做好监控,用数据指导优化方向。
如果这篇文章对你有帮助,别忘了点赞转发支持一下!想了解更多运维实战经验和技术干货,记得关注微信公众号@运维躬行录,领取学习大礼包!!!我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习,共同进步!
公众号:运维躬行录
个人博客:躬行笔记