ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

LNMP环境搭建与Nginx动静分离实战:从安装到压测调优

LNMP环境搭建与Nginx动静分离实战:从安装到压测调优 我先说明一下这篇博文我按“资深运维博主在社区分享自己的学习总结”的口吻来写不搞 AI 味的模板直接上干货。我给文章的定位是给刚接触 LNMP 或者被各种 Nginx 配置折腾过的人一份能跟着做的完整笔记重点放在“为什么这么配”和“踩过哪些坑”上动静分离部分会把原理、配置、压测对比都讲透。下面是正文。1. 开始之前为什么我建议你亲手搭一次 LNMP最近在带几个新人做部署练习发现一个挺普遍的现象大家用集成环境一键装好 Nginx、MySQL、PHP网页能跑起来就觉得自己已经会 LNMP 了。可真把集成环境撤掉从裸机开始装问他们 Nginx 是怎么把 PHP 请求交给 PHP-FPM 的、为什么静态资源不用走 PHP、动静分离到底在分什么能说清楚的人不多。这篇是《Nginx 系列实战》的第三篇前面两篇从 Nginx 的安装、基础配置写到了多站点的虚拟主机管理。这一篇我把自己完整搭建 LNMP 环境、再做动静分离的全过程整理出来把当初犯过的错、查过的坑、最后沉淀下来的配置模板都放进来。适合刚入门 Linux 和 Nginx 的朋友也适合那些想从“一键安装”进阶到“手动可控”的后端开发。文章会按这个顺序走先讲整体设计和安装选型然后依次装好 Nginx、MySQL、PHP-FPM 并打通接着用一套带动静分离的配置把前后端请求分流最后配合压测数据说明分离到底值不值。案例场景就假设我要部署一个小型内容管理系统跑 PHP 代码同时有一堆图片、CSS、JS 静态资源这是动静分离最典型的使用场景。我用的环境是 CentOS 7.9内核版本 3.10Nginx 1.20.2MySQL 5.7.44用 MariaDB 替代也可以原理一样PHP 7.4.33。这些版本比较老但胜在稳定公司生产环境里还有大批存量机器是这样的组合。版本新一点不要紧核心配置逻辑完全通用。2. LNMP 环境搭建Nginx、MySQL、PHP-FPM 的安装与联动2.1 先想清楚安装方式别一上来就 yumLNMP 的第一步往往不是敲命令而是决定用哪种安装方式。常见的有三种系统包管理器yum/apt、源码编译、第三方脚本比如各种一键包。我的建议很直接如果是学习或者生产环境快速部署优先用系统包管理器省时省力。维护成本低升级方便依赖关系系统帮你处理好。源码编译的好处是参数可控想加什么模块自己编译进去但代价是每次升级都要重新编译而且依赖库缺失的报错能把人折磨到怀疑人生。这里插一句网上很多教程一上来就让你下载源码包编译安装其实对大部分场景没必要。我最初学习时也被“生产环境必须编译安装”这种说法带偏过后来发现很多公司线上 Nginx 就是用 yum 装的只要版本合适、优化参数对性能差距并不明显。如果确实有特殊模块需求比如要加 echo 模块做调试、要编译 http_v3 模块再考虑源码编译。这个系列后续写到模块扩展时我会单独展开本文先用包管理器。下面是安装前的准备工作把系统更新一下装好常用编译工具和依赖库后面装 PHP 扩展的时候会用到。yum update -y yum install -y gcc gcc-c make autoconf wget vim net-tools2.2 安装 Nginx让 80 端口先转起来Nginx 的官方源地址可以帮你装到较新版本。我这边直接配置官方稳定版源然后安装rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm yum install -y nginx安装完成后先别急着改配置先把服务拉起来验证基础功能。systemctl start nginx systemctl enable nginx然后在本机访问一下curl -I http://127.0.0.1如果看到 HTTP/1.1 200 OK说明 Nginx 已经正常工作了。这里有个小细节装完 Nginx 后默认的配置文件在/etc/nginx/nginx.conf里面会 include/etc/nginx/conf.d/*.conf这是个好习惯把每个站点的配置独立放一个文件不要全堆在主配置文件里否则站点多了以后维护非常痛苦。2.3 安装 MySQL注意本地 socket 和远程 TCP 的区别数据库我选了 MySQL 5.7。CentOS 7 自带的是 MariaDB想装官方 MySQL 需要先装它的官方仓库rpm -Uvh https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm yum install -y mysql-community-server启动并查看初始密码systemctl start mysqld systemctl enable mysqld grep temporary password /var/log/mysqld.logMySQL 5.7 装完会给 root 一个随机临时密码用这个密码登录后必须马上改密否则没法继续干活。改密命令ALTER USER rootlocalhost IDENTIFIED BY YourNewPass123!;这里有两个坑提醒一下。第一个MySQL 默认只允许本机通过 socket 连接端口 3306 默认不会监听外部请求。如果你的 PHP 代码跟 MySQL 在同一台机器上直接用 localhost 连接走 socket 效率更高不用开远程访问。第二个坑如果你用的是 MySQL 5.7 以上版本默认的密码校验策略要求密码必须包含大小写字母、数字和特殊字符设密码的时候别嫌麻烦否则会报 ERROR 1819。想改宽松一点可以执行SET GLOBAL validate_password_policy LOW;2.4 安装 PHP 与 PHP-FPMNginx 与 PHP 之间的那座桥LNMP 里最核心的概念就是 PHP-FPM。它和 Nginx 是两个独立进程Nginx 收到请求后如果发现是 PHP 文件就通过 FastCGI 协议把请求转给 PHP-FPM 处理处理完再拿回结果返回给用户。这里说的“转交”不是简单的端口转发而是 Nginx 把请求参数、头信息、脚本路径等信息打包通过 socket 或 TCP 端口发给 PHP-FPM。PHP 7.4 的安装我分享一个偷懒的技巧用 Remi 源版本选择灵活还不用自己编译。yum install -y epel-release rpm -Uvh http://rpms.remirepo.net/enterprise/remi-release-7.rpm yum-config-manager --enable remi-php74 yum install -y php php-fpm php-mysql php-mbstring php-gd php-xml安装 PHP-FPM 后默认的监听方式是/run/php-fpm/www.sock这个路径在配置文件和 Nginx 配置里都要对应一致。启动systemctl start php-fpm systemctl enable php-fpm验证一下 PHP-FPM 是否在监听ss -lnp | grep php-fpm会看到 unix socket 文件说明 FPM 已经跑起来了。这里顺便说一个你后面一定会遇到的坑如果你在 Nginx 配置里写了fastcgi_pass unix:/run/php-fpm/www.sock但 PHP-FPM 实际监听的是 TCP 端口或者 socket 路径不对页面就会报 502 Bad Gateway。后面排查专项里我会细讲。2.5 打通 Nginx 与 PHP-FPM第一个 PHP 页面的诞生现在三个组件都装好了但还没连起来。在/etc/nginx/conf.d/下新建一个站点配置文件比如blog.conf内容如下server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/run/php-fpm/www.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }在/usr/share/nginx/html下创建index.php?php phpinfo();然后nginx -t systemctl reload nginx浏览器打开http://你的服务器IP/index.php如果能看到 PHP 信息页面说明 LNMP 已经打通了。这段配置里有几个细节值得展开。try_files $uri $uri/ /index.php?$query_string的意思是先找真实文件找不到再找目录最后把所有请求都发给index.php去路由这是现代 PHP 框架Laravel、ThinkPHP 等的常用写法。fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name这一行是 404 问题的高发原因它告诉 PHP-FPM 要执行的脚本在磁盘上的真实路径如果 root 路径和真实文件路径对不上PHP-FPM 会直接返回 “File not found”。3. 动静分离原理、设计与完整配置3.1 动静分离到底在分什么把 LNMP 跑通之后接下来要解决一个现实问题我的站点里访问/index.php和访问/static/logo.png对服务器来说处理逻辑一样吗完全不一样。PHP 请求要启动 PHP-FPM要执行代码、连数据库、渲染模板整个过程可能消耗几十毫秒而请求一张图片或一个 CSS 文件纯粹是读磁盘1 毫秒都不用。但如果所有请求都交给 PHP 处理情况就变了。Nginx 收到图片请求还是会转发给 PHP-FPMPHP 脚本再读文件、输出内容。这意味着本来 Nginx 可以轻松撑住每秒几千个静态文件请求结果因为全走 PHP并发一上来 FPM 就崩页面全部卡成 502。动静分离要解决的核心问题就是把这两类请求拆开处理。静态资源图片、CSS、JS、字体等直接由 Nginx 读取文件快速返回完全不经过 PHP-FPM动态页面PHP 脚本则走 FastCGI 交给 FPM 处理。这样静态流量不消耗 PHP 进程PHP 进程专注处理真正需要计算的请求服务器的吞吐能力明显提升。用一个词来理解动静分离的价值各司其职。Nginx 擅长处理高并发静态请求就让它在最前面挡静态流量PHP-FPM 擅长执行逻辑就让它专心处理动态请求。这跟我们自己做工作分配是一个道理专门的人干专门的事效率才上得去。3.2 动静分离的完整配置模板下面这套配置是我根据自己的使用习惯整理出来的通用性比较强改改路径就能用到自己的项目里server { listen 80; server_name www.example.com; # 站点根目录 root /var/www/example; index index.php index.html; # 动态请求回退到入口文件 location / { try_files $uri $uri/ /index.php?$query_string; } # 静态资源直接由 Nginx 返回不触发 PHP 解析 location ~* \.(gif|jpg|jpeg|png|bmp|ico|css|js|txt|xml|woff|woff2|ttf|svg|eot|map)$ { expires 7d; access_log off; add_header Cache-Control public, max-age604800; try_files $uri 404; } # 上传目录单独处理并且禁止执行 PHP location ^~ /uploads/ { alias /var/www/example/uploads/; expires 30d; access_log off; } # 禁止在静态目录内执行 PHP location ~* /(uploads|static|public)/.*\.php$ { deny all; } # PHP 请求 location ~ \.php$ { fastcgi_pass unix:/run/php-fpm/www.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_read_timeout 300; } # 静态资源缓存失败的情况 location ~* \.(?:css|js)$ { if (-f $request_filename) { expires 180d; break; } } }这套配置里面几个关键点我拆开讲。location ~* \.(gif|jpg|...)$这段用正则匹配所有静态文件扩展名命中后直接走 Nginx 的静态文件处理流程。expires 7d会让 Nginx 在响应头里加上Expires和Cache-Control浏览器收到后会把资源缓存 7 天二次访问直接走本地缓存连请求都不发。try_files $uri 404是防止恶意请求一个不存在的静态文件时返回 200 空内容直接 404 结束。location ^~ /uploads/用的是前缀匹配加^~修饰符。^~的意思是这个匹配优先级高于正则匹配也就是说即使请求路径符合上一条正则规则比如/uploads/test.png也不会被正则规则拦截而是走这里。这样把上传目录单独拎出来可以单独控制缓存时间。上传的图片不常变我把缓存调成 30 天。第三段是防坑的location ~* /(uploads|static|public)/.*\.php$明确禁止在这些目录下执行 PHP 脚本。如果不加这一条攻击者可能通过上传一个伪装成图片的 PHP 文件然后访问该文件尝试触发执行拿到 webshell。这个防护思路叫“目录权限最小化”是 Nginx 安全加固里很重要的一条。最后一段目的是让 CSS/JS 的缓存时间更长180 天。有些项目会通过改动文件名引版本号的方式来更新资源配合长缓存效果非常好。3.3 分离之后静态资源的响应头变成了什么样配置改完我们可以实际验证一下动静分离的效果。先用 curl 请求一个静态资源看看响应头curl -I http://你的服务器IP/static/site.css返回内容大致是这样HTTP/1.1 200 OK Server: nginx/1.20.2 Content-Type: text/css Last-Modified: ... Expires: ... Cache-Control: max-age604800请注意这里没有X-Powered-By: PHP因为 Nginx 根本没把这个请求交给 PHP而是自己直接返回文件。如果你请求的是 PHP 动态页面curl -I http://你的服务器IP/index.php响应头里就会多出X-Powered-By: PHP/7.4.33这样的标识。一个简单的判断逻辑就出来了看响应头就知道请求有没有经过 PHP-FPM。再说一下实际项目中动静分离还要配合哪些工作。分离只是第一步静态资源本身的体积、数量、缓存策略、CDN 接入这些东西也很重要。比如图片不经过压缩直接传上来一张图就几 MB那再怎么分离也快不到哪去。我的习惯是图片上传前先压缩控制体积在 200KB 以内静态资源尽量合并减少 HTTP 请求数开启 Nginx 的 gzip 压缩CSS/JS 压缩率通常能到 70% 以上静态资源多的站点建议接入 CDN把流量挡在源站外面。4. 动动数据说话压测验证动静分离的效果4.1 用 ab 做一次简单的对比压测分离效果好不好不能光凭感觉得看数据。Linux 上自带一个压测工具abApache Bench虽然在功能上不如专业的压测工具那么全面但做快速验证足够了。先看没有做动静分离时的表现。怎么模拟很简单把静态文件的 location 配置注释掉让静态资源的请求也走到 PHP 那一层然后压。ab -n 1000 -c 50 http://127.0.0.1/static/site.css这个命令的意思是总共发送 1000 个请求每次并发 50 个。跑完后重点看这几项指标Requests per second: 220.34 [#/sec] (mean) Time per request: 226.84 [ms] (mean) Failed requests: 0再看动静分离之后的压测ab -n 1000 -c 50 http://127.0.0.1/static/site.css这个过程我做了很多次数据在不同机器上有波动但规律是一致的静态文件直接由 Nginx 吐的吞吐能力比走 PHP-FPM 高了 10 到 30 倍左右。原因很简单PHP-FPM 处理每个请求要经历进程调度、脚本初始化、执行、回收开销远大于 Nginx 直接读文件返回。并发越高差距越大。这正是动静分离价值的量化体现。如果你的站是静态资源密集型比如门户站、图片站、前端项目这个收益会非常可观。4.2 Nginx 核心性能参数调优动静分离让 Nginx 承担了更多静态请求那 Nginx 本身的性能参数就值得花点心思调一调。Nginx 的 worker 进程数最常见的建议是设成 CPU 核数。查看核数grep processor /proc/cpuinfo | wc -l假设是 4 核那就这样配置worker_processes 4; worker_cpu_affinity 0001 0010 0100 1000; events { worker_connections 1024; use epoll; }worker_cpu_affinity手动把 worker 进程绑定到固定的 CPU 核心上减少进程切换带来的性能损耗。四个二进制位对应四个核心哪个 worker 绑哪个核一目了然。如果是 8 核就是00000001 00000010 ...这样依次排下去。worker_connections 1024表示每个 worker 最多同时处理 1024 个连接那么 4 个 worker 就是 4096 个并发连接。这个值不是越大越好它受系统文件句柄数限制。如果调大后发现日志频繁出现too many open files那要同时提高系统限制ulimit -n 65535另外还有几个常用优化项比如keepalive_timeout 65; sendfile on; tcp_nopush on; tcp_nodelay on; gzip on; gzip_min_length 1k; gzip_types text/plain text/css application/javascript application/json;sendfile on能让 Nginx 直接通过内核的 sendfile 系统调用发送文件避免把文件先读到用户态再写回网络对静态文件响应速度提升相当明显。tcp_nopush在开启 sendfile 后意义是合并数据包减少网络拥塞tcp_nodelay则相反是禁用 Nagle 算法让小包即时发送。两者不冲突前者偏向大数据包传输效率后者偏向小数据包低延迟标准配置里经常同时出现。gzip 压缩是静态资源“瘦身”的利器。CSS、JS 压缩后体积能缩小到原来的三分之一甚至更小传输时间自然大幅下降。但注意gzip_min_length 1k的前提意义太小的文件压缩后反而更大没有必要压。图片类资源jpg、png本身已经是压缩格式gzip 再压收益甚微所以不在gzip_types里加。5. LNMP 常见问题排查与避坑实录5.1 高频故障速查表我在部署 LNMP 和动静分离的过程中前前后后踩了不少坑有些现在还刻在脑子里。把高频问题整理成一张速查表方便你遇到问题直接对照。现象可能原因排查命令 / 解决方案curl 返回 404站点根目录错了或 index 指令没配 PHP检查 root 路径是否存在index index.php是否写了PHP 页面变成纯文本源码Nginx 未配置 PHP 解析规则检查是否配置了location ~ \.php$那段规则访问 PHP 报 502 Bad GatewayPHP-FPM 没启动或 fastcgi_pass 的 socket 路径与 FPM 监听不一致systemctl status php-fpmss -lnp | grep php-fpmPHP 页面报 404 “File not found”fastcgi_param SCRIPT_FILENAME路径不对确认$document_root与磁盘真实路径一致静态页面可以访问PHP 页面 403目录没有 php-fpm 进程的运行权限检查目录权限chmod -R 755并确认属主访问任何 .php 都返回 500PHP-FPM 配置内部错误tail -f /var/log/php-fpm/error.log看真实报错图片能访问CSS/JS 变成乱码Nginx 把 JS 文件当成 PHP 解析了静态正则的优先级要高于 PHP 正则用^~处理修改了配置不生效没有 reload改完配置必须nginx -t systemctl reload nginx重启后 Nginx 起不来80 端口被占用ss -lnp | grep :80找到占用进程并处理5.2 日志定位不要瞎猜先看日志很多人遇到部署问题第一反应是到处搜答案但我的经验是先看日志。LNMP 里最关键的日志有三处Nginx 错误日志/var/log/nginx/error.logNginx 访问日志/var/log/nginx/access.logPHP-FPM 错误日志/var/log/php-fpm/error.log有一次我一个站点页面全是空白浏览器 F12 看到请求返回 500。我第一反应是想改 PHP 代码但后来养成先看日志的习惯后执行了这样一组命令tail -f /var/log/nginx/error.log tail -f /var/log/php-fpm/error.log然后刷新页面几秒钟内日志就给出了答案PHP Fatal error: Call to undefined function mysqli_connect()。原因是我装 PHP 时没装php-mysql扩展数据库函数不可用。装完扩展重启 PHP-FPM 立刻就好了。如果那时瞎改代码两个小时未必能定位到问题。5.3 我沉淀下来的几条配置习惯写了这么多配置最后把自己常用的一些小习惯整理出来这些都是在实际部署中踩过坑之后慢慢养成的。第一每个站点独立配置文件。在/etc/nginx/conf.d/下按域名命名比如blog.conf、shop.conf。千万不要所有站点塞进一个nginx.conf否则改一个站点要小心翼翼怕影响其他站点。第二改配置前先备份。我的习惯是cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak别看这一步简单它能让你在改坏之后 10 秒恢复现场。第三写完 Nginx 配置先语法检查再重载。nginx -t是保护你的最后一道防线执行通过后再systemctl reload nginx不给 502/404 留下机会。第四目录权限慎重处理。Nginx 静态文件用nginx用户读PHP-FPM 默认用www用户执行。两个用户权限不统一经常导致“静态能访问PHP 页面打不开”或者反过来的情况。我的处理方式是让 PHP-FPM 的运行用户改成nginx这样整个站点目录统一用nginx用户管理和读取权限冲突天然减少。第五关于 PHP-FPM 的 socket 模式生产环境我优先用unix socket而不是 TCP 端口。原因很简单socket 不走网络协议栈少了 TCP 握手开销和端口暴露风险性能稍好也更安全。前提是让 Nginx 和 PHP-FPM 运行在同一个用户下否则会出现permission denied访问不了 socket 文件的情况。这些习惯都不玄乎但每个背后都有真实踩坑的代价。写作这篇文章时我又重新梳理了一遍配置流程把动静分离部分做成了可直接复用的模板放在博客上方便以后查用。Nginx 这个工具越用越觉得精髓不在命令里而在配置背后的业务理解和请求链路的清晰认知上。后续如果再写这个系列我打算展开讲讲 Nginx 的负载均衡配置和 HTTPS 证书自动续期这两个方向在实际工作中的出场率也相当高。
返回列表