优化 Nginx 的必要性
????????初步学习 Nginx 有必然要了解下如何进一步压榨 Nginx 的性能。要是你要搭建自己的服务器,那么你肯定会想方设法地优化 Nginx(就算你的服务器目前压力不是很大),既然这样,那我们就必须进一步了解 Nginx。
????????在接下来的介绍中,会解释通过调整 Nginx 中的哪些设置可以得到更好的性能,来应对大量客户端请求。不过这只是对一些可以通过调整来改进性能的设置的简单概述。有可能一个参数带来的影响对于数以万计都是巨大的。
Nginx 可以从哪里优化
????????对于 Nginx 本身,我们在前面提到过,最重要的也就是它的配置文件了,在这个配置文件中,可以配置 Nginx 的各种属性,而对于和其它模块的协作方面的优化,这里暂时不讲。
????????既然我们需要对 Nginx 进行优化,那么首先想到的肯定就是这个配置文件,这个文件名为?nginx.conf ,它保存有 Nginx 不同模块的全部设置。
nginx.conf
定义了 Nginx 在为你的网站提供服务时,worker 进程(上一章进程里面提到过)的数量。据参考,这个优化值受到包括 CPU 内核数、存储数据的磁盘数、负载值在内的许多因素的影响。
如果不确定的话,将其设置为可用的 CPU 内核的数量(即?auto )是一个不错的选择(设置为auto ,将会尝试自动检测可用的值)。
events 模块包括了 Nginx 中处理链接的全部设置:
worker_connections ?设置了一个 worker 进程可以同时打开的链接数。
这个值原本受 events 里面的 worker_rlimit_nofile 参数所限制,但是现在这里没有这一项参数,那么调整的幅度就不要太大。虽然没有既定值,但是你只要知道这个值的含义,往后如果有需求,完全可以回头调整。
multi_accept ?的作用是告诉 Nginx 在收到新链接的请求通知时,尽可能接受链接。当然,得让他开着。
像前一章讲的,当外部有 http 请求时, Nginx 的 http 模块才是处理这个请求的核心。我们只要简单的了解一下就能优化不少参数。
这就是 http 模块的配置:
在 http 的配置文件中我们可以看到 每一个小模块 都是被独立标注出来的,很好区分,这里我们就尽量一个一个查看:
Basic Settings
- sendfile指向 sendfile()函数。sendfile() 在磁盘和 TCP 端口(或者任意两个文件描述符)之间复制数据。
在 sendfile 出现之前,为了传输这样的数据,需要在用户空间上分配一块数据缓存,使用?read() ?从源文件读取数据到缓存,然后使用?write() ?将缓存写入到网络。
sendfile() ?直接从磁盘上读取数据到操作系统缓冲。由于这个操作是在内核中完成的,sendfile() ?比?read() ?和?write() ?联合使用要更加有效率。
Nginx 配置优化项:
? ? sendfile ???????on;
????tcp_nopush ?????on;
????tcp_nodelay ????on;
1、TCP_NODELAY
怎么可以强制socket在它的缓冲区里发送数据?
一个解决方案是 TCP 堆栈的 TCP_NODELAY 选项。这样就可以使缓冲区中的数据立即发送出去。
Nginx的 TCP_NODELAY 选项使得在打开一个新的 socket 时增加了TCP_NODELAY选项。
但这时会造成一种情况:
终端应用程序每产生一次操作就会发送一个包,而典型情况下一个包会拥有一个字节的数据以及40个字节长的包头,于是产生4000%的过载,很轻易地就能令网络发生拥塞。
为了避免这种情况,TCP堆栈实现了等待数据 0.2秒钟,因此操作后它不会发送一个数据包,而是将这段时间内的数据打成一个大的包。这一机制是由Nagle算法保证。
Nagle化后来成了一种标准并且立即在因特网上得以实现。它现在已经成为默认配置了,但有些场合下把这一选项关掉也是合乎需要的。现在假设某个应用程序发出了一个请求,希望发送小块数据。我们可以选择立即发送数据或者等待产生更多的数据然后再一次发送两种策略。
如果我们马上发送数据,那么交互性的以及客户/服务器型的应用程序将极大地受益。如果请求立即发出那么响应时间也会快一些。以上操作可以通过设置套接字的 TCP_NODELAY = on 选项来完成,这样就禁用了Nagle 算法。(不需要等待0.2s)
2、tcp_nopush
在 nginx 中,tcp_nopush 配置和 tcp_nodelay "互斥"。它可以配置一次发送数据的包大小。也就是说,它不是按时间累计? 0.2 秒后发送包,而是当包累计到一定大小后就发送。在 nginx 中,tcp_nopush 必须和 sendfile 搭配使用。
3、sendfile
现在流行的web 服务器里面都提供 sendfile 选项用来提高服务器性能,那到底 sendfile是什么,怎么影响性能的呢?sendfile实际上是 Linux2.0+以后的推出的一个系统调用,web服务器可以通过调整自身的配置来决定是否利用 sendfile这个系统调用。先来看一下不用 sendfile的传统网络传输过程:
read(file,tmp_buf, len);
write(socket,tmp_buf, len);
硬盘 >> kernel buffer >> user buffer>> kernel socket buffer >>协议栈
一般来说一个网络应用是通过读硬盘数据,然后写数据到socket 来完成网络传输的。上面2行用代码解释了这一点,不过上面2行简单的代码掩盖了底层的很多操作。来看看底层是怎么执行上面2行代码的:
1、系统调用 read()产生一个上下文切换:从 user mode 切换到 kernel mode,然后 DMA 执行拷贝,把文件数据从硬盘读到一个 kernel buffer 里。
2、数据从 kernel buffer拷贝到 user buffer,然后系统调用 read() 返回,这时又产生一个上下文切换:从kernel mode 切换到 user mode。
3、系统调用write()产生一个上下文切换:从 user mode切换到 kernel mode,然后把步骤2读到 user buffer的数据拷贝到 kernel buffer(数据第2次拷贝到 kernel buffer),不过这次是个不同的 kernel buffer,这个 buffer和 socket相关联。
4、系统调用 write()返回,产生一个上下文切换:从 kernel mode 切换到 user mode(第4次切换了),然后 DMA 从 kernel buffer拷贝数据到协议栈(第4次拷贝了)。
上面4个步骤有4次上下文切换,有4次拷贝,我们发现如果能减少切换次数和拷贝次数将会有效提升性能。在kernel2.0+ 版本中,系统调用 sendfile() 就是用来简化上面步骤提升性能的。sendfile() 不但能减少切换次数而且还能减少拷贝次数。
再来看一下用 sendfile()来进行网络传输的过程:
sendfile(socket,file, len);
硬盘 >> kernel buffer (快速拷贝到kernelsocket buffer) >>协议栈
1、系统调用sendfile()通过 DMA把硬盘数据拷贝到 kernel buffer,然后数据被 kernel直接拷贝到另外一个与 socket相关的 kernel buffer。这里没有 user mode和 kernel mode之间的切换,在 kernel中直接完成了从一个 buffer到另一个 buffer的拷贝。
2、DMA 把数据从 kernelbuffer 直接拷贝给协议栈,没有切换,也不需要数据从 user mode 拷贝到 kernel mode,因为数据就在 kernel 里。
步骤减少了,切换减少了,拷贝减少了,自然性能就提升了。这就是为什么说在Nginx 配置文件里打开 sendfile on 选项能提高 web server性能的原因。
logging setings
Gzip settings
-
gzip ?设置 nginx gzip 压缩发送的数据。这会减少需要发送的数据的数量。 -
gzip_disable ?为指定的客户端禁用 gzip 功能。 -
gzip_proxied ?允许或禁止基于请求、响应的压缩。设置为 any,就可以 gzip 所有的请求。 -
gzip_comp_level ?设置了数据压缩的等级。等级可以是 1-9 的任意一个值,9 表示最慢但是最高比例的压缩。 -
gzip_types ?设置进行 gzip 的类型。有下面这些,不过还可以添加更多。
访问控制?
基于各种原因,我们要进行访问控制。比如说,一般网站的后台都不能让外部访问,所以要添加 IP 限制,通常只允许公司的 IP 访问。访问控制就是指只有符合条件的 IP 才能访问到这个网站的某个区域。
相关指令
涉及模块:ngx_http_access_module
模块概述:允许限制某些 IP 地址的客户端访问。
对应指令:
语法:
allow address | CIDR | unix: | all;
默认值: 无
作用域:?http, server, location, limit_except
允许某个 IP 或者某个 IP 段访问。如果指定 unix,那将允许 socket 的访问。
注意:unix 在 1.5.1 中新加入的功能,如果你的版本比这个低,请不要使用这个方法。
语法:
deny address | CIDR | unix: | all;
默认值: 无
作用域:http, server, location, limit_except
禁止某个 IP 或者一个 IP 段访问。如果指定 unix,那将禁止 socket 的访问。
配置范例:
location / {
deny 192.168.1.1;
allow 192.168.1.0/24;
allow 10.1.1.0/16;
allow 2001:0db8::/32;
deny all;
}
规则按照顺序依次检测,直到匹配到第一条规则。
在这个例子里,IPv4 的网络中只有 10.1.1.0/16 和 192.168.1.0/24 允许访问,但 192.168.1.1 除外;对于 IPv6 的网络,只有 2001:0db8::/32 允许访问。
ngx_http_access_module ?配置允许的地址能访问,禁止的地址被拒绝。
这只是很简单的访问控制,而在规则很多的情况下,使用?ngx_http_geo_module ?模块变量更合适。
这里的?ngx_http_geo_module ?模块大家下来可以了解 :?ngx_ht
?DDos预防配置
????????DDoS 的特点是分布式,针对带宽和服务攻击,也就是四层流量攻击和七层应用攻击,相应的防御瓶颈四层在带宽,七层的多在架构的吞吐量。
对于七层的应用攻击,我们还是可以做一些配置来防御的,使用 Nginx 的?http_limit_conn ?和?http_limit_req ?模块通过限制连接数和请求数能相对有效的防御。
限制每秒请求数
ngx_http_limit_req_module ?模块通过漏桶原理来限制单位时间内的请求数,一旦单位时间内请求数超过限制,就会返回 503 错误。配置需要在两个地方设置:
nginx.conf 的 http 段内定义触发条件,可以有多个条件
在 location 内定义达到触发条件时 nginx 所要执行的动作
http {
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
...
server {
...
location ~ \.php$ {
limit_req zone=one burst=5 nodelay;
}
}
}
参数说明
参数 | 描述 |
---|
binary_remote_addr | 二进制远程地址,这个参数就写这个就好了,不需要改 | zone=one:10m | 定义 zone 名字叫 one,并为这个 zone 分配 10M 内存,用来存储会话(二进制远程地址),1m 内存可以保存 16000 会话 | rate=10r/s | 限制频率为每秒 10 个请求 | burst=5 | 允许超过频率限制的请求数不多于 5 个,假设 1、2、3、4 秒请求为每秒 9 个,那么第 5 秒内请求 15 个是允许的,反之,如果第一秒内请求 15 个,会将 5 个请求放到第二秒,第二秒内超过 10 的请求直接 503,类似多秒内平均速率限制。 | nodelay | 超过的请求不被延迟处理,设置后 15 个请求在 1 秒内处理。 |
限制 IP 连接数
上一章讲过,我们就直接写出来
http {
limit_conn_zone $binary_remote_addr zone=addr:10m; //触发条件
...
server {
...
location /download/ {
limit_conn addr 1; // 限制同一时间内1个连接,超出的连接返回503
}
}
}
白名单设置
http_limit_conn 和 http_limit_req 模块限制了单 IP 单位时间内的连接和请求数,但是如果 Nginx 前面有 lvs 或者 haproxy 之类的负载均衡或者反向代理,nginx 获取的都是来自负载均衡的连接或请求,这时不应该限制负载均衡的连接和请求,就需要 geo 和 map 模块设置白名单:
geo $whiteiplist {
default 1;
10.11.15.161 0;
}
map $whiteiplist $limit {
1 $binary_remote_addr;
0 "";
}
limit_req_zone $limit zone=one:10m rate=10r/s;
limit_conn_zone $limit zone=addr:10m;
geo 模块定义了一个默认值是 1 的变量 whiteiplist,当在 ip 在白名单中,变量 whiteiplist 的值为 0,反之为 1
如果在白名单中--> whiteiplist=0 -->?$limit="" --> 不会存储到 10m 的会话状态(one 或者 addr)中 --> 不受限制;
反之,不在白名单中 --> whiteiplist=1 -->?$limit=二进制远程地址 -->存储进 10m 的会话状态中 --> 受到限制。
?
|