一般Nginx作为整个应用的入口,即做静态服务器,也做负载均衡、反向代理;同时也因为位置靠前,还可以通过Nginx对于访问的IP、并发数进行相应的限制。在Java web应用性能分析中,Nginx是重要环节,Nginx的性能也影响整个应用。
Nginx原理
下面我们看一下Nginx的整体架构
Nginx 里有一个 master 进程和多个 worker 进程。master 进程并不处理网络请求,主要负责调度工作进程:加载配置、启动工作进程及非停升级。worker 进程负责处理网络请求与响应。
master进程主要用来管理worker进程,具体包括如下4个主要功能:
- 接收来自外界的信号。
- 向各worker进程发送信号。
- 监控woker进程的运行状态。
- 当woker进程退出后(异常情况下),会自动重新启动新的woker进程。
woker进程主要用来处理基本的网络事件:
- 多个worker进程之间是对等且相互独立的,他们同等竞争来自客户端的请求。
- 一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。
- worker进程的个数是可以设置的,一般我们会设置与机器cpu核数一致。同时,nginx为了更好的利用多核特性,具有cpu绑定选项,我们可以将某一个进程绑定在某一个核上,这样就不会因为进程的切换带来cache的失效。
Ngnix 是如何实现高性能的?
1.事件驱动模型
基于异步及非阻塞的事件驱动模型,可以说是 Nginx 得以获得高并发、高性能的关键因素。这一点上和 Netty 类似,底层都是使用的 BSD kqueue、Linux epoll 及 Solaris event ports。
2.多进程机制
使用多进程的好处有两点:
- 进程之间不共享资源,不需要加锁,减少了使用锁对性能造成的影响,同时降低编程的复杂度,降低开发成本。
- 采用独立的进程,可以让进程互相之间不会影响,如果一个进程发生异常退出时,其它进程正常工作,master 进程则很快启动新的 worker 进程,确保服务不会中断,从而将风险降到最低。
3.内存池
为了避免出现内存碎片,减少向操作系统申请内存的次数、降低各个模块的开发复杂度,Nginx 设计了简单的内存池,它的作用主要是把多次向系统申请内存的操作整合成一次,这大大减少了 CPU 资源的消耗,同时减少了内存碎片。
4.模块化设计
高度模块化的设计是 Nginx 的架构基础。Openresty 就是在 Nginx 上引入了 lua 等第三方模块,使得扩展更加方便了。
Nginx调优
在这里我们Nginx调优主要包括:性能调优、安全调优、以及资源管理。
请求响应模型过程中的TCP网络消耗
1.worker工作进程调整:绑定CPU
查看CPU核心数
user nginx nginx; # 启动Nginx⼯作进程的⽤⼾和组
worker_processes [number | auto]; # 启动Nginx⼯作进程的数量
worker_cpu_affinity 00000001 00000010 00000100 00001000;
# 将Nginx⼯作进程绑定到指定的CPU核⼼,默认Nginx是不进⾏进程绑定的,
# 绑定并不是意味着当前nginx进程独 占以⼀核⼼CPU,但是可以保证此进程不会运⾏在其他核⼼上,
# 这就极⼤减少了nginx的⼯作进程在不同的cpu核 ⼼上的来回跳转,减少了CPU对进程的资源分配与回收以及内存管理等,
#因此可以有效的提升nginx服务器的性 能。 此处CPU有四颗核心。也可写成:
#worker_cpu_affinity 0001 0010 0100 1000;
#cpu的亲和能偶使nginx对于不同的work工作进程绑定到不同的cpu上面去。就能够减少在work间不#断切换cpu,把进程通常不会在处理器之间频繁迁移,进程迁移的频率小,来减少性能损耗。
#每个 worker 的线程可以把一个 cpu 的性能发挥到极致。所以 worker 数和服务器的 cpu 数相#等是最为适宜的。设少了会浪费 cpu,设多了会造成 cpu 频繁切换上下文带来的损耗
- 一个master Process管理多个worker process,也就是说Nginx采用的是多进程结构,而不是多线程结构
- 当client发出请求(任务)时,master Process会通知管理的worker process
- worker process开始争抢任务,争抢到的worker process会开启连接,完成任务
- 每个worker都是一个独立的进程,每个进程里只有一个主线程
- Nginx采用了IO多路复用机制(需要在Linux环境),使用IO多路复用机制,是Nginx在使用为数不多的worker process就可以实现高并发的关键
2.事件处理模型和worker工作进程数
work_connections表示每个worker(子进程)可以创建多少个连接,
默认:work_connections:1024
最大:work_connections:65535
同时要根据系统的最大打开文件数来调整
系统的最大打开文件数>= worker_connections*worker_process
根据系统的最大打开文件数来调整,worker_connections 进程连接数量要小于等于系统的最大打开文件数
worker_connections 进程连接数量真实数量= worker_connections * worker_process
查看系统的最大打开文件数:
ulimit -a | grep "open files"
其他系统内核调整
# 配置文件/etc/sysctl.conf
sysctl -w net.ipv4.tcp_syncookies=1 # 防止一个套接字在有过多试图连接到时引起过载
sysctl -w net.core.somaxconn=1024 # 默认128,连接队列
sysctl -w net.ipv4.tcp_fin_timeout=10 # timewait 的超时时间
sysctl -w net.ipv4.tcp_tw_reuse=1 # os 直接使用 timewait的连接
sysctl -w net.ipv4.tcp_tw_recycle=0 # 回收禁用
# /etc/security/limits.conf
* hard nofile 204800
* soft nofile 204800
* soft core unlimited
* soft stack 204800
events {
worker_connections 1024; # 每个工作进程的最大连接数,默认值512
multi_accept on; # 尽可能多地接受新的连接
use epoll; # 选择合适的事件模型,例如epoll或kqueue
}
Nginx 事件模型对比
Nginx 的事件模型主要有两种:select、poll、epoll、kqueue、devpoll、eventport。
select:select 模型是最早期的,它在大多数操作系统上都有相同的表现。它的主要缺点是,每次调用 select 时,都需要传递所有的文件描述符集合,这使得 select 的系统调用在大量并发连接的情况下效率非常低。
poll:poll 模型在实现上是比 select 更先进的模型,它没有文件描述符的数量限制。但是,它的性能仍然不如 epoll。
epoll:epoll 是 Linux 下的一个高效的事件通知模型,它只有在有事件通知时才会唤醒进程,而且它是边缘触发,而 select 和 poll 是水平触发。
kqueue:kqueue 是 BSD 系统(包括 macOS)下的一个高效事件通知模型,它也是边缘触发。
devpoll:devpoll 是 Solaris 下的一个事件通知模型。
eventport:eventport 是 Solaris 和部分其他操作系统下的一个事件通知模型,它是边缘触发,而且是线程安全的。
在 Nginx 中,可以通过 --with-select_module、--without-select_module、--with-poll_module、--without-poll_module、--with-epoll_module、--with-kqueue_module、--with-devpoll_module 和 --with-eventport_module 这些编译参数来启用或禁用对应的事件模型。
例如,如果你想要在 Nginx 中使用 epoll 事件模型,你可以在编译 Nginx 时添加 --with-epoll_module 参数。
./configure --with-epoll_module
make
make install
在实际的生产环境中,通常会根据操作系统的不同选择不同的事件模型。例如,在 Linux 系统上,通常会选择 epoll,而在 Solaris 系统上,则会选择 devpoll 或 eventport。
注意:在 Nginx 中,默认情况下会自动选择最适合当前操作系统的事件模型。
3.开启gzip压缩
启用Nginx的gzip模块可以压缩响应内容,减少网络传输数据量,提高性能。可以使用gzip on指令启用压缩功能。
gzip on;
gzip_min_length 1k;
gzip_comp_level 9;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
释义如下:
这是用来配置 gzip 压缩的。gzip 是一种用于压缩文件的压缩程序,在 Web 开发中被广泛应用,主要用于压缩 Web 应用的静态资源以减少传输流量,提高网页的加载速度。
gzip on;:启用 gzip 压缩。
gzip_min_length 1k;:指定压缩文件的最小长度,只有文件大小超过 1KB 时才启用压缩。
gzip_comp_level 9;:指定压缩级别。级别越高,压缩效率越高,但会占用更多的 CPU 资源和时间。一般建议将级别设置为 6-9 之间。(这里根据你的服务器来定)
gzip_types:指定需要压缩的文件类型。在这里,配置文件将 text、application 和 image 类型的文件压缩。一般来说,压缩的文件类型应该是纯文本格式或可压缩的二进制文件。
gzip_vary on;:使用 Vary 头来指示代理服务器或浏览器缓存已压缩的版本。这样,更高效的压缩格式可以分别缓存,并在请求时正确地使用。
gzip_disable "MSIE [1-6].";:禁用 gzip 压缩的浏览器,例如早期版本的 Internet Explorer。这些浏览器对于压缩格式的支持很差,因此禁用压缩可以避免出现问题。(这一步避免版本低浏览器访问网页出现问题)
4.配置静态文件缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
}
5.限制连接速率
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
server {
...
location / {
limit_req zone=mylimit;
...
}
}
6.保持连接时间
keepalive_timeout 65;
keepalive就是建立一下长连接,这个timeout就是,当浏览器建立一个连接之后,这个连接最长能存在多少事件不给他关闭,这个65也不是说建立65s连接后就直接关闭,而是一个活跃事件,就是第一次请求和下一次请求都会刷新这个65s,再65s内没有再请求数据才会真的关闭这个连接,所以这个事件也不应该太长,60左右也差不多了。
7.配置超时时间
client_body_timeout 12;
send_timeout 10;
- keepalived_timeout :客户端连接保持会话超时时间,超过这个时间,服务器断开这个链接。
- tcp_nodelay:也是防止网络阻塞,不过要包涵在keepalived参数才有效。
- client_header_buffer_size 4k:客户端请求头部的缓冲区大小,这个可以根据你的系统分页大小来设置,一般一个请求头的大小不会超过 1k,不过由于一般系统分页都要大于1k,所以这里设置为分页大小。分页大小可以用命令getconf PAGESIZE取得。
- open_file_cache max=102400 inactive=20s :这个将为打开文件指定缓存,默认是没有启用的,max指定缓存数量,建议和打开文件数一致,inactive 是指经过多长时间文件没被请求后删除缓存。
- open_file_cache_valid 30s:这个是指多长时间检查一次缓存的有效信息。
- open_file_cache_min_uses 1 :open_file_cache指令中的inactive 参数时间内文件的最少使用次数,如果超过这个数字,文件描述符一直是在缓存中打开的,如上例,如果有一个文件在inactive 时间内一次没被使用,它将被移除。
- client_header_timeout :设置请求头的超时时间。我们也可以把这个设置低些,如果超过这个时间没有发送任何数据,nginx将返回request time out的错误。
- client_body_timeout设置请求体的超时时间。我们也可以把这个设置低些,超过这个时间没有发送任何数据,和上面一样的错误提示。
- reset_timeout_connection :告诉nginx关闭不响应的客户端连接。这将会释放那个客户端所占有的内存空间。
- send_timeout :响应客户端超时时间,这个超时时间仅限于两个活动之间的时间,如果超过这个时间,客户端没有任何活动,nginx关闭连接。
- server_tokens :并不会让nginx执行的速度更快,但它可以关闭在错误页面中的nginx版本数字,这样对于安全性是有好处的。
- client_max_body_size:上传文件大小限制。
proxy_connect_timeout 90; #后端服务器连接的超时时间,发起握手等候响应超时时间
proxy_send_timeout 90; #后端服务器数据回传时间,就是在规定时间内后端服务器必须传完所有的数据
proxy_read_timeout 90; #连接成功后,等候后端服务器响应时间,其实已经进入后端的排队之中等候处理(也可以说是后端服务器处理请求的时间,页面等待服务器响应时间)
proxy_buffers 4 32k; #4是数量 32k是大小 该指令设置缓存区的大小和数量,从被代理的后端服务器取得的第一部分响应内容,会放置到这里,默认情况下,一个缓存区的大小等于内存页面大小,可能是4k也可能是8k取决于平台
proxy_busy_buffers_size 64k; #nginx在收到服务器数据后,会分配一部分缓冲区来用于向客户端发送数据,这个缓存区大小由proxy_busy_buffers_size决定的。大小通常是proxy_buffers单位大小的两倍,官网默认是8k/16k。
keepalive_timeout 60;
keepalive_requests 100;
tcp_nodelay on;
client_header_buffer_size 4k;
open_file_cache max=102400 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 1;
client_header_timeout 15;
client_body_timeout 15;
reset_timedout_connection on;
send_timeout 15;
server_tokens off;
client_max_body_size 10m;
这两个配置参数是用来设置服务器的 keepalive 功能的。
keepalive_timeout: 这个参数指定了一个已经建立的连接在没有活动(无数据传输)时保持的时间长度。对于每个连接,如果超过 keepalive_timeout 时间没有数据传输,则服务器会关闭该连接。默认值通常为 75 秒。较小的值可以确保连接及时释放,但会增加连接关闭和重新建立的频率;较大的值可以减少连接关闭和重新建立的频率,但可能会导致长时间的闲置连接占用服务器资源。
keepalive_requests: 这个参数定义了一个 keepalive 连接上最多能够处理的请求次数。当一个 keepalive 连接处理了 keepalive_requests 次请求之后,服务器会关闭该连接。默认值通常为 100。较大的值可以减少连接的关闭和重新建立,但在某些情况下可能会占用过多的服务器资源。
8.启用TCP缓冲
通过调整tcp_nodelay和tcp_nopush指令可以启用TCP缓冲,从而提高性能。这些指令可以确保Nginx等待足够的数据量再发送给客户端,从而减少TCP连接的数量,提高传输效率。
9.配置缓存
启用Nginx的缓存可以显著提高性能。可以使用proxy_cache_path指令在Nginx中配置缓存目录。启用缓存后,Nginx会在第一次请求时将响应存储在缓存中,以便在后续请求中快速提供响应。
往nginx.conf 的 http内容段落中加入
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m;
释义如下:
/path/to/cache 是指定的缓存存储路径。
levels=1:2 指定了文件系统中缓存目录的层级结构,这里是一级目录和两级子目录。
keys_zone=my_cache:10m 定义了一个名为 my_cache 的缓存区域,大小为 10 兆字节。
max_size=10g 指定了缓存最大可使用的空间大小为 10 GB。
inactive=60m 表示缓存文件在60m时间内没有被访问时,会被视为不活动,并有可能被清理掉。
10.开启高效传输模式
http {
include mime.types;
default_type application/octet-stream;
……
sendfile on;
tcp_nopush on;
……
}
- Include mime.types :媒体类型,include 只是一个在当前文件中包含另一个文件内容的指令。
- default_type application/octet-stream :默认媒体类型足够。
- sendfile on:开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成off。
- tcp_nopush on:必须在sendfile开启模式才有效,防止网路阻塞,积极的减少网络报文段的数量(将响应头和正文的开始部分一起发送,而不一个接一个的发送。)
11.启用HTTPS
启用Nginx的HTTP2模块可以提高性能,因为HTTP2使用多路复用技术,可以同时发送多个请求和响应,从而提高网络传输效率。可以使用http2 on指令启用HTTP2。
12.调整缓冲区
可以通过调整Nginx的缓冲区大小来提高性能。可以使用client_body_buffer_size、client_header_buffer_size和large_client_header_buffers等指令调整缓冲区大小。
总的来说,Nginx性能调优需要结合服务器的硬件配置、网络环境和预期的并发连接数等因素进行综合考虑。
13.设置完整的日志格式和路径
在后期问题排查过程中,配置完整的日志,可以快速协助定位问题。
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
14.设置Nginx监控
Nginx 提供了一个状态页面,可以用来监控 Nginx 的运行状态。为了使用这个状态页面,你需要在 Nginx 配置中启用 stub_status
模块。
stub_status模块参数可以用来查看Nginx的一些状态信息如连接数、请求数统计等,需要安装好相应的模块stub_status才支持配置,安装时需要指定–with-http_stub_status_module,
比如:./configure --prefix=/usr/local/nginx --with-http_stub_status_module
以下是一个配置示例,它在 server 块中添加了一个 location 用于 Nginx 状态页面:
server {
listen 80;
server_name localhost;
location /nginx_status {
stub_status on; # 开启状态页面
access_log off; # 不记录访问nginx状态的日志
allow 127.0.0.1; # 只允许本地访问
deny all; # 拒绝其他IP访问
}
}