标签

Nginx

Nginx 是一个网络和代理服务器。Nginx (发音为 "engine-x") 是一个高性能的 HTTP 和反向代理服务器,同时也是一个 IMAP/POP3 代理服务器。Nginx 是由 Igor Sysoev 开发的,最初发布于2004年,旨在解决 C10k 问题,即同时处理大量客户端连接的需求。由于其高性能、稳定性、丰富的功能集以及低资源消耗,Nginx 在全球范围内广泛用于提供网页内容,特别是在高流量的网站中非常流行。

Nginx
查看更多相关内容
服务端5月30日 15:53
什么是 Nginx?它为什么适合高并发?Nginx 是一个高性能 Web 服务器,也常用作反向代理、负载均衡器、静态资源服务器和 API 网关。它适合高并发,核心原因是事件驱动和异步非阻塞 I/O:少量 worker 进程通过事件循环处理大量连接,不需要给每个连接都分配一个线程。简单说,Apache 更像“一个请求安排一个人盯着”,Nginx 更像“少量工作人员按事件通知处理”,长连接和静态资源场景下资源消耗更低。 ## 追问 ### Nginx 主要解决什么问题? 它常解决三件事:对外提供 HTTP 服务,把请求转发给后端,以及把流量分摊到多台服务器。实际项目里还会让它处理 HTTPS 终止、静态资源缓存、限流、压缩、访问日志和灰度路由。 ### 为什么说 Nginx 适合高并发? 它不为每个连接创建独立线程,而是通过 worker 进程和事件循环处理连接状态变化。大量连接处于等待网络 I/O 时,Nginx 可以把 CPU 留给真正有事件的连接,所以并发连接数上来后仍然比较稳。 ### Nginx 和 Apache 有什么区别? Nginx 更擅长高并发连接、反向代理和静态资源;Apache 模块生态成熟,动态内容处理能力强。现代架构里常见做法是 Nginx 放入口层,业务逻辑交给 Node.js、Java、Go、PHP-FPM 等后端服务。 ### 反向代理和负载均衡分别是什么? 反向代理是客户端只访问 Nginx,由 Nginx 转发到真实后端;负载均衡是在多个后端实例之间分配请求。两者经常一起用:Nginx 既隐藏后端地址,也按权重、轮询、IP Hash 等策略分流。 ### 实际项目里怎么用? 常见链路是“用户 → CDN/云负载均衡 → Nginx → 应用服务”。Nginx 负责入口层的 TLS、路由、缓存、限流和日志;后端只关注业务处理。排查问题时也通常先看 Nginx access.log、error.log 和 upstream 响应时间。 ## 写段配置 ```nginx upstream app { server 127.0.0.1:3000; server 127.0.0.1:3001; } server { listen 80; server_name example.com; location /static/ { root /var/www; expires 7d; } location /api/ { proxy_pass http://app; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ```
服务端5月28日 08:25
Nginx 如何优化静态资源?有哪些优化策略?## Nginx 如何优化静态资源?有哪些优化策略? Nginx 处理静态资源的能力是面试高频考点。优化的核心思路是:减少磁盘 I/O、压缩传输体积、利用缓存避免重复请求、将负载推到边缘节点。下面从内核层到架构层逐级展开。 ### sendfile 与零拷贝:从内核直接发送 传统文件读取流程:磁盘 → 内核缓冲区 → 用户空间 → Socket 缓冲区 → 网卡,经历两次用户态拷贝。`sendfile` 让数据直接在内核态完成传输,省掉这两次拷贝,这是 Nginx 静态服务高性能的底层基础。 ```nginx http { sendfile on; # 启用零拷贝,数据在内核态直接从文件描述符传输到 socket tcp_nopush on; # 在包头积累到最大后才发送,减少网络帧数 tcp_nodelay on; # 禁用 Nagle 算法,小包立即发送 } ``` **面试追问:tcp_nopush 和 tcp_nodelay 看起来矛盾,为什么要同时开启?** tcp_nopush 在 sendfile 阶段生效:把 HTTP 响应头和文件内容拼成一个大数据块再发送,减少碎片帧;当最后一块数据不足 MSS 时,tcp_nodelay 接管,确保尾部数据不延迟发出。两者作用阶段不同,互不冲突。这是 Nginx 面试的经典追问。 ### Gzip 压缩:减小传输体积 文本类资源(HTML/CSS/JS/JSON/SVG)压缩收益显著,通常可减少 60%-80% 体积。但图片和视频本身已是压缩格式,再做 Gzip 反而浪费 CPU 且体积几乎不变。 ```nginx http { gzip on; gzip_vary on; # 添加 Vary: Accept-Encoding,帮助 CDN 区分压缩/非压缩版本 gzip_min_length 1024; # 小于 1KB 不压缩,压缩收益抵不过开销 gzip_comp_level 6; # 压缩级别 1-9,6 是速度与压缩率的最佳平衡点 gzip_types text/plain text/css text/javascript application/json application/javascript application/xml image/svg+xml; gzip_static on; # 优先发送预压缩的 .gz 文件,避免实时压缩消耗 CPU } ``` **gzip_static 的意义:** 生产环境建议在构建阶段预生成 `.gz` 文件,Nginx 直接发送预压缩文件,零 CPU 开销。`gzip on` 作为兜底,当 `.gz` 文件不存在时实时压缩。 ### 浏览器缓存:避免重复请求 缓存策略的核心是根据资源更新频率设置不同的过期时间。带哈希的静态资源(如 `app.a3b2c1.js`)可以长期缓存并标记 `immutable`,HTML 入口文件则需要短缓存或必须重新验证。 ```nginx server { # 带哈希的静态资源:长期缓存 + immutable location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; # 静态资源不写日志,减少磁盘 I/O } # HTML 文件:短期缓存,必须重新验证 location ~* \.html$ { expires 1h; add_header Cache-Control "public, must-revalidate"; } } ``` **面试追问:immutable 是什么意思?** 告诉浏览器,资源在过期前不会变化,不需要发条件请求(If-Modified-Since / If-None-Match)验证。配合文件名哈希使用,用户刷新页面也不会产生 304 请求,直接从本地缓存读取。这在 HTTP RFC 8246 中被标准化。 ### 文件描述符缓存:减少系统调用 Nginx 对频繁访问的文件可以缓存文件描述符(fd),避免每次请求都执行 `open()` / `stat()` 系统调用。在高并发场景下,这个优化效果显著。 ```nginx http { open_file_cache max=10000 inactive=20s; open_file_cache_valid 30s; # 每 30s 验证缓存项是否仍有效(检查文件是否被修改) open_file_cache_min_uses 2; # 20s 内访问少于 2 次则移除,避免冷文件占用缓存 open_file_cache_errors on; # 缓存文件不存在等错误,防止同一 404 反复穿透磁盘 } ``` 内存开销:每个缓存项约占 256 字节,10000 项约 2.5MB,对现代服务器可忽略。但 `open_file_cache_errors on` 要注意,如果频繁请求不存在的文件,错误缓存会占用大量条目。 ### 动静分离与 CDN 将静态资源部署到独立域名或 CDN 有三个核心收益: 1. **突破浏览器同域并发限制**——浏览器对同一域名通常限制 6 个并发连接,独立域名可以并行下载更多资源 2. **减少主站 Cookie 污染**——静态资源请求不携带主站 Cookie,减少请求体积 3. **边缘节点就近分发**——CDN 将资源缓存到离用户最近的节点,大幅降低延迟 ```nginx server { listen 80; server_name example.com; # 方案一:静态资源独立目录 location /static/ { root /var/www/static; expires 1y; add_header Cache-Control "public, immutable"; access_log off; } # 方案二:直接重写到 CDN location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2)$ { return 301 https://cdn.example.com$request_uri; } } ``` ### HTTP/2 与资源预加载 HTTP/2 多路复用已大幅降低多请求的开销,但资源预加载仍然有效。需要注意:Chrome 106+ 已移除 HTTP/2 Server Push 支持,推荐使用 `<link rel="preload">` 替代。 ```nginx server { listen 443 ssl http2; # 使用 Link header 预加载关键资源 location = / { add_header Link "</css/style.css>; rel=preload; as=style, </js/app.js>; rel=preload; as=script"; } } ``` **preload vs prefetch:** `preload` 告诉浏览器当前页面一定会用到该资源,立即下载;`prefetch` 表示下一页可能用到,空闲时下载。面试中常考两者区别。 ### 图片与字体优化 ```nginx server { # WebP 自动转换:浏览器支持 WebP 时优先返回 location ~* \.(jpg|png)$ { try_files $uri$webp_suffix $uri =404; expires 1y; add_header Cache-Control "public, immutable"; add_header Vary Accept; # 告诉 CDN 根据 Accept 头缓存不同版本 } # 字体文件:CORS 支持 + 长缓存 location ~* \.(woff2?|ttf|otf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; add_header Access-Control-Allow-Origin "*"; access_log off; } # 图片防盗链 location ~* \.(jpg|jpeg|png|gif)$ { valid_referers none blocked example.com *.example.com; if ($invalid_referer) { return 403; } } } ``` WebP 相比 JPEG/PNG 通常可减少 25%-35% 体积,配合 CDN 的 `Vary: Accept` 头可以同时缓存原始格式和 WebP 格式。 ### 静态资源合并 CSS/JS 合并减少请求数,但在 HTTP/2 场景下收益降低。是否合并需根据实际场景权衡: - HTTP/1.1 环境:合并有效,减少连接开销 - HTTP/2 环境:多路复用已解决队头阻塞,合并收益有限,反而影响缓存命中率 ```nginx # 使用 ngx_http_concat_module(淘宝开源模块) # 访问 /static/css/??a.css,b.css,c.css 可合并返回 location /static/css/ { concat on; concat_types text/css; concat_max_files 10; } ``` ### 安全与日志优化 ```nginx server { # 禁止访问隐藏文件(.git、.env 等敏感文件) location ~ /\. { deny all; access_log off; log_not_found off; } # 静态资源关闭 access_log,减少磁盘写入 location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?|ttf|eot)$ { access_log off; } } ``` 隐藏文件泄露是常见的安全漏洞。`.git` 目录暴露可能导致源码泄露,`.env` 暴露可能泄露数据库密码和 API Key。 ### 生产环境完整配置参考 ```nginx user nginx; worker_processes auto; # 自动匹配 CPU 核心数 worker_rlimit_nofile 65535; # 提升进程级文件描述符上限 events { worker_connections 10240; # 每个 worker 的最大连接数 use epoll; # Linux 下使用 epoll 事件模型 multi_accept on; # 一次性接受所有新连接 } http { # 内核层优化 sendfile on; tcp_nopush on; tcp_nodelay on; # 传输层优化 gzip on; gzip_vary on; gzip_min_length 1024; gzip_comp_level 6; gzip_types text/plain text/css text/javascript application/json application/javascript application/xml image/svg+xml; gzip_static on; # 文件系统缓存 open_file_cache max=10000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on; # 按内容类型动态设置缓存时间 map $sent_http_content_type $expires { default off; text/html 1h; text/css 1y; application/javascript 1y; ~image/ 1y; ~font/ 1y; } server { listen 80; server_name example.com; root /var/www/html; # 静态资源长缓存 location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?|ttf|eot)$ { expires $expires; add_header Cache-Control "public, immutable"; access_log off; } # 字体 CORS location ~* \.(woff2?|ttf|otf|eot)$ { add_header Access-Control-Allow-Origin "*"; } # 禁止隐藏文件 location ~ /\. { deny all; } location / { try_files $uri $uri/ =404; } } } ``` ### 优化策略速查表 | 层级 | 策略 | 核心指令 | 收益 | |------|------|----------|------| | 内核层 | 零拷贝传输 | sendfile + tcp_nopush + tcp_nodelay | 减少 CPU 拷贝和网络碎片帧 | | 传输层 | Gzip 压缩 | gzip + gzip_static | 文本资源体积减少 60%-80% | | 缓存层 | 浏览器缓存 | expires + Cache-Control immutable | 避免重复请求,零带宽消耗 | | 缓存层 | 文件描述符缓存 | open_file_cache | 减少 open/stat 系统调用 | | 架构层 | 动静分离 + CDN | 独立域名 + CDN 回源 | 突破并发限制 + 就近分发 | | 协议层 | HTTP/2 + Preload | http2 + Link header | 多路复用 + 关键资源预加载 | | 格式层 | WebP + WOFF2 | try_files + Vary Accept | 图片体积再减 25%-35% | | 安全层 | 防盗链 + 隐藏文件 | valid_referers + deny | 防止资源盗用和源码泄露 | 面试中回答这类问题,建议按"内核优化 → 传输优化 → 缓存策略 → 架构设计"的层次递进,体现系统思维。每个策略说清原理、配置和收益,比单纯罗列配置更有说服力。如果面试官追问某一项的细节,可以深入到内核原理层面(如 sendfile 的 DMA 实现、epoll 的 LT/ET 模式),展示技术深度。
服务端5月28日 08:23
Nginx 如何处理动态内容?有哪些配置方式?## Nginx 如何处理动态内容?有哪些配置方式? Nginx 本身不执行动态脚本,它的角色是**请求分发器**——根据协议类型将动态请求转给后端应用服务器处理,自己只负责连接管理、缓冲、压缩和缓存等"外围工作"。常见的转发协议有四种:FastCGI、uWSGI、SCGI 和 HTTP 反向代理。 ## 核心答案:四种协议的区别与选型 | 协议 | 典型后端 | 通信方式 | 适用场景 | |------|---------|---------|---------| | FastCGI | PHP-FPM | Unix socket / TCP | PHP 项目,最常见 | | uWSGI | Python (Django/Flask) | Unix socket / TCP | Python Web 应用 | | SCGI | Ruby 等 | Unix socket / TCP | 较少使用 | | HTTP Proxy | Node.js / Go / Java | HTTP/TCP | 通用,任何 HTTP 服务 | **选型原则**:后端用什么语言/框架,就用对应协议。如果后端本身就是 HTTP 服务(如 Node.js),直接用 `proxy_pass` 做反向代理即可。 ## FastCGI 配置(PHP 项目最常用) FastCGI 是 Nginx 处理 PHP 的标准方式。关键指令是 `fastcgi_pass`,它告诉 Nginx 把请求转发到哪里。 ```nginx location ~ \.php$ { try_files $uri =404; fastcgi_pass unix:/var/run/php/php8.0-fpm.sock; fastcgi_index index.php; # 这个参数必须设置,否则 PHP 找不到要执行的脚本 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; # 超时:后端响应慢时防止 Nginx 一直等 fastcgi_connect_timeout 60s; fastcgi_send_timeout 60s; fastcgi_read_timeout 60s; # 缓冲区:后端返回内容先缓冲再发给客户端 fastcgi_buffer_size 4k; fastcgi_buffers 8 4k; } ``` **几个容易踩的坑**: - `SCRIPT_FILENAME` 必须拼成绝对路径,否则 PHP-FPM 报 404 - Unix socket 比 TCP 快(省掉网络栈开销),但只能本机通信 - `try_files $uri =404` 防止 PHP 执行上传目录里的恶意脚本 ## uWSGI 配置(Python 项目) Django、Flask 等 Python 框架常用 uWSGI 部署,Nginx 通过 `uwsgi_pass` 转发请求。 ```nginx upstream django_backend { server unix:/var/run/uwsgi/app.sock; server 127.0.0.1:8000; } server { listen 80; server_name example.com; location / { uwsgi_pass django_backend; include uwsgi_params; uwsgi_connect_timeout 60s; uwsgi_read_timeout 60s; } # 静态文件直接由 Nginx 处理,不走 uWSGI location /static/ { alias /var/www/html/static/; expires 1y; } } ``` **核心区别**:uWSGI 用自己的二进制协议,比 HTTP 更紧凑高效,但只有 Nginx 等 Web 服务器能直接对接。 ## HTTP 反向代理(Node.js / Go / Java) 如果后端本身就是一个 HTTP 服务,直接用 `proxy_pass` 转发,这也是微服务架构中最常见的方式。 ```nginx upstream nodejs_backend { server 127.0.0.1:3000; server 127.0.0.1:3001; } server { listen 80; server_name example.com; location / { proxy_pass http://nodejs_backend; # 透传客户端真实信息 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # WebSocket 支持 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } ``` **为什么需要这些 header**:后端拿到的 remote_addr 是 Nginx 的 IP 而非客户端 IP,必须通过 `X-Real-IP` 和 `X-Forwarded-For` 透传。WebSocket 升级也需要额外的 `Upgrade` 头。 ## FastCGI 缓存:让动态内容也变快 不是所有动态请求都要实时回源。比如文章详情页、列表页这类"准静态"内容,可以用 FastCGI 缓存大幅降低后端压力。 ```nginx # 在 http 块中定义缓存区 fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=fastcgi_cache:10m max_size=1g inactive=60m; server { location ~ \.php$ { fastcgi_cache fastcgi_cache; fastcgi_cache_valid 200 60m; # 200 响应缓存 60 分钟 fastcgi_cache_valid 404 1m; # 404 只缓存 1 分钟 fastcgi_cache_key "$scheme$request_method$host$request_uri"; # 登录用户等场景需要跳过缓存 set $skip_cache 0; if ($http_cookie ~* "comment_author|wordpress_logged_in") { set $skip_cache 1; } fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; fastcgi_pass unix:/var/run/php/php8.0-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; add_header X-Cache-Status $upstream_cache_status; } } ``` **缓存清理**:`inactive=60m` 表示 60 分钟无访问自动清除。如果需要主动清除,可用 `proxy_cache_purge` 模块或直接删除缓存目录下的文件。 ## 动态内容负载均衡 后端多实例部署时,Nginx 的 upstream 模块提供多种分发策略: ```nginx upstream php_backend { least_conn; # 最少连接数优先 server 192.168.1.100:9000 weight=3; # 权重 3 server 192.168.1.101:9000 weight=2; # 权重 2 server 192.168.1.102:9000; # 默认权重 1 keepalive 32; # 保持长连接,减少握手开销 } ``` **策略选择**: - `round-robin`(默认):轮询,适合后端性能一致的场景 - `least_conn`:最少连接优先,适合请求处理时间差异大的场景 - `ip_hash`:同一 IP 固定到同一后端,适合需要会话保持的场景 ## Gzip 压缩:减少传输体积 动态内容通常是 JSON/HTML,压缩率很高,开启 Gzip 可以减少 60%-80% 的传输量。 ```nginx gzip on; gzip_vary on; gzip_min_length 1024; # 小于 1KB 不压缩,压缩反而更大 gzip_comp_level 6; # 6 是性能和压缩率的平衡点 gzip_types text/plain text/css application/json application/javascript application/xml; ``` ## 安全配置要点 动态内容处理中,安全问题主要集中在两个方向:防止恶意脚本执行和限制请求大小。 ```nginx # 禁止访问敏感文件 location ~* \.(htaccess|htpasswd|ini|log|sh|sql|bak|swp)$ { deny all; } # 防止上传目录中的 PHP 被执行 location ^~ /uploads/ { location ~ \.php$ { deny all; } } # 限制请求体大小 client_max_body_size 10m; ``` **常见攻击面**:上传一个 `.php` 文件到图片目录,然后直接访问执行。用 `^~` 前缀匹配 + 内部 deny 可以彻底堵住这个口。 ## 追问:Nginx 处理动态内容的请求流程是什么? 客户端请求到达 Nginx 后,大致经历以下步骤: 1. Nginx 接收请求,根据 `location` 规则匹配到对应的转发配置 2. 按协议(FastCGI/uWSGI/HTTP)将请求转发给后端应用服务器 3. 后端处理完成,将响应返回给 Nginx 4. Nginx 对响应做缓冲、压缩、缓存等处理 5. 最终将响应返回给客户端 **关键点**:Nginx 在整个过程中只做"搬运工",不解析脚本内容。缓冲机制确保即使客户端读得慢,后端也能尽快释放连接;缓存机制则让重复请求直接从 Nginx 返回,后端完全不用参与。 ## 追问:502 Bad Gateway 和 504 Gateway Timeout 有什么区别? - **502**:Nginx 成功连接到后端,但后端返回了无效响应(进程崩溃、端口未监听、协议不匹配等) - **504**:Nginx 等待后端响应超时(后端处理太慢,超过了 `fastcgi_read_timeout` 或 `proxy_read_timeout`) 排查思路:先看后端进程是否存活,再看 Nginx error log 中的具体错误信息,最后根据超时或连接失败调整对应参数。
服务端5月28日 07:28
Nginx 如何配置虚拟主机?有哪些配置方式?## Nginx 如何配置虚拟主机?有哪些配置方式? 虚拟主机(Virtual Host)是 Nginx 最核心的能力之一——一台服务器、一个 Nginx 进程,就能同时服务几十个甚至上百个网站。面试中这道题考察的不只是"怎么配",更是你对其背后路由机制的理解深度。 Nginx 虚拟主机的本质就是 `server` 块。每个 `server` 块是一个独立的虚拟主机,Nginx 根据请求的域名、端口或 IP 地址,将请求路由到匹配的 `server` 块处理。三个关键指令决定了路由规则: - `listen`:监听的地址和端口,如 `listen 80` 或 `listen 443 ssl` - `server_name`:匹配请求头中的 `Host` 字段,支持精确匹配、通配符和正则 - `root`:该虚拟主机的网站根目录 ### 基于域名的虚拟主机 **这是生产环境最主流的方式。** 多个域名共享同一个 IP,Nginx 根据 HTTP 请求头中的 `Host` 字段决定将请求交给哪个 `server` 块处理。这也叫 Name-Based Virtual Host: ```nginx server { listen 80; server_name example.com www.example.com; root /var/www/example.com; index index.html; access_log /var/log/nginx/example.com.access.log; error_log /var/log/nginx/example.com.error.log; location / { try_files $uri $uri/ =404; } } server { listen 80; server_name test.com www.test.com; root /var/www/test.com; index index.html; access_log /var/log/nginx/test.com.access.log; error_log /var/log/nginx/test.com.error.log; location / { try_files $uri $uri/ =404; } } ``` 两个 `server` 块都监听 80 端口,Nginx 收到请求后先匹配 `server_name`,命中哪个就走哪个配置。`server_name` 用空格分隔可以写多个域名,`www.example.com` 和 `example.com` 都会命中第一个块。 **面试追问:如果两个 server 块的 server_name 都匹配同一个域名会怎样?** Nginx 有明确的匹配优先级:精确匹配 > 最长通配符前缀 > 最长通配符后缀 > 第一个正则匹配 > default_server。优先级相同则按配置文件加载顺序,先加载的优先。 ### 基于端口的虚拟主机 通过不同端口区分服务,适合将管理后台、API 服务与主站做物理隔离: ```nginx # 主站 server { listen 80; server_name example.com; root /var/www/example.com; index index.html; location / { try_files $uri $uri/ =404; } } # 管理后台 server { listen 8080; server_name example.com; root /var/www/example.com/admin; index index.html; location / { try_files $uri $uri/ =404; } } # HTTPS 安全服务 server { listen 8443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; root /var/www/example.com/secure; index index.html; location / { try_files $uri $uri/ =404; } } ``` 端口方式的缺点是用户需要显式指定端口号(如 `example.com:8080`),80 和 443 之外的端口对用户不友好,因此一般只用于内部服务或管理入口。 ### 基于 IP 地址的虚拟主机 服务器绑定多个 IP 时,按 IP 地址区分站点。这种方式在早期互联网常用,但在现代云环境下已很少使用——公网 IP 资源有限且费用高,基于域名的方式更经济: ```nginx server { listen 192.168.1.100:80; server_name example.com; root /var/www/example.com; index index.html; location / { try_files $uri $uri/ =404; } } server { listen 192.168.1.101:80; server_name example.com; root /var/www/example.com/mirror; index index.html; location / { try_files $uri $uri/ =404; } } ``` ### 通配符与正则域名匹配 子域名多且动态变化时,逐一配置 `server_name` 不现实。Nginx 提供通配符和正则两种高级匹配: **通配符匹配**——用 `*` 匹配子域名: ```nginx server { listen 80; server_name *.example.com; location / { set $subdomain $host; if ($subdomain ~* ^(.*)\.example\.com$) { set $subdomain $1; } root /var/www/subdomains/$subdomain; index index.html; } } # 默认虚拟主机:兜底处理未匹配的请求 server { listen 80 default_server; server_name _; root /var/www/default; return 444; # 直接关闭连接,比 404 更安全 } ``` `default_server` 是安全防线——任何未匹配到 `server_name` 的请求都会走这个块。建议返回 444(Nginx 特有状态码,直接关闭连接),比返回 404 更能防止信息泄露。 **正则表达式匹配**——用命名捕获组提取子域名: ```nginx server { listen 80; server_name ~^(?<subdomain>.+)\.example\.com$; root /var/www/example.com/$subdomain; index index.html; location / { try_files $uri $uri/ =404; } } server { listen 80; server_name ~^(?<user>.+)\.users\.example\.com$; root /var/www/users/$user; index index.html; location / { try_files $uri $uri/ =404; } } ``` `?<name>` 是命名捕获组,提取的值以 `$name` 变量在配置中引用。通配符只能做简单的 `*` 匹配,正则则能处理复杂的域名模式——实际项目中正则用得更多。 ### HTTPS 虚拟主机 生产环境必须启用 HTTPS,标准做法是 HTTP 301 永久跳转到 HTTPS: ```nginx # HTTP -> HTTPS 301 跳转 server { listen 80; server_name example.com; return 301 https://$server_name$request_uri; } # HTTPS 服务 server { listen 443 ssl http2; server_name example.com; ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; root /var/www/example.com; index index.html; location / { try_files $uri $uri/ =404; } } ``` `http2` 参数直接在 `listen` 指令中启用 HTTP/2,无需额外编译模块。`ssl_session_cache` 将 SSL 会话缓存 10 分钟,客户端重连时可以复用,避免完整的 TLS 握手,显著提升 HTTPS 性能。SSL 协议只保留 TLSv1.2 和 TLSv1.3,禁用所有不安全的旧版本。 ### 反向代理虚拟主机 虚拟主机在生产中最常见的用途是反向代理——不同域名转发到不同的后端服务,Nginx 作为网关统一入口: ```nginx upstream backend1 { server 192.168.1.100:8080; server 192.168.1.101:8080; } upstream backend2 { server 192.168.1.200:8080; server 192.168.1.201:8080; } server { listen 80; server_name api.example.com; location / { proxy_pass http://backend1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } server { listen 80; server_name admin.example.com; location / { proxy_pass http://backend2; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ``` `proxy_set_header` 三件套必须配置:`Host` 让后端知道原始域名,`X-Real-IP` 传递客户端真实 IP,`X-Forwarded-For` 追加代理链路。不加这些头,后端拿到的全是 Nginx 的内网 IP,日志和鉴权都会出问题。 ### 多域名共享配置 多个域名配置相似、仅 `root` 路径不同时,用 `map` 指令消除重复: ```nginx map $host $root_path { example.com /var/www/example.com; test.com /var/www/test.com; default /var/www/default; } server { listen 80; server_name example.com test.com; root $root_path; index index.html; location / { try_files $uri $uri/ =404; } } ``` `map` 在请求处理阶段根据 `$host` 变量的值映射到对应目录,一个 `server` 块就能服务多个域名。当域名数量超过 5 个且配置差异仅在路径时,这种方式比逐个写 `server` 块更易维护。 ### 配置文件分离与站点管理 生产环境中,千万不要把所有虚拟主机堆在 `nginx.conf` 一个文件里。用 `include` 拆分: ```nginx # /etc/nginx/nginx.conf http { include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; } ``` 每个虚拟主机一个配置文件,放在 `sites-available` 目录,通过符号链接启用: ```bash # 启用站点 ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/ # 禁用站点 rm /etc/nginx/sites-enabled/example.com # 测试配置语法(修改后必做) nginx -t # 重新加载配置(不影响正在处理的请求) nginx -s reload ``` `nginx -t` 是生产操作的铁律——修改配置后先检测语法,通过后再 `reload`。`reload` 是平滑重载,Nginx 会等旧请求处理完再切换到新配置,不会中断服务。而 `restart` 会直接杀掉工作进程,正在处理的请求会断开。 ### PHP 应用虚拟主机 WordPress、Laravel 等 PHP 应用需要配置 FastCGI 传递给 PHP-FPM: ```nginx server { listen 80; server_name example.com; root /var/www/example.com; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.0-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; } } ``` `try_files` 中的 `/index.php?$query_string` 是 PHP 框架的标配写法——先尝试找静态文件,找不到就转发给 `index.php` 处理,这是 Laravel 等框架 URL 重写的基础。`SCRIPT_FILENAME` 必须用 `$document_root` 拼接,否则 PHP-FPM 找不到脚本文件。 ### 静态站点虚拟主机 纯静态站点可以做更激进的优化——关日志、开压缩、长缓存: ```nginx server { listen 80; server_name static.example.com; root /var/www/static; index index.html; gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/json application/javascript; location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; } location / { try_files $uri $uri/ =404; } } ``` 静态资源关闭 `access_log` 能显著减少磁盘 IO。高流量站点中,日志写入是主要瓶颈之一,对不需要统计的静态资源关闭日志是常规优化手段。 ### 生产环境完整配置模板 综合安全、性能和可维护性的完整配置,可作为新项目的起点: ```nginx server { listen 80; server_name example.com www.example.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name example.com www.example.com; # SSL ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # 站点根目录 root /var/www/example.com; index index.php index.html; # 日志 access_log /var/log/nginx/example.com.access.log; error_log /var/log/nginx/example.com.error.log; # 安全头 add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; # Gzip 压缩 gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/json application/javascript; # 静态资源长缓存 location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; } # PHP-FPM location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.0-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } # 主路由 location / { try_files $uri $uri/ /index.php?$query_string; } # 禁止访问隐藏文件(.git、.env 等) location ~ /\. { deny all; access_log off; log_not_found off; } } ``` ### 配置要点总结 **三种类型的选用原则**:基于域名是首选方案,一个 IP 托管所有站点,配置简单且用户无感知;基于端口适合内部服务隔离,但需要用户感知端口;基于 IP 在公网环境下已基本淘汰。 **`server_name` 匹配优先级**(面试必考):精确匹配 > 通配符前缀(`*.example.com`)> 通配符后缀(`example.*`)> 正则表达式 > `default_server`。优先级相同的,按配置文件加载顺序决定。 **配置排错四步法**:`nginx -t` 检查语法 -> `nginx -s reload` 确认重载 -> 检查 `server_name` 是否匹配请求域名 -> 查看 `error.log` 中的具体报错。大部分"配置不生效"的问题,要么是忘记 `reload`,要么是 `server_name` 写错了。
服务端5月28日 07:26
Nginx 的 location 指令如何匹配?优先级是什么?## Nginx 的 location 指令如何匹配?优先级是什么? location 是 Nginx 中最核心的指令之一,它决定了一个请求由哪个配置块来处理。理解它的匹配规则和优先级,不仅是面试高频考点,更是排查 Nginx 配置问题的基本功。 ### location 的四种匹配方式 location 指令的语法:`location [=|~|~*|^~] uri { ... }` 修饰符不同,匹配行为完全不同: | 修饰符 | 匹配方式 | 匹配后是否继续搜索 | |--------|---------|-------------------| | `=` | 精确匹配 | 否,立即停止 | | `^~` | 前缀匹配 | 否,跳过正则检查 | | `~` | 正则匹配(区分大小写) | 否,按配置顺序首个命中即停止 | | `~*` | 正则匹配(不区分大小写) | 否,按配置顺序首个命中即停止 | | 无 | 前缀匹配 | 是,继续检查正则 | #### 精确匹配(=) 只有请求 URI 与指定路径完全一致时才命中,一旦匹配立即停止搜索,性能最优: ```nginx location = / { # 仅匹配 /,不匹配 /index.html } ``` #### 前缀匹配(无修饰符) 按 URI 前缀匹配,匹配成功后不会立即使用,而是先记住这个最长前缀匹配,继续检查正则表达式。如果正则没有命中,才会回退使用这个前缀匹配: ```nginx location /docs/ { # 匹配以 /docs/ 开头的所有 URI # 但如果有正则也命中了,正则优先 } ``` #### 正则匹配(~ 和 ~*) - `~`:区分大小写 - `~*`:不区分大小写 正则匹配按配置文件中出现的顺序依次检查,首个命中即停止: ```nginx location ~ \.php$ { # 区分大小写,匹配 .php 结尾的请求 } location ~* \.(jpg|png|gif|css|js)$ { # 不区分大小写,匹配常见静态资源 } ``` 此外还有 `!~` 和 `!~*`,表示正则不匹配,但它们不能用于 location 指令,只能用在 if 条件判断中。 #### 前缀匹配(^~) 行为与无修饰符的前缀匹配类似,但关键区别是:如果 `^~` 前缀匹配成功,会跳过后续所有正则检查,直接使用该 location。这在对性能敏感的场景下非常有用: ```nginx location ^~ /static/ { # 匹配 /static/ 开头的请求 # 即使有正则也匹配,也不检查,直接用这个 } ``` ### Nginx 完整匹配算法 面试中光背优先级顺序不够,必须理解 Nginx 的完整匹配流程: 1. Nginx 首先检查所有前缀匹配(包括 `=`、`^~` 和无修饰符),找到最长前缀匹配 2. 如果最长前缀是精确匹配(`=`),立即使用,匹配结束 3. 如果最长前缀是 `^~` 匹配,立即使用,匹配结束 4. 按配置文件顺序依次检查所有正则表达式(`~` 和 `~*`) 5. 如果正则命中,使用该正则 location,匹配结束 6. 如果所有正则都未命中,回退使用步骤 1 中找到的最长前缀匹配 这个流程说明一个关键点:**正则匹配的优先级高于普通前缀匹配,但低于 `=` 和 `^~`**。 ### 优先级总结(从高到低) 1. **精确匹配 `=`** — 最高优先级,匹配即停 2. **前缀匹配 `^~`** — 匹配后跳过正则检查 3. **正则匹配 `~` / `~*`** — 按配置顺序,先到先得 4. **普通前缀匹配** — 优先级最低,作为兜底 注意:正则匹配之间没有优先级之分,完全取决于配置文件中的书写顺序,写在前面的先匹配。 ### 配置示例与匹配结果 ```nginx server { listen 80; server_name example.com; location = / { return 200 "1: exact /"; } location / { return 200 "5: prefix /"; } location ^~ /images/ { return 200 "2: ^~ /images/"; } location ~ \.php$ { return 200 "3: regex .php"; } location ~* \.(jpg|png|gif)$ { return 200 "4: regex image"; } location /docs/ { return 200 "6: prefix /docs/"; } } ``` 匹配结果验证: | 请求 URI | 命中 location | 原因 | |----------|--------------|------| | `/` | `= /` | 精确匹配,优先级最高 | | `/images/logo.jpg` | `^~ /images/` | `^~` 命中后跳过正则 | | `/api/test.php` | `~ \.php$` | 前缀匹配 `/` 记住后,正则命中 | | `/photo.JPG` | `~* \.(jpg\|png\|gif)$` | 不区分大小写正则命中 | | `/docs/readme.html` | `/docs/` | 最长前缀 `/docs/` 优先于 `/`,且无正则命中 | | `/about` | `/` | 仅前缀 `/` 命中,无正则匹配 | ### 常见面试陷阱 **陷阱一:前缀匹配的长度优先** 多个普通前缀同时匹配时,Nginx 选择最长前缀,而非配置顺序: ```nginx location /api/ { ... } # 前缀长度 5 location /api/v1/ { ... } # 前缀长度 9,优先 ``` 请求 `/api/v1/users` 会匹配 `/api/v1/`,因为前缀更长。 **陷阱二:正则覆盖普通前缀** ```nginx location /images/ { ... } location ~* \.(jpg|png)$ { ... } ``` 请求 `/images/logo.jpg` 会命中正则 `~*`,而非前缀 `/images/`,因为正则优先级高于普通前缀。如果希望 `/images/` 下的请求不被正则抢走,必须使用 `^~`。 **陷阱三:`=` 只匹配精确路径** ```nginx location = /api { ... } ``` 它只匹配 `/api`,不匹配 `/api/`、`/api/v1`。如果需要同时匹配,应该用前缀匹配。 ### 实际配置建议 1. 高频路径用精确匹配 `=`,性能最优且语义清晰 2. 静态资源目录用 `^~`,避免被正则拦截 3. 正则匹配控制数量,按命中频率从高到低排列 4. 避免在正则中使用复杂回溯表达式,防止 ReDoS 攻击 5. 通用兜底 `location /` 放在最后 ```nginx # 精确匹配首页 location = / { proxy_pass http://frontend; } # 静态资源,跳过正则检查 location ^~ /static/ { alias /var/www/static/; expires 30d; } # PHP 请求转发 location ~ \.php$ { fastcgi_pass unix:/run/php/php-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } # 管理后台,跳过正则检查 location ^~ /admin/ { auth_basic "Restricted"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://admin_backend; } # API 代理 location /api/ { proxy_pass http://api_backend; } # 兜底 location / { try_files $uri $uri/ /index.html; } ``` ### 追问:location 嵌套怎么用? location 支持嵌套,但只有普通前缀匹配可以嵌套在普通前缀内,正则和精确匹配不能嵌套: ```nginx location /api/ { proxy_pass http://backend; location /api/internal/ { # 更具体的路径,覆盖外层配置 deny all; } } ``` 内层 location 会完全覆盖外层的处理逻辑,而不是继承。如果内层也需要 `proxy_pass`,必须显式重新声明。 ### 追问:location 和 rewrite 的执行顺序? Nginx 处理请求时,rewrite 阶段在 location 匹配之前执行(server 级别的 rewrite)。如果 rewrite 修改了 URI,location 会基于修改后的 URI 重新匹配。但 location 内部的 rewrite 可能触发重新匹配,需要注意避免循环重写。
服务端5月28日 07:26
Nginx 如何实现缓存?缓存策略怎么配才能防击穿?## Nginx 如何实现缓存?如何配置缓存策略? Nginx 的缓存能力是后端服务性能优化的关键手段。面试中常从"Nginx 有哪几种缓存""proxy_cache 和 fastcgi_cache 怎么选""如何防止缓存击穿"这几个角度考察,理解原理比背配置更重要。 ## Nginx 缓存的三大层次 Nginx 缓存并不是单一机制,而是分布在请求链路的不同位置: 1. **浏览器缓存**:通过响应头(`Cache-Control`、`Expires`)让客户端自行缓存,Nginx 只负责下发头信息 2. **代理缓存(proxy_cache)**:Nginx 作为反向代理时,缓存后端上游的响应,适用于反向代理场景 3. **FastCGI 缓存(fastcgi_cache)**:缓存 FastCGI 协议上游(如 PHP-FPM)的响应,适用于 PHP 直连场景 面试时先说清楚这三层,再深入其中一层的配置细节,逻辑比直接贴配置更清晰。 ## 代理缓存配置详解 ```nginx http { proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=proxy_cache:10m max_size=1g inactive=60m use_temp_path=off; server { listen 80; server_name example.com; location / { proxy_cache proxy_cache; proxy_cache_valid 200 302 10m; proxy_cache_valid 404 1m; proxy_cache_key "$scheme$request_method$host$request_uri"; proxy_cache_bypass $http_cache_control; add_header X-Cache-Status $upstream_cache_status; proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } } ``` ### 核心参数解读 | 参数 | 作用 | 注意点 | |---|---|---| | `levels=1:2` | 缓存目录分两级,避免单目录文件过多 | 值越大层级越深,1:2 是常用配置 | | `keys_zone=proxy_cache:10m` | 共享内存区域,存储缓存键和元数据 | 10m 约可存 8 万条键,按需调大 | | `max_size=1g` | 缓存磁盘上限 | 超出后 Nginx 自动淘汰最久未访问的缓存 | | `inactive=60m` | 60 分钟无访问则淘汰 | 与 `proxy_cache_valid` 无关,是另一条淘汰链 | | `use_temp_path=off` | 临时文件写入缓存目录而非系统临时目录 | 减少跨磁盘拷贝,生产环境建议开启 | | `proxy_cache_valid 200 302 10m` | 200/302 响应缓存 10 分钟 | 必须显式配置,否则不缓存 | | `proxy_cache_key` | 缓存键的计算方式 | 默认含 scheme + method + host + uri,带参请求需考虑是否加入 `$args` | ## FastCGI 缓存配置 FastCGI 缓存适用于 Nginx 直连 PHP-FPM 的场景,参数与 proxy_cache 对称: ```nginx http { fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=fastcgi_cache:10m max_size=1g inactive=60m; server { location ~ \.php$ { fastcgi_cache fastcgi_cache; fastcgi_cache_valid 200 60m; fastcgi_cache_methods GET HEAD; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; add_header X-Cache-Status $upstream_cache_status; fastcgi_pass unix:/var/run/php/php8.0-fpm.sock; include fastcgi_params; } } } ``` ### proxy_cache 与 fastcgi_cache 怎么选? | 对比维度 | proxy_cache | fastcgi_cache | |---|---|---| | 适用场景 | 反向代理后端(任何 HTTP 服务) | 直连 PHP-FPM 等 FastCGI 进程 | | 协议 | HTTP | FastCGI | | 灵活性 | 更通用,后端不限语言 | 仅限 FastCGI 协议 | | 生产推荐 | 微服务、多语言后端 | 纯 PHP 架构 | 两者不能同时作用于同一 location。如果用了 `proxy_pass` 就用 proxy_cache,用了 `fastcgi_pass` 就用 fastcgi_cache。 ## 缓存风暴与缓存锁定 这是面试高频追问点。当缓存过期瞬间,大量并发请求同时穿透到后端,这就是**缓存风暴(Cache Stampede)**。 ```nginx # 缓存锁定:只放一个请求去后端取数据,其余等待 proxy_cache_lock on; proxy_cache_lock_timeout 5s; # 过期缓存兜底:后端异常时返回旧缓存 proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; # 后台异步更新:命中过期缓存时异步刷新,不阻塞请求 proxy_cache_background_update on; ``` 三者配合的执行逻辑:缓存过期 → `proxy_cache_lock` 放行一个请求 → 其余请求读 `proxy_cache_use_stale` 返回旧数据 → 新数据写入后所有请求命中。这就是完整的防击穿方案。 ## 动态缓存控制 不是所有请求都该缓存。用 `map` 指令按条件跳过缓存: ```nginx # 按 URI 跳过缓存 map $request_uri $skip_cache { default 0; ~*/admin/ 1; ~*/api/ 1; ~*/user/ 1; } # 按后端响应头跳过缓存 map $upstream_http_cache_control $skip_cache_by_header { ~*no-cache 1; ~*private 1; default 0; } # 组合条件 map $skip_cache$skip_cache_by_header $combined_skip { default 0; ~1 1; } ``` 然后在 location 中使用: ```nginx proxy_cache_bypass $combined_skip; proxy_no_cache $combined_skip; ``` `proxy_cache_bypass` 和 `proxy_no_cache` 的区别:`bypass` 是跳过缓存直接请求后端但可能将响应写入缓存;`no_cache` 则完全不写缓存。生产环境通常两者配合使用,确保该跳过的请求既不读缓存也不写缓存。 ## 静态文件与浏览器缓存 静态资源的缓存走另一套逻辑,不经过 proxy_cache,直接由 Nginx 返回文件并设置浏览器缓存头: ```nginx location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; } ``` `immutable` 告诉浏览器:资源不会变,不需要发条件请求验证。搭配文件名加 hash(如 `app.3a7b2c.js`)效果最佳,更新时换文件名即可让浏览器重新请求。 ## 缓存清除方案 Nginx 开源版不支持主动清除缓存,三种替代方案: 1. **自然过期**:通过 `proxy_cache_valid` 设定 TTL,到期自动淘汰 2. **第三方模块 ngx_cache_purge**:支持按 URL 主动清除,需编译安装 3. **删除缓存文件**:根据 `proxy_cache_key` 的 MD5 值定位文件路径并删除,`rm -rf /var/cache/nginx/proxy` 可全量清除 生产环境中,最稳妥的方式是修改 `proxy_cache_key` 加入版本号参数,发布时更新版本号让旧缓存自然失效。 ## 缓存命中率监控 通过 `X-Cache-Status` 响应头可观察缓存命中情况: ```nginx add_header X-Cache-Status $upstream_cache_status; ``` 状态值含义: | 状态 | 含义 | |---|---| | MISS | 未命中,请求穿透到后端 | | BYPASS | 命中跳过条件,不走缓存 | | EXPIRED | 缓存已过期,需重新获取 | | STALE | 后端异常,返回过期缓存 | | UPDATING | 缓存正在更新,返回旧内容 | | HIT | 命中缓存,直接返回 | 监控思路:统计 HIT / (HIT + MISS + EXPIRED) 的比值,低于 80% 就需要调优缓存键或 TTL。 ## 缓存配置的常见踩坑 **1. 后端响应头导致缓存不生效** 后端返回 `Cache-Control: no-cache` 或 `Set-Cookie` 时,Nginx 默认不缓存。需要忽略这些头: ```nginx proxy_ignore_headers X-Accel-Expires Expires Cache-Control Set-Cookie; ``` **2. 缓存键缺少关键参数** 默认 `proxy_cache_key` 不含 `$args`,但 API 请求 `?page=1` 和 `?page=2` 应该返回不同内容,需要加入查询参数: ```nginx proxy_cache_key "$scheme$request_method$host$request_uri$is_args$args"; ``` **3. 缓存最小请求次数导致首次不缓存** `proxy_cache_min_uses 2` 表示请求出现 2 次才缓存,低流量接口可能永远不缓存。生产环境建议设为 1。 **4. keys_zone 过小导致缓存元数据丢失** keys_zone 只存键和元数据,不存响应体。10m 约存 8 万条键,key 较长时需适当增大。 ## 生产环境完整配置参考 ```nginx http { proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=proxy_cache:100m max_size=10g inactive=60m use_temp_path=off; fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=fastcgi_cache:100m max_size=10g inactive=60m; map $request_uri $skip_cache { default 0; ~*/admin/ 1; ~*/api/ 1; ~*/user/ 1; } server { listen 80; server_name example.com; location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; } location / { proxy_cache proxy_cache; proxy_cache_valid 200 302 10m; proxy_cache_valid 404 1m; proxy_cache_key "$scheme$request_method$host$request_uri"; proxy_cache_bypass $skip_cache; proxy_no_cache $skip_cache; proxy_cache_lock on; proxy_cache_lock_timeout 5s; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; proxy_cache_background_update on; add_header X-Cache-Status $upstream_cache_status; proxy_pass http://backend; } location ~ \.php$ { fastcgi_cache fastcgi_cache; fastcgi_cache_valid 200 60m; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; add_header X-Cache-Status $upstream_cache_status; fastcgi_pass unix:/var/run/php/php8.0-fpm.sock; } } } ```
服务端5月28日 07:24
Nginx 如何实现访问控制?有哪些访问控制方法?## Nginx 如何实现访问控制?有哪些访问控制方法? Nginx 的访问控制是后端面试高频考点,核心思路是"在反向代理层拦截非法请求,减轻后端压力"。主要方法有五种:**IP 黑白名单**、**HTTP 基本认证**、**请求方法限制**、**基于请求头的鉴权**、**地理/时间条件控制**,实战中往往组合使用。下面逐一拆解原理和配置要点。 ### 一、IP 黑白名单:最基础的网络层控制 Nginx 通过 `allow` / `deny` 指令按 IP 或 CIDR 段做访问控制,规则从上到下依次匹配,命中即生效: ```nginx location /admin { allow 192.168.1.0/24; # 内网放行 allow 10.0.0.0/8; # VPN 段放行 deny all; # 其余全部拒绝 proxy_pass http://backend; } ``` **注意事项:** - 当客户端经过代理时,`$remote_addr` 拿到的是代理 IP 而非真实客户端 IP,需要配合 `$http_x_forwarded_for` 或 `realip` 模块获取真实地址 - 白名单优先于黑名单是安全最佳实践——默认拒绝,显式放行 ### 二、HTTP 基本认证:用户名密码验证 使用 `auth_basic` + `auth_basic_user_file` 实现,密码文件通过 `htpasswd` 工具生成: ```nginx location /admin { auth_basic "Admin Area"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://backend; } ``` 生成密码文件: ```bash htpasswd -c /etc/nginx/.htpasswd admin_user ``` **关键点:** Basic Auth 的凭据是 Base64 编码而非加密,生产环境务必搭配 HTTPS 使用,否则密码可被中间人截获。 ### 三、请求方法限制:只允许特定 HTTP 方法 用 `limit_except` 指令比 `if ($request_method)` 更规范,它在 location 级别做方法白名单: ```nginx location /api { limit_except GET POST { deny all; # 只允许 GET 和 POST,其他方法返回 403 } proxy_pass http://api_backend; } ``` **与 `if` 写法的区别:** `limit_except` 是 Nginx 官方推荐的方式,不会触发 "if is evil" 问题,且与 `satisfy` 指令配合更好。 ### 四、基于请求头的鉴权:API Key、Referer、User-Agent **API Key 校验:** ```nginx map $http_x_api_key $api_valid { default 0; "sk_prod_abc123" 1; "sk_prod_def456" 1; } location /api { if ($api_valid = 0) { return 401; } proxy_pass http://api_backend; } ``` 用 `map` 比 `if` 直接比较更灵活,支持多 key 映射且可集中管理。 **防盗链(Referer 校验):** ```nginx location /images/ { valid_referers none blocked server_names *.example.com; if ($invalid_referer) { return 403; } root /var/www/images; } ``` **UA 过滤:** 屏蔽恶意爬虫 ```nginx if ($http_user_agent ~* (bot|crawler|spider|scraper)) { return 403; } ``` ### 五、地理与时间条件控制 **地理位置限制(基于 geo 模块):** ```nginx geo $allowed_country { default no; CN yes; US yes; } server { location / { if ($allowed_country = no) { return 403; } proxy_pass http://backend; } } ``` 如需精确到城市级,可用 `geoip` 模块配合 MaxMind 数据库。 **时间条件限制:** ```nginx map $time_iso8601 $business_hours { default 0; ~^(\d{4}-\d{2}-\d{2}T(09|1[0-9]|2[0-1])) 1; } location /admin { if ($business_hours = 0) { return 403; } proxy_pass http://backend; } ``` 适合管理后台只在工作时段开放的场景。 ### 六、组合策略:satisfy 指令与多层防护 `satisfy any` 表示满足任一条件即可访问,`satisfy all` 表示必须全部满足: ```nginx location /admin { satisfy any; # IP 白名单 或 密码认证,满足其一即可 allow 192.168.1.0/24; deny all; auth_basic "Admin Area"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://backend; } ``` **实战建议:** 管理后台常用 `satisfy any`——内网 IP 免密码,外网需要认证;API 接口常用 `satisfy all`——IP + Key 双重验证。 ### 七、安全加固:敏感文件与目录防护 ```nginx # 禁止访问隐藏文件(如 .git、.env) location ~ /\. { deny all; access_log off; log_not_found off; } # 禁止访问敏感后缀文件 location ~* \.(htaccess|htpasswd|ini|log|sh|sql|bak|swp)$ { deny all; access_log off; } # 禁止目录遍历 autoindex off; ``` 这些规则应作为 Nginx 配置的基线安全策略,防止信息泄露。 ### 面试追问与核心要点 **Q:Nginx 访问控制的执行顺序是什么?** `allow/deny` 按配置顺序从上到下匹配,先命中先生效。location 内的规则优先于 server 级别,server 级别优先于 http 级别。 **Q:`satisfy any` 和 `satisfy all` 的区别?** `any` 是"或"逻辑——IP 白名单和认证满足其一即可;`all` 是"与"逻辑——两者都必须通过。默认是 `all`。 **Q:代理场景下 IP 限制为什么不生效?** 因为 `$remote_addr` 拿到的是上一层代理的 IP。解决方案:使用 `ngx_http_realip_module` 设置 `set_real_ip_from` 和 `real_ip_header X-Forwarded-For` 还原真实客户端 IP。 **Q:`if` 指令在 location 中有什么陷阱?** Nginx 的 `if` 在 location 中属于 rewrite 阶段,可能导致非预期行为("if is evil")。能用 `map`、`limit_except`、`allow/deny` 替代的就避免用 `if`。
服务端5月28日 07:23
Nginx 如何实现限流?有哪些限流策略?## Nginx 如何实现限流?有哪些限流策略? Nginx 限流的核心思路是控制单位时间内的请求量或并发连接数,防止后端服务被流量打垮。面试中这道题主要考察三个层面:你知道哪些限流模块、你理解底层算法吗、你在生产环境怎么用。 ### 限流的两种基本方式 Nginx 提供两大限流模块: - **`limit_req`**:限制请求速率,控制单位时间内允许的请求数 - **`limit_conn`**:限制并发连接数,控制同一时刻的 TCP 连接数 两者的区别在于粒度——`limit_req` 关注的是请求频率(每秒多少个请求),`limit_conn` 关注的是连接数(同时存在多少个连接)。一个长连接可以承载多个请求,所以实际防护中通常两者配合使用。 ### limit_req:请求速率限制 ```nginx http { limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; server { location /api/ { limit_req zone=api_limit burst=20 nodelay; limit_req_status 429; proxy_pass http://backend; } } } ``` 关键参数解读: - `$binary_remote_addr`:以客户端 IP 作为限流键,二进制格式比字符串格式节省内存,10MB 共享内存大约能记录 16 万个 IP - `zone=api_limit:10m`:定义共享内存区域名称和大小 - `rate=10r/s`:每秒允许 10 个请求,也可以用 `r/m` 表示每分钟 - `burst=20`:允许 20 个突发请求排队等待 - `nodelay`:突发请求不延迟处理,超出 burst 容量则直接拒绝 - `limit_req_status 429`:被限流时返回 429 而非默认的 503 ### burst 和 nodelay 到底怎么配合? 这是面试的高频追问,很多人配置过但说不清楚原理。 **只用 `limit_req zone=api_limit`**:严格按 rate 执行,超出的请求直接 503,体验差。 **加 `burst=20`**:允许 20 个请求排队,Nginx 按 rate 速率逐个处理队列中的请求,多余请求延迟响应。好处是不误杀,坏处是用户感知延迟。 **再加 `nodelay`**:队列中的请求立即处理,不延迟响应,但队列满了还是拒绝。实际效果是「短时间内允许突发,超出就拒绝」,适合大多数 API 场景。 简单记:**burst 控制能容忍多少突发,nodelay 决定突发请求是延迟还是立即处理**。 ### limit_conn:并发连接数限制 ```nginx http { limit_conn_zone $binary_remote_addr zone=conn_limit:10m; server { limit_conn conn_limit 10; proxy_pass http://backend; } } ``` 这里 `limit_conn conn_limit 10` 表示同一个 IP 最多同时保持 10 个连接。注意这和 `limit_req` 不同——`limit_req` 限制的是请求的到达速率,`limit_conn` 限制的是连接的并发数量。 典型场景:防止单个客户端通过大量并发连接耗尽服务器资源(如慢速攻击 Slowloris)。 ### 底层算法:漏桶与令牌桶 面试中问限流,必然会追问算法原理。 **漏桶算法(Leaky Bucket)** 请求像水一样倒入桶中,桶以固定速率漏水。如果桶满了,新请求被丢弃。特点是输出速率恒定,不管输入多猛烈,处理速度始终平稳。`limit_req` 不加 burst 参数时就是典型的漏桶行为——严格按 rate 处理,超出直接拒绝。 **令牌桶算法(Token Bucket)** 系统以固定速率往桶里放令牌,每个请求需要取走一个令牌。桶满了令牌不再增加。与漏桶的区别在于:令牌桶允许突发——桶里攒够了令牌时,可以一次性处理一批请求。`limit_req` 加上 burst 参数就实现了类似令牌桶的效果,允许一定程度的流量突发。 核心区别:漏桶强制匀速输出,令牌桶允许有限突发。Nginx 的 `limit_req` 实际上是两者的结合——基础速率是漏桶,burst 提供了令牌桶式的突发能力。 ### 带宽限制 除了请求和连接层面的限流,Nginx 还能限制响应传输速率: ```nginx location /download/ { limit_rate 1m; limit_rate_after 10m; root /var/www/files; } ``` - `limit_rate 1m`:限速 1MB/s - `limit_rate_after 10m`:前 10MB 不限速,之后才限速 适用于大文件下载场景,防止少数大流量用户占满带宽。 ### 白名单与动态限流 生产环境中通常需要对内部 IP 或特定请求方法豁免限流。 **基于 geo 的白名单:** ```nginx geo $limit_key { default $binary_remote_addr; 192.168.1.0/24 ""; 10.0.0.0/8 ""; } limit_req_zone $limit_key zone=whitelist:10m rate=10r/s; ``` 白名单内的 IP 对应空字符串,不参与限流计算。 **基于请求方法的动态限流:** ```nginx map $request_method $limit_key { default $binary_remote_addr; GET ""; HEAD ""; } ``` GET 和 HEAD 请求不限流,其他方法(POST、PUT 等)参与限流,适合写操作需要更严格控制的场景。 ### 多层限流实战配置 ```nginx http { # 全局限流 limit_req_zone $binary_remote_addr zone=global:10m rate=50r/s; # API 接口限流 limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; # 登录接口限流 limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; # 连接数限制 limit_conn_zone $binary_remote_addr zone=conn:10m; limit_req_status 429; limit_conn_status 429; server { limit_conn conn 20; location / { limit_req zone=global burst=50 nodelay; proxy_pass http://backend; } location /api/ { limit_req zone=global burst=50 nodelay; limit_req zone=api burst=10 nodelay; proxy_pass http://api_backend; } location /login { limit_req zone=login burst=2 nodelay; proxy_pass http://auth_backend; } } } ``` 这套配置的思路是分层防护:全局兜底防 DDoS,API 层控制接口频率,登录接口单独严控防暴力破解。同一个 location 可以叠加多个 `limit_req`,任一规则触发都会拒绝请求。 ### 限流日志与监控 ```nginx log_format limit '$remote_addr - [$time_local] "$request" ' '$status limit=$limit_req_status'; limit_req_log_level warn; ``` `$limit_req_status` 变量记录限流状态,`limit_req_log_level` 控制限流日志级别。生产环境建议用 warn 级别,避免日志量过大。配合 ELK 或 Prometheus 可以做限流趋势分析和告警。 ### 生产环境的几个经验 1. **阈值不是拍脑袋定的**——先压测后端服务的极限 QPS,限流值设在其 70%-80% 作为安全水位 2. **429 响应要友好**——返回 JSON 格式的错误提示,带上 Retry-After 头告诉客户端多久后重试 3. **zone 内存别省**——10MB 约存 16 万 IP,如果用户量大要相应调大,内存耗尽后新请求直接 503 4. **burst 要结合业务**——API 类场景 burst 可以小一些(5-10),页面访问场景可以大一些(20-50) 5. **限流不是万能的**——在 Nginx 层限流只能防住从外到内的流量冲击,内部服务间的调用保护需要 Sentinel 或熔断器 6. **关注误杀**——公司出口 IP 共享场景下,单 IP 限流会误伤同一 NAT 后的多个用户,可考虑基于 token 或租户维度的限流键 ### 追问:limit_req 和 limit_conn 该选哪个? 都要用。`limit_req` 防高频请求冲击,`limit_conn` 防连接数耗尽,两者解决不同问题。面试中如果只答一个,会被认为理解不全面。 ### 追问:Nginx 限流有什么局限? - 单机维度的限流,分布式环境下需要 Redis + Lua 或专门的限流服务 - 限流键有限,复杂业务逻辑(如按用户等级限流)需要结合 OpenResty 或网关层 - `limit_req` 基于共享内存,重启后状态丢失 - 不支持滑动窗口计数,只有固定时间窗口的速率计算
服务端5月27日 22:13
Nginx 如何进行安全配置?有哪些安全最佳实践?## Nginx 如何进行安全配置?有哪些安全最佳实践? 核心思路是:隐藏信息、限制访问、加密传输、防御攻击,四层递进。 ### 一、隐藏服务器指纹 攻击者第一步是信息收集,版本号是最直接的突破口。 ```nginx server_tokens off; ``` 关掉之后,响应头和错误页不再暴露 Nginx 版本。面试追问:**还能怎么隐藏?** 可以用 `more_set_headers` 模块改掉 `Server` 字段本身,或者在前端反代层抹掉这个头。 ### 二、访问控制与限流 IP 限制和速率限制是防暴力攻击的第一道门。 ```nginx # IP 白名单 location /admin { allow 192.168.1.0/24; deny all; } # 限流 limit_req_zone $binary_remote_addr zone=req:10m rate=10r/s; limit_req zone=req burst=20 nodelay; limit_conn_zone $binary_remote_addr zone=conn:10m; limit_conn conn 10; ``` 面试追问:**burst 和 nodelay 区别是什么?** burst 允许突发排队,nodelay 让排队的请求立即处理而不延时等待,否则超出的请求会被延迟处理。 ### 三、SSL/TLS 与安全头 HTTPS 是底线配置,安全头加固浏览器端防护。 ```nginx ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:50m; ssl_session_tickets off; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Content-Security-Policy "default-src 'self'" always; ``` 面试追问:**为什么关 ssl_session_tickets?** 默认的 session ticket key 是明文保存在 worker 共享内存中的,多台 Nginx 之间无法复用,且存在前向安全性问题。集群部署时应手动轮换 key 或直接关闭。 ### 四、文件与目录防护 禁止访问隐藏文件和敏感后缀,堵住信息泄露的口子。 ```nginx location ~ /\. { deny all; } location ~* \.(htaccess|ini|log|sql|bak|swp)$ { deny all; } autoindex off; ``` ### 五、超时与缓冲区 防止慢速攻击和缓冲区溢出。 ```nginx client_body_timeout 10; client_header_timeout 10; keepalive_timeout 5; send_timeout 10; client_max_body_size 10m; client_header_buffer_size 1k; ``` 面试追问:**如果只改一个配置,改哪个?** 先关 `server_tokens`,零成本高风险。然后上 HTTPS,这是生产环境硬性要求。限流和访问控制按业务场景逐步加。 ### 常见误区 用 `if` 正则匹配 SQL 注入或 XSS 关键词来拦截攻击,这是错误的。Nginx 的 `if` 在 location 中行为不稳定,且正则容易被编码绕过。防注入应交给 WAF(如 ModSecurity)或应用层参数校验,Nginx 只做流量层防护。
服务端5月27日 22:13
Nginx 性能调优需要关注哪些关键参数?## Nginx 性能调优需要关注哪些关键参数? Nginx 调优的核心思路是:减少不必要的系统调用、充分利用内核零拷贝能力、压缩传输体积、避免连接浪费。下面按影响程度从大到小逐项说明。 ## Worker 进程与连接 `worker_processes` 设为 `auto`,让 Nginx 自动匹配 CPU 核心数。`worker_rlimit_nofile` 调到 100000,避免文件描述符耗尽。每个 worker 的 `worker_connections` 可设 65535,理论最大并发 = worker 数 × 65535。`multi_accept on` 让 worker 一次性接收所有就绪连接,`accept_mutex off` 在高并发下减少锁争用。 ```nginx worker_processes auto; worker_rlimit_nofile 100000; events { worker_connections 65535; use epoll; multi_accept on; accept_mutex off; } ``` ## 零拷贝与传输优化 `sendfile on` 跳过用户态拷贝,数据从内核直接到 socket。`tcp_nopush on` 让数据攒满一个包再发,配合 sendfile 减少系统调用次数。`tcp_nodelay on` 禁用 Nagle 算法,避免小包延迟。三者同时开启并不矛盾:tcp_nopush 在 sendfile 阶段生效,最后一个包由 tcp_nodelay 立即发出。 ```nginx sendfile on; tcp_nopush on; tcp_nodelay on; ``` ## Gzip 压缩 压缩能将文本响应体积缩减 60%-80%,直接降低带宽和传输耗时。`gzip_comp_level` 建议设 4-6,再高收益递减但 CPU 开销上升。`gzip_min_length` 设 1024,避免压缩小响应反而变大。别忘了通过 `gzip_types` 覆盖 JSON、SVG 等常见 MIME 类型。 ```nginx gzip on; gzip_vary on; gzip_min_length 1024; gzip_comp_level 5; gzip_types text/plain text/css application/json application/javascript application/xml image/svg+xml; ``` ## 连接复用与超时 `keepalive_timeout` 控制客户端长连接保持时间,默认 75s,可视业务缩短到 30-60s。`keepalive_requests` 限制单连接最大请求数,防止连接泄漏。对上游服务器也要开长连接:upstream 块中 `keepalive 32` 保持 32 条空闲连接,减少反复握手开销。 ```nginx keepalive_timeout 60s; keepalive_requests 1000; upstream backend { server 10.0.0.1:8080; keepalive 32; } ``` ## 缓冲区与文件缓存 `client_body_buffer_size` 和 `client_header_buffer_size` 按业务调整,过小会触发临时文件写入拖慢请求。`open_file_cache` 缓存频繁访问的文件描述符,减少磁盘 stat 调用,对静态资源场景效果显著。 ```nginx client_header_buffer_size 2k; client_body_buffer_size 128k; open_file_cache max=10000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; ``` ## 日志降耗 写日志是高并发下的隐性瓶颈。静态资源 `access_log off` 直接关掉;动态请求的日志加 `buffer=32k flush=5s`,写操作先进内存再批量落盘。 ## 追问:调了参数怎么验证效果? 用 wrk 或 ab 做基准测试,对比调优前后的 QPS、P99 延迟和错误率。生产环境通过 `stub_status` 模块持续监控活跃连接数和请求处理量。记住一个原则:每次只改一个参数,观察效果后再改下一个,否则无法定位哪个改动真正有效。
服务端5月27日 22:13
Nginx 日志怎么配置?有哪些格式和优化方法?## Nginx 如何配置日志?有哪些日志格式和优化方法? Nginx 提供了访问日志(access_log)和错误日志(error_log)两种日志类型。面试中常考的是日志格式自定义、条件记录和性能优化三条主线。 ## 访问日志与自定义格式 访问日志通过 `log_format` 定义格式,再用 `access_log` 引用。常用三种格式: - **main 格式**:记录 IP、请求行、状态码、Referer、UA、耗时等基础信息 - **detailed 格式**:在 main 基础上增加 upstream 地址、状态、X-Forwarded-For、request_id - **JSON 格式**:用 `escape=json` 转义,方便 ELK/Grafana Loki 等工具直接解析 ```nginx log_format main "$remote_addr - $remote_user [$time_local] " ""$request" $status $body_bytes_sent " ""$http_referer" "$http_user_agent" " "$request_time $upstream_response_time"; log_format json_combined escape=json "{" ""time_local":"$time_local", ""remote_addr":"$remote_addr", ""request":"$request", ""status":"$status", ""request_time":"$request_time"" "}"; access_log /var/log/nginx/access.log main; ``` **追问:access_log 和 error_log 有什么区别?** access_log 记录每次请求的详细信息,可自定义格式;error_log 记录服务器错误,不支持自定义格式,只能设级别(debug/info/notice/warn/error/crit/alert/emerg)。 ## 日志性能优化 高并发场景下日志写入会成为瓶颈,核心优化手段: - **关闭不必要的日志**:静态资源、健康检查路径用 `access_log off` - **缓冲写入**:`access_log ... buffer=32k flush=5s`,减少磁盘 I/O 次数 - **gzip 压缩**:`access_log ... gzip=9`,节省磁盘空间 - **条件记录**:用 `map` + `if=` 只记录特定状态码或慢请求 ```nginx map $status $loggable { ~^[23] 0; default 1; } access_log /var/log/nginx/access.log main if=$loggable; location ~* \.(css|js|jpg|png|gif|ico)$ { access_log off; } access_log /var/log/nginx/access.log main buffer=32k flush=5s; ``` **追问:buffer 和 flush 参数分别控制什么?** buffer 控制缓冲区大小,写满才刷盘;flush 控制最大等待时间,超时强制刷盘。两者配合保证日志既不丢又不多写。 ## 日志轮转与分离 单文件日志会无限增长,必须配合 logrotate 轮转: ```bash # /etc/logrotate.d/nginx /var/log/nginx/*.log { daily rotate 14 compress delaycompress missingok sharedscripts postrotate [ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid) endscript } ``` `kill -USR1` 让 Nginx 重新打开日志文件,不会中断服务。 按业务分离日志同样重要:API 请求写独立文件,不同 server 块各写各的,便于精准排查问题。 ## 常用变量速查 | 变量 | 说明 | |------|------| | $remote_addr | 客户端 IP | | $status | 响应状态码 | | $request_time | 请求总耗时 | | $upstream_response_time | 上游响应时间 | | $http_x_forwarded_for | 真实客户端 IP | 生产环境建议用 JSON 格式 + 缓冲写入 + logrotate 轮转 + 条件过滤,四板斧组合基本够用。
服务端5月27日 22:12
Nginx 监控运维怎么做?stub_status、Prometheus、ELK 怎么选?## 答案前置:Nginx 监控运维的核心思路 Nginx 监控围绕三条线展开:**指标采集**(stub_status / 日志)→ **存储与展示**(Prometheus+Grafana / ELK / Zabbix)→ **告警与响应**(阈值告警 + 自动化脚本)。面试中常考的不是你背了多少工具名,而是能不能说清楚每一步为什么这么做、不同规模场景怎么选型。 ## 内置指标:stub_status 能拿到什么? stub_status 是 Nginx 自带的状态模块,开启后在指定路径暴露一组关键指标: ```nginx location /nginx_status { stub_status on; access_log off; allow 127.0.0.1; deny all; } ``` 访问后返回:Active connections(当前活跃连接数)、accepts/handled/requests(累计连接与请求数)、Reading/Writing/Waiting(读请求头、写响应、空闲等待的连接数)。 **追问:Waiting 数量大说明什么?** 如果 Active connections ≈ Waiting,说明大量连接是 keep-alive 空闲态,连接复用率高是好事;但如果同时 Writing 偏低而请求排队,就要检查上游响应是否过慢。 ## 日志监控:比 stub_status 更细的粒度 stub_status 只有粗粒度指标,真正定位问题靠日志。关键是自定义 log_format 加入上游响应时间: ```nginx log_format detailed '$remote_addr - [$time_local] "$request" $status ' 'rt=$request_time uct=$upstream_connect_time ' 'uht=$upstream_header_time urt=$upstream_response_time'; access_log /var/log/nginx/detailed.log detailed; ``` `request_time` 是总耗时,`upstream_response_time` 是上游处理耗时,两者差值就是 Nginx 自身开销。如果差值大,排查 Nginx 层面的缓冲、压缩或 DNS 解析。 对于 ELK 场景,直接输出 JSON 格式日志省掉 Logstash 的 grok 解析: ```nginx log_format json_log escape=json '{"time":"$time_local","ip":"$remote_addr",' '"status":$status,"rt":$request_time,"urt":"$upstream_response_time"}'; access_log /var/log/nginx/json.log json_log; ``` ## Prometheus + Grafana:云原生场景首选 nginx-prometheus-exporter 把 stub_status 的指标转为 Prometheus 格式,Grafana 做可视化: ```yaml # prometheus.yml scrape_configs: - job_name: 'nginx' static_configs: - targets: ['localhost:9113'] ``` **选型逻辑:** 容器化/K8s 环境下 Prometheus 是事实标准,开箱即用 Service Discovery,Grafana 社区模板丰富。Nginx Plus 用户还可以用官方 nginx-plus-exporter 拿到更细的 upstream 指标。 ## ELK Stack:日志深度分析场景 当需求不只是看指标曲线,而是要按 IP、URL、状态码做聚合分析和历史追溯,ELK 更合适。Filebeat 采集 → Logstash 清洗 → Elasticsearch 存储 → Kibana 可视化,链路长但灵活度高。 **选型对比:** Prometheus 适合指标型监控(数字曲线),ELK 适合日志型分析(文本检索+聚合)。小团队二选一推荐 Prometheus,监控告警闭环更短。 ## Zabbix:传统企业环境的选择 Zabbix 通过 Agent 调用 stub_status 页面,用正则提取指标配置监控项。适合已有 Zabbix 基建的企业,不推荐新项目为 Nginx 单独搭 Zabbix。 ## 告警:监控闭环的关键 有监控没告警等于没监控。核心告警规则: - 5xx 比率超阈值(如 > 1%)触发 critical - Active connections 突增超过 2 倍标准差触发 warning - upstream_response_time P99 > 2s 触发 warning Prometheus 用 Alertmanager 配路由和静默,ELK 用 Watcher 或 ElastAlert,Zabbix 自带触发器机制。 ## 运维常用命令速查 ```bash nginx -t # 测试配置语法 nginx -s reload # 平滑重载,不中断连接 nginx -s quit # 优雅停止,处理完当前请求后退出 nginx -s reopen # 重新打开日志文件(配合 logrotate) ``` 日志轮转用 logrotate,配置 `postrotate` 里发 USR1 信号让 Nginx 重新打开文件句柄,避免写入已轮转的老文件。 ## 面试追问方向 1. **stub_status 的 Waiting 和 keep-alive 是什么关系?** Waiting 连接就是 keep-alive 空闲连接,`keepalive_timeout` 控制超时回收。 2. **Nginx 502 怎么排查?** 先查 upstream 是否存活,再看 Nginx error log 里 `connect() failed` 的具体原因,最后检查 `proxy_read_timeout` 配置。 3. **如何不重启更新配置?** `nginx -t && nginx -s reload`,reload 会 fork 新 worker 加载新配置,老 worker 处理完手头请求后退出。 4. **Prometheus 和 ELK 怎么选?** 指标监控选 Prometheus(轻量、告警闭环好),日志分析选 ELK(全文检索、聚合灵活),大团队通常两套都搭。
服务端5月27日 22:12
Nginx 如何配置 WebSocket 代理?## Nginx 如何配置 WebSocket 代理? WebSocket 建立在 HTTP/1.1 之上,通过 `Upgrade` 机制将 HTTP 连接升级为全双工长连接。Nginx 默认会清除 `Upgrade` 头,所以必须手动配置才能正确代理 WebSocket。 ### 核心配置(三行必写) ```nginx location /ws { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } ``` - `proxy_http_version 1.1`:WebSocket 要求 HTTP/1.1,Nginx 默认用 1.0 - `Upgrade $http_upgrade`:转发客户端的协议升级请求 - `Connection "upgrade"`:告知 Nginx 这不是普通 HTTP,需要保持升级状态 ### 推荐用 map 管理 Connection 头 多 location 场景下,硬编码 `"upgrade"` 会导致非 WebSocket 请求也被标记。用 `map` 按需切换更安全: ```nginx map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { location /ws { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } } ``` 没有 `Upgrade` 头时走 `close`,普通请求不受影响。 ### 超时:为什么连着连着就断了? Nginx 的 `proxy_read_timeout` 默认 60 秒。WebSocket 是长连接,如果 60 秒内没有数据传输,Nginx 会主动断开。解决办法: ```nginx proxy_read_timeout 3600s; # 1小时 proxy_send_timeout 3600s; proxy_connect_timeout 60s; # 建连超时保持短即可 ``` 按业务调,不是越大越好。过长超时意味着僵死连接不会被回收。 ### WSS(WebSocket over TLS) 在 SSL server 块里照加那三行即可,Nginx 负责 TLS 卸载,后端仍用 `ws://`: ```nginx server { listen 443 ssl; ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; location /ws { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } } ``` ### 负载均衡要注意会话保持 WebSocket 是有状态长连接,轮询策略会导致重连时切到不同后端。必须用 `ip_hash`: ```nginx upstream ws_backend { ip_hash; server 10.0.0.1:8080; server 10.0.0.2:8080; } ``` 如果后端是无状态的(如用 Redis Pub/Sub 做消息同步),也可以用轮询。 ### 追问:连接断开怎么排查? 1. 查 Nginx error log,确认是否超时断开 2. 检查 `Upgrade` / `Connection` 头是否正确转发 3. 确认 `proxy_buffering off` 已设置,避免数据被缓冲 4. 检查防火墙或 CDN 是否拦截长连接 5. 用 `curl -H "Upgrade: websocket"` 手动测试握手是否返回 101
服务端5月27日 22:11
Nginx 反向代理怎么配置?## Nginx 反向代理怎么配置? 反向代理是 Nginx 最核心的用途之一:客户端请求先到 Nginx,再由 Nginx 转发给后端服务器,客户端感知不到真实后端的存在。和正向代理(代理客户端出国)相反,反向代理代理的是服务端。 ### 最小可用配置 ```nginx server { listen 80; server_name example.com; location / { proxy_pass http://192.168.1.100:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ``` `proxy_pass` 指定后端地址,三条 `proxy_set_header` 是标配——不带这些头,后端拿到的全是 Nginx 的 IP,日志和鉴权都会出问题。 ### 多后端负载均衡 ```nginx upstream backend { server 192.168.1.100:8080 weight=3; server 192.168.1.101:8080 weight=1; server 192.168.1.102:8080 backup; } server { listen 80; location / { proxy_pass http://backend; } } ``` 四种策略选哪个?轮询(默认)无状态通用;`ip_hash` 保会话粘性但分布不均;`least_conn` 适合长连接;加权轮询按机器性能分配。生产环境常用加权轮询 + 健康检查。 ### WebSocket 代理 ```nginx location /ws/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; } ``` 不加 `Upgrade` 头,WebSocket 握手直接失败;`proxy_read_timeout` 不加长,空闲连接会被 Nginx 主动断开,这是最常见的踩坑点。 ### 面试追问:反向代理和正向代理的区别? 反向代理代理服务端,客户端不知道真实后端是谁;正向代理代理客户端,服务端不知道真实客户端是谁。一句话:正向代理帮客户端藏身份,反向代理帮服务端藏身份。 ### 生产环境别漏这些 - 超时三件套:`proxy_connect_timeout`、`proxy_send_timeout`、`proxy_read_timeout`,默认 60s,慢接口要调大 - 缓冲默认开着,大文件上传场景注意 `proxy_buffer_size` 和 `proxy_buffers` 调大 - HTTPS 场景在 Nginx 做 SSL 终止,后端走内网 HTTP,证书只管 Nginx 一层 - `proxy_redirect off` 防止后端 302 重定向把内网地址暴露给客户端
服务端5月27日 22:10
Nginx 如何配置 HTTPS 和 SSL 证书?## 答案 Nginx 启用 HTTPS 的核心是在 server 块中监听 443 端口并指定证书与私钥路径: ```nginx server { listen 443 ssl http2; server_name example.com; ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; } ``` 同时配置 HTTP 自动跳转 HTTPS: ```nginx server { listen 80; server_name example.com; return 301 https://$host$request_uri; } ``` ## 追问一:SSL 证书有哪些类型?怎么选? - **自签名证书**:测试用,浏览器不信任 - **Let's Encrypt**:免费 DV 证书,90 天有效期,Certbot 自动申请与续期 - **商业证书**(OV/EV):CA 机构签发,验证组织身份,适合生产环境 Let's Encrypt 申请示例: ```bash sudo certbot --nginx -d example.com -d www.example.com ``` ## 追问二:如何提升 HTTPS 安全性? 四个关键措施: 1. **仅启用 TLS 1.2+**,禁用 SSLv3 和 TLS 1.0 2. **HSTS 头**,防止降级攻击:`add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;` 3. **OCSP Stapling**,减少证书验证延迟:`ssl_stapling on; ssl_stapling_verify on;` 4. **强密钥**,至少 2048 位 RSA 或 256 位 ECC ## 追问三:证书链不完整怎么办? 浏览器验证证书需要完整的信任链。若缺少中间证书,需将服务器证书与中间证书合并: ```bash cat example.com.crt intermediate.crt > bundle.crt ``` Nginx 中指向合并后的文件:`ssl_certificate /etc/nginx/ssl/bundle.crt;` ## 追问四:多域名如何共用证书? - **通配符证书**(`*.example.com`)覆盖子域 - **SAN 证书**支持多个域名,Certbot 申请时加多个 `-d` 参数 - **SNI**(Server Name Indication)让同一 IP 托管多张证书,Nginx 原生支持 配置验证用 `nginx -t`,无停机重载用 `nginx -s reload`。
服务端5月27日 21:52
Nginx 的事件驱动模型是什么?如何实现高并发?## 答案 Nginx 采用**事件驱动 + 非阻塞 I/O** 模型,核心是 Master-Worker 进程架构 + epoll 事件循环。每个 Worker 进程单线程运行一个事件循环,通过 epoll 同时监听数千个连接的读写事件,事件就绪时回调处理,I/O 等待期间不阻塞进程,从而用少量进程支撑数万并发连接。 ## 事件驱动核心机制 **epoll 的工作方式**:内核维护一个就绪队列,只有活跃连接才会触发事件通知,时间复杂度 O(1)。与传统 select/poll 的 O(n) 轮询不同,epoll 不受 FD 数量影响——这正是 Nginx 解决 C10K 问题的根基。 **事件循环流程**: ``` Worker 进程启动 → 注册监听 FD 到 epoll → epoll_wait 阻塞等待事件 → 事件就绪返回 → 回调处理(accept/read/write) → 继续 epoll_wait ``` **连接状态机**:每个连接在 Worker 内部以状态机方式管理,经历 `等待读 → 处理请求 → 等待写 → 发送响应 → 等待新请求(keepalive)` 的状态转换,I/O 等待时让出 CPU 给其他连接处理。 ## Master-Worker 进程模型 ```nginx worker_processes auto; # 通常等于 CPU 核心数 worker_rlimit_nofile 65535; # 文件描述符上限 events { worker_connections 10240; # 单 Worker 最大连接数 use epoll; # Linux 选择 epoll multi_accept on; # 一次 accept 多个连接 accept_mutex off; # 高并发下关闭,减少锁争用 } ``` - **Master**:管理 Worker 生命周期,加载配置,不处理业务请求 - **Worker**:各自独立运行事件循环,互不干扰,进程隔离保证稳定性 - **理论并发**:`worker_processes × worker_connections`,4 Worker × 10240 = 40960 并发 ## 与 Apache 的本质区别 | 对比维度 | Nginx | Apache (prefork) | |---------|-------|------------------| | 模型 | 事件驱动,非阻塞 | 每连接一个进程,阻塞 | | 内存 | 10 连接 vs 10 连点约 2MB | 10 连接约 200MB | | C10K | 原生支持 | 受限于进程数 | | 上下文切换 | 极少 | 频繁 | ## 高并发调优关键参数 1. **worker_processes**:设为 `auto` 或 CPU 核心数 2. **worker_connections**:根据内存调整,通常 10240-65535 3. **accept_mutex**:高并发下关闭(`off`),低并发开启防惊群 4. **系统级**:`fs.file-max`、`net.core.somaxconn`、`tcp_tw_reuse` ## 追问 - **epoll 的 LT 和 ET 模式有什么区别?Nginx 默认用哪个?** — LT 水平触发会重复通知,ET 边沿触发只通知一次,Nginx 默认 LT,配合非阻塞 I/O 确保数据读完 - **为什么 Worker 单线程还能处理上万连接?** — 因为 99% 时间连接在等 I/O,事件驱动只在 I/O 就绪时才占用 CPU - **accept_mutex 是什么?什么时候该关?** — 惊群控制锁,防止所有 Worker 同时争抢新连接。连接数远大于 Worker 数时关闭可提升吞吐
服务端5月27日 21:45
Nginx 的负载均衡有哪些策略?如何配置?## Nginx 负载均衡有哪些策略?如何配置? Nginx 通过 `upstream` 模块实现负载均衡,内置 5 种策略,另有第三方扩展策略。面试核心答案:轮询(默认)、加权轮询、最少连接、IP 哈希、一致性哈希。 ### 1. 轮询(Round Robin,默认) 按顺序依次分配请求,服务器性能相近时使用。 ```nginx upstream backend { server 192.168.1.100:8080; server 192.168.1.101:8080; } ``` ### 2. 加权轮询(Weighted Round Robin) 权重越高分配越多,适用于服务器性能不均。 ```nginx upstream backend { server 192.168.1.100:8080 weight=3; server 192.168.1.101:8080 weight=1; } ``` ### 3. 最少连接(Least Connections) 将请求分给当前活动连接数最少的服务器,适用于请求处理时间差异大的场景。 ```nginx upstream backend { least_conn; server 192.168.1.100:8080; server 192.168.1.101:8080; } ``` ### 4. IP 哈希(IP Hash) 同一客户端 IP 始终分配到同一台服务器,实现会话保持。 ```nginx upstream backend { ip_hash; server 192.168.1.100:8080; server 192.168.1.101:8080; } ``` **注意**:ip_hash 在后端服务器增减时会导致大量请求重新分配,一致性哈希可解决此问题。 ### 5. 一致性哈希(Hash) 基于指定 key(如 URI、Cookie)做哈希,`consistent` 参数启用一致性哈希算法,减少服务器变动时的映射抖动。 ```nginx upstream backend { hash $request_uri consistent; server 192.168.1.100:8080; server 192.168.1.101:8080; } ``` ### 服务器状态参数 ```nginx upstream backend { server 192.168.1.100:8080 weight=3 max_fails=3 fail_timeout=30s; server 192.168.1.101:8080 weight=2 max_fails=3 fail_timeout=30s; server 192.168.1.102:8080 down; # 永久下线 server 192.168.1.103:8080 backup; # 备用,主服务不可用时启用 server 192.168.1.104:8080 max_conns=100; # 最大并发连接数 } ``` ### 策略选择速查 | 场景 | 推荐策略 | |---|---| | 服务器性能相近 | 轮询 | | 服务器性能不均 | 加权轮询 | | 请求处理时间差异大 | 最少连接 | | 需要会话保持 | IP 哈希 / 一致性哈希 | | 需要缓存命中 | 一致性哈希(基于 URI) | ### 完整配置示例 ```nginx http { upstream backend { least_conn; server 192.168.1.100:8080 weight=3 max_fails=3 fail_timeout=30s; server 192.168.1.101:8080 weight=2 max_fails=3 fail_timeout=30s; server 192.168.1.103:8080 backup; keepalive 32; } server { listen 80; server_name example.com; location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } } ``` ### 健康检查 开源版 Nginx 仅支持被动健康检查(通过 `max_fails` / `fail_timeout` 判断),商业版 Nginx Plus 提供主动健康检查。 ### 面试追问 **Q:ip_hash 和一致性哈希的区别?** ip_hash 以客户端 IP 为 key,后端变动时映射大规模失效;一致性哈希通过虚拟环减少变动影响,且可自定义 key(URI、Cookie 等),灵活性更高。 **Q:Nginx 负载均衡能做四层转发吗?** 能。使用 `stream` 模块可做 TCP/UDP 四层负载均衡,配置方式与 HTTP 的 `upstream` 类似。 **Q:如何实现更精细的会话保持?** Nginx Plus 支持 `sticky cookie` 指令;开源版可配合一致性哈希 `hash $cookie_jsessionid consistent` 实现基于 Cookie 的会话粘滞。
前端2024年7月15日 23:58
如何动态修改 nginx 的配置信息?### 动态配置 Nginx 的方法 确实,动态配置 Nginx 是在不重启服务的情况下更改配置的实用能力。这对于需要高可用性的生产环境尤其重要。以下是几种可以实现动态配置Nginx的方法: #### 1. 使用 `nginx -s reload` 这是最常见的动态修改Nginx配置的方法。修改完nginx的配置文件后,可以使用 `nginx -s reload` 命令来重新加载配置文件,这样做可以不中断服务。这个命令实际上会启动新的worker进程,并逐渐停止旧的worker进程。例如: ```bash sudo nginx -s reload ``` #### 2. 使用 Consul 和 Consul Template Consul 是一个服务网络解决方案,可以用来动态地处理服务发现和配置。搭配使用 Consul Template 可以动态生成Nginx配置文件。Consul Template 监控Consul的状态变化,一旦检测到变化,就会重新渲染配置模板并重新加载Nginx。这种方法适用于基于服务发现的动态配置场景。 #### 3. 使用 OpenResty OpenResty 是一个基于Nginx与Lua的动态web平台,它允许通过编写Lua脚本来动态地更改配置逻辑。例如,可以在access阶段根据请求的不同动态改变代理服务器或者其他配置。这种方法提供了极高的灵活性。 #### 4. 使用 Docker 容器 在Docker容器中运行Nginx时,可以通过更新Docker容器的配置来实现Nginx的动态配置。这通常涉及到使用环境变量或挂载配置卷来修改配置。容器化管理工具(如Kubernetes)可以在不停机的情况下滚动更新Nginx配置。 #### 5. 动态模块 Nginx也支持动态模块,这些模块可以在不重新编译Nginx的情况下加载或卸载。这使得用户可以根据需要添加或删除功能,虽然这不直接修改Nginx的配置文件,但它提供了一种方式来扩展Nginx的功能而不需要重启服务。 ### 结论 动态配置Nginx主要目的是减少因配置更改而导致的服务中断。上述方法各有优势,适用于不同的场景和需求。在选择具体的实现方式时,应评估实际的业务需求、资源和技术栈。