Nginx花了好多篇文章介绍了,今天谈谈它的优化。我们从优化考虑的方面,压力测试工具ab,具体的优化点三个方面去介绍,话不多说,直接开始!
1、了解系统结构、系统层次结构和瓶颈,结构方面,了解每个服务最大支持多少并发,支持多少的qps(每秒查询率);系统层次结构方面,我们要考虑Nginx是做代理、动静分离、还是直接服务用户;瓶颈方面,可以通过top查看系统的CPU负载、内存使用率、总的运行进程,也可以通过日志分析请求情况。也可以通过压力测试工具,业务低谷期进行压力测试,了解这套系统能承担多少的请求和并发。
2、了解业务模式,我们的性能优化是为业务提供服务的,我们要了解业务类型,例如抢购类的业务,平时没什么流量,到了抢购时间流量就会激增。
3、还需要考虑性能和安全,不能只注重一点忽略了另一点,例如我们在设计防火墙功能时候,如果检测的过于严密,就会给性能带来影响,如果对性能完全追求,不顾服务的安全,也容易造成安全隐患,所以我们需要权衡好对应的点
我们可以从OSI模型去考虑优化方向
硬件:代理(CPU)、静态(磁盘IO)、动态(cpu、内存)
网络:带宽、丢包、延迟
系统:文件描述符(文件句柄数)
应用:服务与服务保持长连接http1.1
服务:静态资源服务优化
在业务量没有增长之前,我们要做好响应准备,以防患业务量突增带来的接口压力,所以对于接口压力测试就显得非常重要,我们可以用测试工具,检测当前系统情况,看是否能满足对应压力的需求。
1、安装ab压力测试工具
[root@Web01 ~]# yum install -y httpd-tools
2、压力工具使用方式
-n 要执行的请求数
-c 请求的并发数
-k 是否开启长连接
[root@Web01 ~]# ab -n 200 -c 2 http://blog.koten.com/
3、配置Nginx静态网站与tomcat动态网站环境
#静态网站配置
[root@Web02 ~]# cat /etc/nginx/conf.d/test.conf
server {
listen 80;
server_name test.koten.com;
location / {
root /code;
try_files $uri $uri/ @java;
index index.jsp index.html;
}
location @java {
proxy_pass http://172.16.1.8:8080;
}
}
[root@Web02 ~]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@Web02 ~]# systemctl restart nginx
[root@Web02 ~]# echo 'nginx ab' > /code/ab.html
#动态网站配置
[root@Web02 ~]# wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.73/bin/apache-tomcat-9.0.73.tar.gz --no-check-certificate
[root@Web02 ~]# tar xf apache-tomcat-9.0.73.tar.gz
[root@Web02 ~]# cd /usr/share/tomcat/webapps/ROOT
[root@Web02 ROOT]# echo "test_tomcat" > tomcat.html
#静态资源压力测试
[root@Web01 ~]# ab -n 10000 -c 200 http://test.koten.com/ab.html
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking test.koten.com (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests
Server Software: nginx/1.22.1
Server Hostname: test.koten.com
Server Port: 80
Document Path: /ab.html
Document Length: 9 bytes
Concurrency Level: 200
Time taken for tests: 3.536 seconds
Complete requests: 10000
Failed requests: 0
Write errors: 0
Total transferred: 2380000 bytes
HTML transferred: 90000 bytes
Requests per second: 2827.96 [#/sec] (mean)
Time per request: 70.722 [ms] (mean)
Time per request: 0.354 [ms] (mean, across all concurrent requests)
Transfer rate: 657.28 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 18 130.3 0 3009
Processing: 12 48 41.2 45 1648
Waiting: 1 48 41.2 45 1648
Total: 31 66 148.7 45 3052
Percentage of the requests served within a certain time (ms)
50% 45
66% 46
75% 46
80% 47
90% 50
95% 56
98% 245
99% 1051
100% 3052 (longest request)
#动态资源压力测试
[root@Web01 ~]# ab -n 10000 -c 200 http://test.koten.com/tomcat.html
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking test.koten.com (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests
Server Software: nginx/1.22.1 #版本号
Server Hostname: test.koten.com #URL主机名
Server Port: 80 #请求端口
Document Path: /tomcat.html #请求路径
Document Length: 12 bytes #HTTP相应数据的正文长度
Concurrency Level: 200 #并发用户数,是我们设置的参数
Time taken for tests: 12.337 seconds #所有请求被处理完成所花费的时间
Complete requests: 10000 #总请求数量,我们设置的参数
Failed requests: 0 #失败的请求数量
Write errors: 0
Total transferred: 2500000 bytes #所有请求的相应数据长度总和,包括每个HTTP响应数据的头部信息和正文数据的长度
HTML transferred: 120000 bytes #所有请求的响应数据中正文数据的总和
Requests per second: 810.59 [#/sec] (mean) #吞吐量,计算公式是总请求数除以处理完成这些请求数所花费的时间
Time per request: 246.733 [ms] (mean) #用户平均请求等待时间,计算公式是处理完成所有请求数所花费的时间除以(总请求数除以并发用户数)
Time per request: 1.234 [ms] (mean, across all concurrent requests) #服务器平均请求等待时间,计算公式是吞吐率的倒数,也可以通过用户平均请求等待时间除以并发用户数
Transfer rate: 197.90 [Kbytes/sec] received #表示这些请求在单位时间内从服务器获取的数据长度,计算公式是所有请求的相应数据长度总和除以所有请求被处理完成所花费的时间,这个统计可以很好说明服务器的处理能力达到极限时,其出口宽带的需求量
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 9 86.5 1 1007
Processing: 32 225 400.9 103 3555
Waiting: 32 225 400.8 103 3555
Total: 33 234 413.7 104 3567
Percentage of the requests served within a certain time (ms)
50% 104
66% 132
75% 161
80% 191
90% 317
95% 1100
98% 1317
99% 2083
100% 3567 (longest request)
1、网络
1)网络的流量;2)网络是否丢包;3)这些会影响http的请求与调用
2、系统
1)硬件有没有磁盘损坏,磁盘速率是否稳定;2)系统的负载、内存、系统稳定性
3、服务
1)连接优化,请求优化;2)根据业务形态做对应的服务设置
4、程序
1)接口性能;2)处理速度;3)程序执行效率
5、数据库
每个服务之间都或多或少有一些关联,我们需要将整个架构分层去看,找到对应系统或服务的短板,然后进行优化
Linux一切皆文件,而文件句柄是文件的索引,文件句柄随着我们的进程调用频繁增加,系统默认的文件句柄有限制,不能让一个进程无限调用
[root@Web01 ~]# vim /etc/security/limits.conf #文件句柄限制配置文件
1、系统全局性修改。
# * 代表所有用户
* soft nofile 25535
* hard nofile 25535
2.用户局部性修改
#针对root用户,soft仅提醒,hard限制,nofile打开最大文件数
root soft nofile 65535
root hard nofile 65535
3.进程局部性修改
#针对nginx进程,nginx自带配置
worker_rlimit_nofile 30000
通过调整内核参数,让time_wait状态重用(端口重用)
[root@web01 ROOT]# vim /etc/sysctl.conf
net.ipv4.tcp_tw_reuse = 1 # 开启端口复用
net.ipv4.tcp_timestamps = 0 # 禁用时间戳
[root@web01 ROOT]# sysctl -p #可以查看我们添加的内核参数
[root@web01 ROOT]# sysctl -a #可以查看所有内核参数
在高并发短连接的TCP服务器上,当服务器处理完请求后立刻主动正常关闭连接。这个场景下会出现大量socket处于TIME_WAIT状态。如果客户端的并发量持续很高,此时部分客户端就会显示连接不上。 我来解释下这个场景。主动正常关闭TCP连接,都会出现TIMEWAIT。
高并发短连接有两方面需要注意:
1、高并发可以让服务器短时间内占用大量端口,端口范围0-65535除去系统和其他服务并不多
2、在这个场景中,短连接表示业务处理+传输数据的时间 远远小于TIMEWAIT超时的时间的连接
这个情况下,有可能http短连接话一秒处理数据,却要花几分钟停留在TIMEWAIT状态,而这几分钟一直占用着端口不工作,所以导致了资源浪费,在实际业务中,一般长连接对应的业务的并发量不会很高。
Nginx作为代理服务,负责转发用户请求,在转发过程中建议开启HTTP长连接,用于减少握手次数,降低服务器损耗。
upstream http_backend {
server 127.0.0.1:8080;
keepalive 16; #长连接
}
server {
...
location /http/ {
proxy_pass http://http_backend;
proxy_http_version 1.1; #对于http协议应该指定为1.1
proxy_set_header Connection ""; #清除“connection”头字段
proxy_next_upstream error timeout http_500 http_502 http_503 http_504; #平滑过渡
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwared-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 30s; # 代理连接web超时时间
proxy_read_timeout 60s; # 代理等待web响应超时时间
proxy_send_timeout 60s; # web回传数据至代理超时时间
proxy_buffering on; # 开启代理缓冲区,web回传数据至缓冲区,代理边收边传返回给客户端
proxy_buffer_size 32k; # 代理接收web响应的头信息的缓冲区大小
proxy_buffers 4 128k; # 缓冲代理接收单个长连接内包含的web响应的数量和大小
...
}
}
fastcgi服务器需要设置fastcgi_keep_conn以便保持长连接
upstream fastcgi_backend {
server 127.0.0.1:9000;
keepalive 8;
}
server {
...
location /fastcgi/ {
fastcgi_pass fastcgi_backend;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_keep_conn on;
fastcgi_connect_timeout 60s;
include fastcgi_params;
...
}
}
注意:scgi和uwsgi协议没有保持连接的概念,但proxy、fastcgi、uwsgi协议都有cache缓存的功能,开启后可加速网站访问的效率。
设置通过keepalive连接提供的最大请求数,在发出最大请求数后,将关闭连接
Syntax: keepalive_requests number;
Default: keepalive_requests 100;
Context: upstream
#该指令出现在1.15.3版中
保持的连接被自动关闭之前的时间限制(以秒为单位)
Syntax: keepalive_timeout timeout;
Default: keepalive_timeout 60s;
Context: upstream
#该指令出现在1.15.3版中
Nginx作为静态资源Web服务器,用于静态资源处理,传输非常的高效
静态资源指的是非Web服务器端运行处理而生成的文件
静态资源类型 | 种类 |
浏览器渲染 | HTML、CSS、JS |
图片文件 | JPEG、GIF、PNG |
视频文件 | FLV、MP4、AVI |
其他文件 | TXT、DOC、PDF |
浏览器缓存设置用于提高网站性能,像新闻网站,图片一旦发布,改动的可能是非常小的,所以我们希望用户访问一次后,图片缓存在用户的浏览器上。 浏览器是有自己的缓存机制,他是基于HTTP协议缓存机制来实现的,在HTTP协议中有很多头信息,那么实现浏览器的缓存就需要依赖特殊的头信息来与服务器进行特殊的验证,如Expires(http/1.0);Cache-control(http/1.1)。
浏览器缓存过期校验机制
浏览器发送请求,检测浏览器中有缓存,检测是否过期,没有过期就从缓存中直接读取,返回给浏览器;
过期了检查有无Etag,没有的话检查有无last-Modified,没有的话向web服务器发送请求,Web服务器请求后端,缓存协商再返回给浏览器,有last-Modified则向Web服务器请求if-Modified-Since,由服务器决策,返回200或304,200则请求后端,进行缓存协商,304则直接返回给浏览器;
如果有Etag,则向Web服务器请求If-None-Match,由服务器决策,返回200或304,200则请求后端,进行缓存协商,304则直接返回给浏览器。
名词解释:
Last-Modified:服务器上文件的最后修改时间
Etag:文件标识
Expires:本地缓存目录中,文件过期的时间(由服务器指定具体的时间)
Cache-control:本地缓存目录中,文件过期的时间(由服务器指定过期的间隔时间,由于浏览器根据间隔生成具体的时间)
配置静态资源缓存expires
#作用:添加Cache-Control Expires头
Syntax: expires [modified] time;
epoch | max | off;
Default: expires off;
Context: http, server, location, if in location
server {
listen 80;
server_name tets.koten.com;
location ~ .*\.(jpg|gif|png)$ {
expires 7d;
}
location ~ .*\.(js|css)$ {
expires 30d;
}
}
#取消js、css、html等静态文件缓存(如果代码没有上线,希望静态文件不被缓存)
location ~ .*\.(js|css|html)$ {
add_header Cache-Control no-store;
add_header Pragma no-cache;
}
Syntax: sendfile on | off;
Default: sendfile off;
Context: http, server, location, if in location
#将多个包一次发送,大文件推荐打开,需要开启sendfile
Syntax: tcp_nopush on | off;
Default: tcp_nopush off;
Context: http, server, location
#来个包发个包不等待,适合小文件,需要开启keepalive
Syntax: tcp_nodelay on | off;
Default: tcpnodelay off;
Context: http, server, location
Nginx将响应报文发送至客户端之前启用压缩功能,然后进行传输,这能够有效地节约带宽,并提高响应至客户端的速度。
#开启gzip传输压缩,传输前压缩,传输后解压
Syntax: gzip on | off;
Default: gzip off;
Context: http, server, location, if in location
#gzip指定压缩文件
Syntax: gzip_types mime-type ...;
Default: gzip_types text/html;
Context: http, server, location
#gzip压缩比率,加快传输,但压缩本身比较耗费服务器性能
Syntax: gzip_comp_level level;
Default:gzip_comp_level level 1;
Context: http, server, location
#gzip压缩协议版本,压缩使用在http哪个协议,主流选择1.1版本
Syntax: gzip_http_version 1.0 | 1.1;
Default:gzip_http_version 1.1;
Context: http, server, location
#静态文件压缩案例
[root@LB01 conf.d]# cat try.conf
server {
listen 80;
server_name test.koten.com;
location ~ .*\.(jpg|png|gif) {
root /code/images;
#gzip on;
#gzip_types image/jpeg image/gif image/png;
#gzip_comp_level 2;
#gzip_http_version 1.1;
}
location ~ .*\.(txt|xml|html|json|js|css)$ {
gzip on;
gzip_http_version 1.1;
gzip_comp_level 1;
gzip_types text/plain application/json application/x-javascript application/css application/xml text/javascript;
}
}
#压缩前后观察对比,可以明显看到传输的Size大小变化
防盗链,指的是防止资源被其他网站恶意盗用。
基础防盗链思路:主要是针对客户端请求过程中所携带的一些Header信息来验证请求的合法性,比如客户端在请求的过程中都会携带referer信息。优点是规则简单,配置和使用都很方便,缺点是防盗链所依赖的Referer验证信息是可以伪造的,所以通过referer信息防盗链并非100%可靠。
Syntax: valid_referers none | blocked | server_name | string ...;
Default: -;
Context: server, location
#none: referer来源头部为空的情况
#blocked: referer来源头部不为空,这些都不以http://或者https://开头
#server_name: 来源头部信息包含当前域名,可以正则匹配
1、在Web1上准备html文件,准备偷取Web02的图片
fangdao_test
2、访问页面可以看到
3、在服务器上配置允许盗的站点
location ~ .*\.(jpg|png|gif) {
root /var/www/wordpress/wp-content/extra/;
valid_referers none blocked *.koten.com server_name ~\.google\. ~\.baidu\.;
if ( $invalid_referer ) {
return 403;
}
}
#所有来自*.koten.com都可以访问到当前站点的图片,如果来源域名不在这个列表中,那么$invalid_referer等于1,在if语句中返回一个403个客户,这样用户便会看到一个403的页面
4、配置返回图片
location ~ .*\.(jpg|png|gif) {
root /var/www/wordpress/wp-content/extra/;
valid_referers none blocked *.koten.com;
if ( $invalid_referer ) {
rewrite ^(.*)$ /Picture/daolian1.gif break;
}
}
5、这种防护并不能百分百保证资源不被盗,因为我们可以通过命令来修改来源的refer信息
#伪造协议头访问
[root@web01 code]# curl -e "https://www.baidu.com" -I http://test.koten.com/Picture/daolian.jpg
HTTP/1.1 403 Forbidden
Server: nginx
Date: Thu, 10 Oct 2019 09:01:05 GMT
Content-Type: text/html; charset=utf-8,gbk
Content-Length: 162
Connection: keep-alive
#伪造协议头访问
[root@web01 code]# curl -e "https://www.koten.com" -I http://test.koten.com/Picture/daolian.jpg
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 10 Oct 2019 09:01:35 GMT
Content-Type: image/jpeg
Content-Length: 556417
Last-Modified: Thu, 10 Oct 2019 07:14:19 GMT
Connection: keep-alive
ETag: "5d9eda4b-87d81"
Accept-Ranges: bytes
跨域访问是当我们通过浏览器访问a网站时,同时会利用到ajax或其他方式,同时也请求b网站,这样的话就出现了请求一个页面,使用了两个域名,这种方式对浏览器来说默认是禁止的。因为浏览器会读取Access-Control-Allow-Origin的头信息,如果服务端允许,则浏览器不会进行拦截。
#编辑Nginx配置文件
[root@web02 code]# vim /etc/nginx/conf.d/s.conf
server {
listen 80;
server_name test.koten.com;
location / {
root /code;
index index.html;
}
}
#编辑html文件
[root@Nginx ~]# cat /code/test.html
测试ajax和跨域访问
跨域访问测试
#被关联网站配置
server {
listen 80;
server_name web.koten.com;
root /code;
index index.html;
charset utf-8;
location ~ .*\.(html|htm)$ {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS;
}
}
CPU亲和(affinity)减少进程之间不断频繁切换,减少性能损耗,其实现原理是建CPU核心和Nginx工作进程绑定方式,把每个worker进程固定到对应的cpu上执行,减少切换CPU的cache miss,获得更好的性能。
#查看当前CPU物理状态
[root@Web01 ~]$ lscpu | grep "CPU(s)"
CPU(s): 8
On-line CPU(s) list: 0-7
NUMA node0 CPU(s): 0-7
#以上服务器有一颗物理CPU,上面有8个核心
#将Nginx worker进程绑定至不同的核心上,建议与CPU核心保持一致
# 第一种绑定组合方式(不推荐)
worker_processes 12;
worker_cpu_affinity 000000000001 000000000010 000000000100 000000001000 000000010000 000000100000 000001000000 000010000000 000100000000 001000000000 010000000000 10000000000;
# 第二种方式(使用较少)
worker_processes 2;
worker_cpu_affinity 101010101010 010101010101;
# 第三种最佳绑定方式,修改nginx启动的work进程为自动。
worker_processes auto;
worker_cpu_affinity auto;
#查看Nginx worker进程绑定至对应CPU
[root@web01 ~]# ps -eo pid,args,psr|grep [n]ginx
1242 nginx: master process /usr/ 2
1243 nginx: worker process 0
1244 nginx: worker process 1
1245 nginx: worker process 2
1246 nginx: worker process 3
[root@nginx ~]# cat nginx.conf
user www; # nginx进程启动用户
worker_processes auto; #与cpu核心一致即可
worker_cpu_affinity auto; # cpu亲和
error_log /var/log/nginx/error.log warn; # 错误日志
pid /run/nginx.pid;
worker_rlimit_nofile 35535; #每个work能打开的文件描述符,调整至1w以上,负荷较高建议2-3w
events {
use epoll; # 使用epoll高效网络模型
worker_connections 10240; # 限制每个进程能处理多少个连接,10240x[cpu核心]
}
http {
include mime.types;
default_type application/octet-stream;
charset utf-8; # 统一使用utf-8字符集
# 定义日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
#定义json日志格式
log_format json_access '{"@timestamp":"$time_iso8601",'
'"host":"$server_addr",'
'"clientip":"$remote_addr",'
'"size":$body_bytes_sent,'
'"responsetime":$request_time,'
'"upstreamtime":"$upstream_response_time",'
'"upstreamhost":"$upstream_addr",'
'"http_host":"$host",'
'"url":"$uri",'
'"domain":"$host",'
'"xff":"$http_x_forwarded_for",'
'"referer":"$http_referer",'
'"status":"$status"}';
access_log /var/log/nginx/access.log main; # 访问日志
server_tokens off; # 禁止浏览器显示nginx版本号
client_max_body_size 200m; # 文件上传大小限制调整
# 文件高效传输,静态资源服务器建议打开
sendfile on;
tcp_nopush on;
# 文件实时传输,动态资源服务建议打开,需要打开keepalive
tcp_nodelay on;
keepalive_timeout 65;
# Gzip 压缩
gzip on;
gzip_disable "MSIE [1-6]\."; #针对IE浏览器不进行压缩
gzip_http_version 1.1;
gzip_comp_level 2; #压缩级别
gzip_buffers 16 8k; #压缩的缓冲区
gzip_min_length 1024; #文件大于1024字节才进行压缩,默认值20
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript image/jpeg;
# 虚拟主机
include /etc/nginx/conf.d/*.conf;
}
1、CPU亲和、worker进程数、调整每个worker进程打开的文件数
2、使用EPOOL网络模型、调整每个worker进程的最大连接数
3、文件的高效读取sendfile、no铺设
4、文件传输实时性、nodealy
5、开启tcp长连接,以及长连接超时时间keepalived
6、开启文件传输压缩gzip
7、开启静态文件expires缓存
8、隐藏Nginx版本号
9、禁止通过ip地址访问,禁止恶意域名解析,只允许域名访问
10、配置防盗链、以及跨域访问
11、防DDOS、CC攻击,限制单IP并发连接,以及http请求
12、建立Nginx错误页面
13、Nginx加密传输https优化
14、Nginx proxy_cache、fastcgi_cache、uwsgi_cache缓存,第三方工具(squid、varnish)
我是koten,10年运维经验,持续分享运维干货,感谢大家的阅读和关注!