赞
踩
Nginx调优
一、性能优化考虑点
当我们需要进行性能优化时,通常意味着现有的服务器无法满足不断增长的业务需求。性能优化是一个涉及多方面的课题,需要从以下几个方面进行探讨。
1、确定当前系统结构瓶颈
在进行性能优化之前,首先要找出现有系统中的瓶颈。这可能包括 CPU、内存、磁盘 I/O、网络等方面的限制。通过监控工具和性能分析,可以帮助我们了解瓶颈所在,并有针对性地进行优化。
2、了解业务模式
深入了解业务需求和模式,以便更好地为其量身定制优化方案。例如,根据业务请求的特点,可以选择适当的负载均衡策略、缓存策略和数据存储方案。
3、原理、性能和安全的平衡
在进行性能优化时,需要平衡原理、性能和安全之间的关系。过度追求性能可能导致系统的可维护性降低,甚至影响安全性。因此,优化过程中要充分考虑这三者之间的平衡,以实现最佳的性能提升。
在进行性能优化时,我们可以从系统架构、软件配置、硬件资源等多个层面进行改进。通过全面分析现有系统的状况,我们可以制定出更加合适的优化策略,提升系统的性能,满足业务的发展需求。
二、master-worker机制
master-worker工作原理图
一个 master (进程) 管理多个 worker (进程)
master-worker机制
争抢机制示意图
1、Nginx 采用 Master-Worker 架构,即一个 Master 进程管理多个 Worker 进程(多进程结构),而非多线程结构。
2、当client发出请求时,Master 进程会通知其管理的 Worker 进程。
3、Worker 进程开始抢占任务,成功抢占的 Worker 进程会建立连接并完成任务。
4、每个 Worker 都是独立的进程,且在每个进程内只有一个主线程。
5、Nginx 使用了 I/O 多路复用机制(需要在 Linux 环境下),这使得 Nginx 能够在有限的 Worker 进程下实现高并发。
通过这种 Master-Worker 机制,Nginx 能够充分利用系统资源,实现高性能、高并发的服务。
master-worker机制示意图
master-worker模式
1、Nginx 在启动后会存在一个 Master 进程和多个相互独立的 Worker 进程。
2、Master 进程负责接收外界的信号,并向各 Worker 进程发送信号。每个 Worker 进程都有可能处理这些连接。
3、进程能监控 Worker 进程的运行状态。当 Worker 进程异常退出时,Master 进程会自动启动新的 Worker 进程以保持服务的稳定性。
通过这种 Master-Worker 模式,Nginx 能够实现更高的可靠性和稳定性,同时有效地处理大量并发连接。
accept_mutex解决“惊群现象”
1、惊群现象是指当一个新连接到来时,多个子进程都从继承的父进程 sockfd 收到通知并竞相与新连接建立连接的现象。
2、在惊群现象中,大量进程被激活又挂起,只有一个进程能实际 accept() 到这个连接,这会消耗系统资源。
3、为解决惊群现象,Nginx 提供了 accept_mutex,这是一个加在 accept 上的共享锁。在执行 accept 之前,每个 Worker 进程需要先获取锁,获取不到则放弃执行 accept()。这样,在同一时刻,只会有一个进程去 accept(),从而避免惊群现象。
4、当一个 Worker 进程 accept() 到连接后,它会开始读取请求、解析请求、处理请求、产生响应数据,最后返回给客户端并断开连接,完成一个完整的请求处理。
5、一个请求完全由一个 Worker 进程处理,且只能在一个 Worker 进程中处理。
通过使用 accept_mutex,Nginx 能够有效地解决惊群现象,降低系统资源消耗,提高服务性能。
用多进程结构而不用多线程结构的好处
1、节省锁开销:每个 Worker 进程都是独立的进程,它们不共享资源,因此不需要加锁。这降低了系统开销,并在编程和问题调试方面提供了便利。
2、独立进程降低风险:采用独立的进程可以确保互相之间不会受到影响。如果一个进程出现问题并退出,其他进程仍然可以继续工作,服务不会中断。同时,Master 进程会快速启动新的 Worker 进程以维护服务稳定性。
实现高并发的秘密-IO多路复用
1、尽管 Nginx 的每个进程只有一个主线程,但它依然能实现高并发。这得益于 IO 多路复用的原理,通过异步非阻塞的事件处理机制和 epoll 模型,实现了轻量级和高并发。
2、具体来说,当一个请求进入时,会有一个 Worker 进程处理。然而,Worker 进程并不会全程处理请求,而是处理到可能发生阻塞的地方。例如,Worker 进程向上游(后端)服务器转发请求并等待响应。
3、此时,Worker 进程不会闲等,而是在发送完请求后注册一个事件:“如果上游服务器返回数据,通知我继续处理。”然后,Worker 进程开始处理其他请求。
4、当上游服务器返回数据时,触发了之前注册的事件,Worker 进程重新接手该请求并继续处理。
由于 Web 服务器的工作性质使得每个请求的大部分时间都花费在网络传输中,而在服务器上的实际处理时间较短。这就是为什么少量进程就能解决高并发的秘密所在。
小结:Nginx的master-worker工作机制的优势
1、支持热部署:可以通过 nginx -s reload 实现配置的热更新,无需重启服务。
2、独立进程减少锁开销:每个 Worker 进程独立运行,无需加锁。这降低了系统开销,并在编程和问题调试方面提供了便利。
3、异步非阻塞 / IO 多路复用:尽管每个进程只有一个主线程,但通过异步非阻塞的方式和 IO 多路复用来处理请求,实现高并发。
4、进程间互不影响,提高稳定性:独立进程之间不会相互影响,即使一个 Worker 进程退出,其他 Worker 进程仍能工作,服务不会中断。Master 进程会快速启动新的 Worker 进程以维护服务稳定性。
5、充分利用 CPU 性能:将一个 Worker 分配给一个 CPU,使得 Worker 线程能够充分发挥 CPU 性能。
三、系统与Nginx性能调优
在了解了系统瓶颈和现状之后,我们可以针对系统性能进行全面优化。
网络:网络流量、丢包情况以及网络稳定性都会影响用户请求。
系统:系统负载、饱和度、内存使用率、系统稳定性以及硬盘磁盘损坏情况都需要关注。
服务:通过在 Nginx 中根据业务需求进行设置,可以进行连接优化、内核性能优化以及 HTTP 服务请求优化。
程序:需要关注接口性能、请求处理速度以及每个程序的执行效率。
数据库和底层服务:考虑数据库性能和底层服务对整体性能的影响。
1、文件句柄
在 Linux/Unix 系统中,一切皆文件。每次用户发起请求时,系统会生成一个文件句柄。文件句柄可以看作是文件的索引,随着请求量的增加,进程对文件句柄的调用频率也会相应提高,导致文件句柄数量增多。系统对文件句柄的默认限制是 1024 个,但对于 Nginx 来说,这个限制过小,需要适当调整以提高性能。
ulimit -n #linux 下可以查看系统设置的最大文件句柄
(1)设置方式
系统全局性修改
用户局部性修改
进程局部性修改
(2)系统全局性修改和用户局部性修改
vim /etc/security/limits.conf
soft:软控制,到达设定值后,操作系统不会采取措施,只是发提醒
hard:硬控制,到达设定值后,操作系统会采取机制对当前进行进行限制,这个时候请求就会受到影响
root:这里代表root用户(用户局部性修改)
*:代表全局,即所有用户都受此限制(系统全局性修改)
nofile:指限制的是文件数的配置项。后面的数字即设定的值,一般设置10000左右
尤其在企业新装的系统,这个地方应该根据实际情况进行设置,可以设置全局的,也可以设置用户级别的。
su #刷新以下环境
ulimit -n #再次查看系统最大文件句柄
这里就改为了,我们对root用户设置的最大文件句柄65535
(3)进程局部性修改
vim /usr/local/nginx/conf/nginx.conf
每个进程的最大文件打开数,所以最好与ulimit -n的值保持一致。实际上,Nginx 工作进程文件句柄限制受系统文件句柄限制的约束。这意味着,Nginx 工作进程文件句柄限制的值不能超过系统文件句柄限制的值。
重启一下服务
2、cpu的亲和配置&worker数量调优
CPU 亲和性配置使得 Nginx 可以将不同的 worker 进程绑定到不同的 CPU 核心上。这样做可以减少 worker 之间在不同 CPU 核心之间频繁切换的次数,降低进程迁移的频率,从而减少性能损耗。
为了充分利用服务器性能,建议将 worker 数量设置为等于服务器 CPU 核心数。这样可以确保每个 worker 线程都能充分利用单个 CPU 的性能。如果 worker 数量设置过少,可能会导致 CPU 资源浪费;而设置过多,则会引起 CPU 频繁切换上下文,进而产生额外的性能损耗。
(1)cpu亲和性配置
Nginx允许工作进程个数一般设置CPU的核心或者核心数*2.如果不了解cpu核数,可以使用top后按1看出来,也可以查看/proc/cpuinfo文件
cat /proc/cpuinfo | grep ^processor | wc -l #查看cpu核数
lscpu | grep "CPU(s)" #另一种查看cpu核数的方式
这里我是4核
随后进入nginx配置文件进行修改。需要知道的就是nginx 1.9版本之后,就帮我们自动绑定了cpu;
所以我们只需要打开cpu亲和就行
vim /usr/local/nginx/conf/nginx.conf
user nginx;
worker_processes 4;
worker_cpu_affinity auto;
#或者 worker_cpu_affinity 0001 0010 0100 1000;
worker_rlimit_nofile 65535;
worker_rlimit_nofile 参数用于更改 Nginx worker 进程的最大打开文件数限制。默认情况下,该值取决于操作系统的限制。为了避免 “too many open files” 问题并提高 Nginx 的性能,可以适当提高此值。但在调整 worker_rlimit_nofile 值之前,请确保先调整操作系统的文件句柄限制,以允许 Nginx 使用更多的文件句柄。
需要注意的是,过高的 worker_rlimit_nofile 值可能导致系统资源耗尽,因此建议根据实际需求和服务器资源情况合理设置这个值。
重启一下nginx,查看一下nginx worker进程绑定对应的cpu
ps -eo pid,args,psr | grep -v grep | grep nginx
这样就能看到nginx的子进程都绑定到了哪些cpu核上
设置worker数量,Nginx 默认没有开启利用多核cpu,可以通过增加worker_cpu_affinity配置参数来充分利用多核cpu的性能
#2 核 cpu,开启 2 个进程
worker_processes 2;
worker_cpu_affinity 01 10;
#2 核 cpu,开启 4 个进程,
worker_processes 4;
worker_cpu_affinity 01 10 01 10;
#4 核 cpu,开启 2 个进程,0101 表示开启第一个和第三个内核,1010 表示开启第二个和第四个内核;
worker_processes 2;
worker_cpu_affinity 0101 1010;
#4 个 cpu,开启 4 个进程
worker_processes 4;
worker_cpu_affinity 0001 0010 0100 1000;
#8 核 cpu,开启 8 个进程
worker_processes 8;
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;
worker_cpu_affinity 理解
(2)worker数量调优
如果不确定如何设置 Nginx 中的 worker_processes 参数,将其设置为与可用 CPU 核心数相等是一个很好的选择。这样,每个 CPU 内核都可以运行一个 worker 进程,从而最有效地利用硬件资源。
官方的说明如下
# 推荐方式:
worker_processes auto;
这将自动设置 worker_processes 为与可用 CPU 核心数相等。
# 可选方式:
- # 假设 VCPU(可用 CPU 核心数)为 4
- # 可以将 worker_processes 设置为 VCPU - 1
# 查看 VCPU 数量的命令:
grep "processor" /proc/cpuinfo | wc -l worker_processes 3;
请注意,上述可选方式中的 “VCPU - 1” 可能不适用于所有场景,建议根据实际需求和服务器资源情况进行调整。
3、事件处理模型&连接处理multi_accept优化
Nginx 的连接处理机制根据不同的操作系统采用不同的 I/O 模型。在 Linux 系统下,Nginx 使用 epoll 作为 I/O 多路复用模型;在 FreeBSD(类 Unix 操作系统)下,使用 kqueue 作为 I/O 多路复用模型;在 Solaris(Unix 系统的一个重要分支操作系统)下,使用 /dev/poll 方式的 I/O 多路复用模型;在 Windows 下,使用 IOCP 等。为了获得最佳性能,建议根据操作系统类型选择适当的事件处理模型。如果你使用的是 CentOS(一种 Linux 发行版),那么我推荐将 Nginx 的事件处理模型调整为 epoll。
进入nginx配置文件再进行修改
- events {
-
- worker_connections 10240;
-
- multi_accept on;
-
- use epoll;
-
- }
multi_accept 参数告诉 Nginx 在收到一个新连接通知后尽可能多地接受连接。默认值是 on。当设置为 on 时,多个 worker 会按串行方式处理连接,即一个连接只唤醒一个 worker,其他 worker 处于休眠状态。当设置为 off 时,多个 worker 会按并行方式处理连接,即一个连接会唤醒所有 worker,直到连接分配完毕,未获得连接的 worker 继续休眠。
当服务器连接数较少时,开启 multi_accept 参数(设置为 on)有助于降低负载。然而,当服务器吞吐量较大时,为了提高效率,可以考虑关闭此参数(设置为 off)。根据实际需求和服务器资源情况进行调整。
4、设置work_connections连接数
worker_connections 参数表示每个 worker(子进程)可以创建的最大连接数。
默认值:worker_connections: 1024
最大值:worker_connections: 65535
需要根据系统的最大打开文件数来调整此参数。
系统的最大打开文件数应大于等于 worker_connections * worker_processes。
为了确保正常运行,worker_connections 参数应小于等于系统的最大打开文件数。
实际的 worker_connections 连接数为:worker_connections * worker_processes
要查看linux系统的最大打开文件数,可以运行以下命令:
ulimit -a | grep "open files"
你可以根据服务器性能和需求调整 worker_connections。在本例中,我们将其设置为 10240:
worker_connections 10240; #nginx设置
根据最大连接数计算最大并发数
对于支持 HTTP 1.1 的浏览器,每次访问通常占用两个连接。因此,对于普通的静态访问,最大并发数为:worker_connections * worker_processes / 2
对于 Nginx 作为反向代理的情况,最大并发数量应为:worker_connections * worker_processes / 4。这是因为作为反向代理服务器,每个并发会建立与客户端的连接和与后端服务的连接,分别占用两个连接。
看一个示意图
5、keepalive timeout会话保持时间
keepalive_timeout 65;
keepalive_timeout参数设置长连接的会话保持时间。这个值表示浏览器与服务器之间的连接在多久内没有活动后会被关闭。在此期间,如果有新的请求产生,计时器将会被重置。因此,这个值不应设置得过长,通常 60 秒左右即可。
此外,还有一些与 keepalive 相关的配置参数:
keepalive:指定向上游服务器保留的连接数。
keepalive_disable:禁用某些浏览器的 keepalive 功能。
keepalive_requests:设置在一个长连接中可以并发接收的请求数量。默认值为 1000。
send_timeout:在向客户端发送数据时,如果在建立连接后两次活动时间内服务器没有返回数据,那么连接将被关闭。
keepalive_time:设置 TCP 连接的总时长。超过此时间,将需要重新建立连接。
总之,keepalive 是一种长连接机制,通过 keepalive_timeout 参数设置会话保持时间,可以有效地减少不必要的连接建立和关闭过程,从而提高服务器性能。
6、GZIP压缩性能优化
- gzip on; # 开启gzip压缩功能,减少传输数据量,提高网页加载速度。
- gzip_min_length 1k; # 设置允许压缩的页面最小字节数为1KB,避免压缩过小的文件导致效果适得其反。
- gzip_buffers 4 32k; # 设置压缩缓冲区大小,分配4个32KB的内存用于存储gzip压缩结果,提高压缩性能。
- gzip_http_version 1.1; # 设置识别HTTP协议版本为1.1,适应大部分浏览器对GZIP解压的支持。
- gzip_comp_level 6; # 设置gzip压缩比例为6,平衡压缩速度和压缩效果,减轻CPU负担。
- gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript text/css application/xml; # 指定需要压缩的文件类型,提高压缩效果,默认值是 gzip_types text/html 。
- gzip_vary on; # 启用vary header支持,允许前端缓存服务器(如Squid)缓存经过gzip压缩的页面,提高缓存效率。
7、连接超时时间及其他优化
以下配置参数用于优化连接超时时间和提高服务器性能,同时保护服务器资源(如 CPU 和内存)。
- keepalive_timeout 60; # 设置长连接超时时间为60秒,保持客户端和服务器之间的连接,提高性能。
- tcp_nodelay on; # 启用TCP_NODELAY选项,降低延迟,提高响应速度。
- client_header_buffer_size 4k; # 设置客户端请求头缓冲区大小为4KB,确保请求头可以被正确处理。
- open_file_cache max=102400 inactive=20s; # 缓存打开文件的描述符,加速文件访问,减轻磁盘I/O压力。
- open_file_cache_valid 30s; # 确保文件的有效性和一致性,缓存的文件在30秒后被认为是无效的。
- open_file_cache_min_uses 1; # 提高缓存的使用率,避免不必要的缓存更新,在文件被认为无效之前,需要至少使用1次。
- client_header_timeout 15; # 设置读取客户端请求头的超时时间为15秒,避免长时间等待恶意或缓慢的请求,提高服务性能。
- client_body_timeout 15; # 设置读取客户端请求体的超时时间为15秒,避免长时间等待恶意或缓慢的请求,提高服务性能。
- reset_timedout_connection on; # 启用超时连接重置,当连接超时时,Nginx将关闭连接并释放资源,提高服务的可用性。
- send_timeout 15; # 设置发送响应数据给客户端的超时时间为15秒,避免因客户端无法接收数据而导致的服务阻塞,提高性能。
- server_tokens off; # 禁止Nginx在响应头中显示版本信息,提高安全性,防止攻击者利用已知的版本漏洞攻击。
- client_max_body_size 10m; # 设置客户端请求主体的最大大小为10MB,限制上传文件大小,保护服务器免受大文件上传导致的资源消耗。
8、代理超时设置(proxy)
以下配置参数用于设置 Nginx 代理服务器的超时时间和缓冲区大小:
- proxy_connect_timeout 90; # 后端服务器连接的超时时间,发起握手等候响应超时时间。
- proxy_send_timeout 90; # 后端服务器数据回传时间,即规定时间内后端服务器必须传完所有数据。
- proxy_read_timeout 90; # 连接成功后,等待后端服务器响应时间。此时间内后端服务器应处理请求并回应(实际上是进入后端服务器排队等待处理)。
- proxy_buffers 4 32k; # 设置缓存区的数量(4 个)和大小(32 KB)。从被代理的后端服务器获取的第一部分响应内容会放置到这里。默认情况下,一个缓存区的大小等于内存页面大小(通常为 4 KB 或 8 KB,取决于平台)。
- proxy_busy_buffers_size 64k; # 设置 Nginx 向客户端发送数据时所使用的缓冲区大小。通常为 proxy_buffers 单位大小的两倍。官方默认值为 8 KB 或 16 KB。
关于缓冲区buffer的工作原理,请注意以下几点:
所有的 proxy buffer 参数都是针对每个请求生效的。每个请求会根据配置参数获得自己的 buffer。proxy buffer 不是全局配置,而是每个请求的配置。
proxy_buffering 用于开启反向代理服务器响应数据的缓存。开启后,proxy_buffers 和 proxy_busy_buffers_size 参数才会生效。
无论 proxy_buffering 是否开启,proxy_buffer_size 都会生效。proxy_buffer_size 设置的缓冲区大小用于存储上游服务器响应的 header。
在 proxy_buffering 开启的情况下,Nginx 会尽可能地将上游服务器传输的所有数据读取到 buffer,直到 proxy_buffers 设置的所有 buffer 被写满或数据被完全读取。此时,Nginx 开始向客户端传输数据,会同时传输整个缓冲区序列。如果响应内容较大,Nginx 会接收并将其写入临时文件,大小由 proxy_max_temp_file_size 控制。当繁忙的缓冲区传输完毕后,Nginx 会继续从临时文件中读取数据,直到传输完成。
一旦 proxy_buffers 设置的 buffer 被写入,直到缓冲区内的数据完全传输完毕(传输到客户端),该缓冲区将一直处于繁忙状态,我们不能对该缓冲区执行任何其他操作
9、proxy_set_header
proxy_set_header 用于设置被代理服务器接收到的 header 信息。
语法:proxy_set_header field value;
field:需要更改的 header 字段,例如 “Host”
value:header 字段的值
如果不设置 proxy_set_header,则 “Host” 默认值为 proxy_pass 后面的域名或 IP(通常使用 IP)。
proxy_set_header X-Real-IP $remote_addr; 和 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 用于设置被代理端接收到的远程客户端 IP。如果不设置这两个参数,则 header 信息中不会透传远程真实客户端的 IP 地址。
- server {
- ...
-
- location /http/ {
- proxy_pass http://http_backend; # 代理转发到后端服务器
-
- proxy_http_version 1.1; # 使用 HTTP/1.1 协议
-
- proxy_set_header Connection ""; # 清除 "Connection" 头字段,避免 "Connection: keep-alive"
- proxy_set_header Host $http_host; # 添加 "Host" 头字段,使用原始请求的域名
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 将客户端和代理链路的 IP 添加到 "X-Forwarded-For" 头字段
-
- proxy_next_upstream error timeout http_500 http_502 http_503 http_504; # 当后端服务器返回指定错误码时,请求被转发到下一个服务器
- proxy_connect_timeout 60s; # 代理与后端服务器连接超时时间
- proxy_read_timeout 60s; # 代理等待后端服务器响应时间
- proxy_send_timeout 60s; # 后端服务器将数据发送回代理的超时时间
- }
-
- ...
- }
关于变量的说明:
$proxy_host:代表 proxy_pass 后面跟着的 host(域名或 IP)
$http_host:始终等于 HTTP 请求头中的 “Host” 字段
$host:等于 $http_host,但是小写且没有端口号(如果存在)
$proxy_add_x_forwarded_for:从客户端到后端服务经过的所有节点的 IP 地址汇总
$remote_addr:代表客户端的 IP 地址,由服务器根据客户端的 IP 指定
$remote_addr 只能获取到与服务器直连的上层请求 IP,但当通过 CDN 或其他代理访问时,后端服务器获取到的将是 CDN 或代理的 IP,而非真实用户 IP。此时,使用 “X-Forwarded-For” 可以记录从客户端真实 IP 到所有经过的代理节点的 IP,以获取用户真实 IP。设置 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 可以获取所有的代理 IP 和客户端 IP。
10、高效传输模式
- http {
- ...
-
- # 高效传输模式设置
- sendfile on; # 开启高效文件传输模式,使用操作系统提供的 sendfile() 函数进行零拷贝文件传输
- tcp_nopush on; # 在 sendfile 模式下生效,将响应头和正文的开始部分一起发送,减少网络报文段的数量,防止网络阻塞
- tcp_nodelay on; # 不论数据包大小,只要有数据包产生,就尽快传输,降低传输时延
-
- ...
- }
11、fastcgi调优
- http {
- ...
-
- # FastCGI 调优配置
- fastcgi_connect_timeout 600; # 连接到后端 FastCGI 服务器的超时时间
- fastcgi_send_timeout 600; # 向 FastCGI 服务器发送请求的超时时间
- fastcgi_read_timeout 600; # 接收 FastCGI 服务器响应的超时时间
-
- fastcgi_buffer_size 64k; # 读取 FastCGI 响应的第一部分所需的缓冲区大小
- fastcgi_buffers 4 64k; # 缓冲 FastCGI 响应所需的缓冲区数量和大小
- fastcgi_busy_buffers_size 128k; # 繁忙时的缓冲区大小,建议设置为 fastcgi_buffers 的两倍
- fastcgi_temp_file_write_size 128k; # 将数据块写入 fastcgi_temp_path 时的大小,默认值是 fastcgi_buffers 的两倍
-
- fastcgi_temp_path /usr/local/nginx1.22/nginx_tmp; # 缓存临时目录
-
- fastcgi_intercept_errors on; # 是否传递 4xx 和 5xx 错误信息到客户端,或让 Nginx 使用 error_page 处理错误信息
-
- fastcgi_cache_path /usr/local/nginx1.22/fastcgi_cache levels=1:2 keys_zone=cache_fastcgi:128m inactive=1d max_size=10g; # FastCGI 缓存目录、缓存空间名称、内存大小、失效时间和硬盘空间大小
-
- ...
-
- server {
- ...
-
- location ~ \.php$ {
- ...
- fastcgi_pass 127.0.0.1:9000; # 指定 FastCGI 服务器监听端口与地址
- fastcgi_cache cache_fastcgi; # 开启 FastCGI 缓存并为其指定一个名称
-
- fastcgi_cache_valid 200 302 1h; # 将 200 和 302 应答缓存一小时
- fastcgi_cache_valid 301 1d; # 将 301 应答缓存一天
- fastcgi_cache_valid any 1m; # 将其他应答缓存为 1 分钟
-
- fastcgi_cache_min_uses 1; # 相同 URL 请求多少次后将被缓存
- fastcgi_cache_key "http://$host$request_uri"; # 设置 Web 缓存的 Key 值,根据 host 和 request_uri 组合而成
- ...
- }
-
- ...
- }
-
- ...
- }
总结:
Nginx 的缓存功能主要包括:proxy_cache 和 fastcgi_cache。
proxy_cache 的作用是缓存后端服务器的内容,可能包括静态和动态内容。通过 proxy_cache 缓存,Nginx 可以减少与后端服务器之间的通信次数,节省传输时间和后端服务器的带宽。(12、proxy_cache会提到)
fastcgi_cache 的作用是缓存 FastCGI 生成的内容,通常用于缓存 PHP 生成的动态内容。通过 fastcgi_cache 缓存,Nginx 可以减少与 PHP 之间的通信次数,进一步减轻 PHP 和数据库(例如 MySQL)的压力。
总之,通过这两种缓存机制,Nginx 可以提高网站的响应速度,减轻服务器负载,并降低服务器资源消耗。
12、proxy_cache缓存
- http {
- # 定义代理缓存路径和参数
- proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m use_temp_path=off;
-
- server {
- listen 80;
- server_name example.com;
-
- location / {
- proxy_cache my_cache; # 启用代理缓存
-
- proxy_cache_valid 200 302 60m; # 设置缓存有效期:200 和 302 状态码的响应为 60 分钟
- proxy_cache_valid 404 1m; # 设置缓存有效期:404 状态码的响应为 1 分钟
-
- proxy_cache_key "$scheme$request_method$host$request_uri"; # 设置缓存键
-
- # 当缓存内容过期时,在更新缓存之前仍然使用旧的缓存内容
- proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504;
-
- proxy_pass http://your-backend-server; # 设置代理服务器
-
- # 设置请求头信息
- 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_http_version 1.1; # 使用 HTTP/1.1 协议
- proxy_set_header Connection ""; # 清除 "Connection" 头字段,避免 "Connection: keep-alive"
- proxy_buffering on; # 使用缓冲区,代理将后端返回的内容逐步发送给客户端
- proxy_buffer_size 4k; # 设置代理保存用户头信息的缓存区大小
- proxy_buffers 8 8k; # 设置代理缓冲区大小 (8 * 8 = 64k)
- }
- }
- }
补充知识:
除了 Nginx 代理缓存和浏览器缓存外,还有一些其他类型的缓存,例如:
CDN 缓存:内容分发网络(CDN)可以缓存静态资源,将其分发到全球各地的边缘节点,以加速资源加载。CDN 缓存的配置取决于您选择的 CDN 提供商。
内存缓存:某些应用程序框架和数据库提供了内存缓存机制,如 Memcached 和 Redis。这些缓存通常用于存储数据库查询结果、会话数据等,以减少数据库负担并提高应用性能。内存缓存的配置需要在应用程序代码或数据库层面进行。
全页缓存:某些内容管理系统(如 WordPress)支持全页缓存插件,可以将整个 HTML 页面缓存在服务器上,提高页面加载速度。这类缓存的配置通常在 CMS 中进行。
13、expires缓存调优
缓存主要针对于图片、CSS、JS 等更改较少的元素,特别是图片。由于图片占用较大的带宽,我们可以设置图片在浏览器本地缓存 365 天,而 CSS、JS 和 HTML 可以缓存 10 天左右。这样,虽然用户第一次访问网站时加载速度较慢,但从第二次开始,加载速度将会明显加快。在配置缓存时,我们需要列出需要缓存的扩展名。Expires 缓存配置在 server 块内。
- location ~* \.(ico|jpe?g|gif|png|jpeg|bmp|swf|flv)$
- {
- expires 365d; # 过期时间为 365 天
- #log_not_found off;
- access_log off;
- }
-
- location ~* \.(js|css)$
- {
- expires 10d;
- log_not_found off;
- access_log off;
- }
注:log_not_found off; 表示是否在 error_log 中记录不存在的错误,默认为记录。
Expires 是缓存到期的时间,这里指的是缓存到客户端的浏览器上的缓存。通过这种方式可以减轻服务器的负担,并减少网络传输,提高用户访问网站的速度。
14、访问限流(rate limiting)
我们构建网站的目的是让用户合法地访问它们。因此,必须采取一些措施来限制滥用访问的用户。滥用访问通常是指同一 IP 每秒向服务器发送大量请求。这可能是在同一时间内,来自世界各地多台机器上的爬虫机器人多次尝试爬取网站内容。
限速方法
限速可以通过多种方式实现:
1)下载速度限制
2)单位时间内请求数限制
3)基于客户端的并发连接限制
nginx限速模块
Nginx 官方版本提供了两个模块来分别限制 IP 的连接和并发:
limit_req_zone 用于限制单位时间内的请求数,即速率限制。采用漏桶算法(Leaky Bucket)。
limit_conn 用于限制同一时间的连接数,即并发限制。
应用场景
下载限速:限制下载速度和并发连接数,应用于下载服务器中,以保护带宽和服务器的 I/O 资源。
请求限速:限制单位时间内用户的访问请求,防止恶意攻击,保护服务器及资源安全。
限速原理
漏桶算法(Leaky Bucket)
漏桶算法的核心思想包括以下几点:
请求(水)从上方倒入水桶,从水桶下方流出(被处理)。
未能及时流出的请求(水)暂存在水桶中(缓冲),以固定速率流出。
当水桶满时,多余的请求(水)会溢出(丢弃)。
漏桶算法的核心在于缓存请求、匀速处理以及丢弃多余的请求。
令牌桶算法(Token Bucket)(上右图)
令牌桶算法与漏桶算法的不同之处在于:
令牌桶算法有一个令牌桶,用于存放令牌。
令牌桶算法还有一个队列,用于存放请求。
令牌桶算法的工作原理是,请求在获得足够的令牌后才能被处理。令牌以固定速率生成,因此令牌桶算法实现了对请求的限速处理。此外,令牌桶算法允许一定程度的突发流量,因为令牌是积累在桶中的,如果有足够的令牌,多个请求可以同时得到处理。
简要补充其他算法:
滑动窗口算法(Sliding Window):滑动窗口算法通过在一段时间窗口内统计请求量来实现限速。窗口在时间轴上滑动,当窗口内的请求量达到限制值时,新的请求会被拒绝或延迟处理。滑动窗口算法可以更灵活地处理突发流量。
漏桶 + 令牌桶组合:这种算法结合了漏桶和令牌桶的特点,通常用于处理具有复杂限速需求的场景。例如,可以在令牌桶的基础上增加一个漏桶,以实现更精细化的限速策略。
限速实现
1)单位时间内请求数限制
DDoS攻击者会发送大量的并发连接,占用服务器资源(包括连接数、带宽等),这会导致正常用户处于等待状态或无法访问服务器。
Nginx 提供了一个 ngx_http_limit_req_module 模块,可以有效降低 DDoS 攻击的风险,防止 DDoS 攻击,或防止上游服务器同时被太多请求淹没。
16,000 个 IP 地址的状态信息大约需要 1MB。
以下是基于 IP 对下载速率进行限制的示例,限制每秒处理 1 次请求,对突发超过 5 个以后的请求放入缓存区:
- http {
- limit_req_zone $binary_remote_addr zone=baism:1m rate=1r/s;
-
- server {
- location /abc {
- limit_req zone=baism burst=5 nodelay;
- }
- }
- }
参数解释:
limit_req_zone $binary_remote_addr zone=baism:10m rate=1r/s;
第一个参数:$binary_remote_addr ,表示通过 remote_addr 这个标识来做限制,binary_ 的目的是减小内存占用量,限制同一客户端 IP 地址。$binary_remote_addr 为 IPv4 时占用 4B,为 IPv6 时占用 16B。
第二个参数:zone=baism:10m,表示生成一个大小为 10M,名字为 baism 的内存区域,用来存储访问的频次信息。
第三个参数:rate=1r/s,表示允许相同标识的客户端的访问频次,这里限制的是每秒 1 次,还可以有例如 30r/m 的,即每 2 秒才能处理一个请求。
limit_req zone=baism burst=5 nodelay;
第一个参数:zone=baism ,设置使用哪个配置区域来做限制,与上面 limit_req_zone 里的 name 对应。
第二个参数:burst=5,这个配置的意思是设置一个大小为 5 的缓冲区,当有大量请求(爆发)过来时,超过了访问频次限制的请求可以先放到这个缓冲区内等待,但是这个等待区里的位置只有 5 个, 超过的请求会直接报 503 的错误然后返回。
第三个参数:nodelay,如果设置,会在瞬时提供处理 (burst+rate) 个请求的能力,请求超过 (burst+rate) 的时候就会直接返回 503, 永远不存在请求需要等待的情况(这里的 rate 的单位是:r/s)。如果没有设置,则所有请求会依次等待排队。
burst详解及实验:
设定一个场景,一个 Nginx 反向代理的后端服务单页请求数是 30 个。如果我们设置 rate 为 30r/s,那么这个 burst 设置就没有意义了。但是,如果我们不想把 rate 设置得这么大,比如 5r/s,同时还想完成一个页面 30 个请求的需求。这个时候 burst 就发挥作用了(前后端一体化的服务,其静态资源单次刷新后,再访问时只会刷新动态请求)。
假设我们一个页面有 30 个请求,后面每秒都会刷新一个页面,但是新页面由于静态资源已经刷新,只有 2-3 个请求。因此,我们把 burst 设置成 200 个(可以让用户连续强制刷新大约 8 次)。
当我们第一次访问时,由于 rate 是 5 个,只能处理 5 个,另外的 25 个会放到 burst 队列。虽然设置成 nodelay 会把这 25 个也处理了,但是队列中占用的 25 个位置需要 25/rate=5 秒来清除。如果第二次请求是 3 个,那么我们实时处理了 3 个请求,并且 rate 剩余 2 个可以清空 burst 队列,这个时候,队列就是占用了 23 个位置。最后,如果队列满了,那么就会出现 503 的请求。
验证效果:基本按上面的方式,连续每秒 30 个请求,即 F5 强制刷新,那么大约会在多少次后出现 503 的情况呢?答案大约是 8 次(200/25)。
burst = (每次请求数 - 请求限制数) * 允许强制刷新的次数
例子演示:
首先我们配置了limit_req_zone,rate=10r/m,即每六秒才处理一次请求,如下:
1.首先测试不加burst和不加nodelay的情况:
查看当前的tcp连接数
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
结果如下:
使用ab测试工具,发起10个并发请求:
ab -n 10 -c 10 url #linux 下测试
ab工具返回结果:
可以看到一共10个请求,9个请求都失败了。且0.002秒就完成了压测
接着查看当前的tcp连接数:
可以观察到此时服务端的TIME_WAIT 比上图多了10,这意味着是服务端主动要求断开了所有TCP连接
接着再查看 /var/log/nginx/access.log,印证了只有一个请求成功了,其它就是都直接返回了503,即服务器拒绝了请求。
2. 只加burst和不加nodelay的情况:
查看当前的tcp连接数
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
结果如下:
使用ab测试工具,发起10个并发请求:
ab -n 10 -c 10 url #linux测试
可以看到测试经过30s才结束
ab工具返回结果:
压测中一共10个请求,有4个请求失败了,直接返回503
查看当前的tcp连接数:
上图是ab测试第一秒时的截图,对比第一次截图,TIME_WAIT=19
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。