Nginx学习:HTTP核心模块(七)请求体及请求限流
对于一个请求来说,请求行、请求头、请求体共同构成了它的整体。不过如果仅仅是 GET 请求的话,其实一般会忽略掉请求体。但是,现在大部分的伪 RESTful 开发风格,基本上已经变成了全部都是 POST 的天下了。特别是小型公司,不管前后端分离还是小程序、APP,一套 POST 走天下。
这也不怪我们懒,或者不规范,小公司毕竟是以成本,以效率来搏命的,即使是我自己弄一些小东西,也只是想着怎么简单怎么来,不会去考虑那么复杂的实现。也正因此,POST 中请求体的部分确实是现在非常重要的内容。下面我们来详细看看相关的配置都有哪些。另外,请求限流部分的内容是我之前从来没接触过的,虽说也看过一点相关的知识,知道一些什么令牌桶之类的名词,但确实从没有自己写过。因此,请求限流部分也只是简单地测试和演示,没法给出更详细的原理及解释。
请求正文
请求正文,指的就是发来的请求中,body 部分的内容,也就是我们常说的 POST 请求的请求体部分。这一部分的配置主要是请求体的大小、超时时间、缓冲区等等。请求正文相关的配置项都可以用于 http、server、location 各个模块中。
client_body_buffer_size
设置读取客户端请求正文的缓冲容量。
代码语言:javascript复制client_body_buffer_size size;
如果请求正文大于缓冲容量,整个正文或者正文的一部分将写入临时文件。缓冲大小默认等于两块内存页的大小,在x86平台、其他 32 位平台和 x86-64 平台,这个值是8K。在其他 64 位平台,这个值一般是 16K 。
我们和下面的配置一起测试这个配置指令。
client_body_temp_path
定义存储客户端请求正文的临时文件的目录,没错,就是上面的超出 client_body_buffer_size 设置大小的数据所保存的临时文件的位置。默认情况下,它是位于当前运行的 Nginx 程序目录下的 client_body_temp ,比如我这里就是在这里:
代码语言:javascript复制[root@localhost nginx]# pwd
/usr/local/nginx
[root@localhost nginx]# ll
total 0
drwx------. 2 www root 6 Jul 13 23:35 client_body_temp
它的配置参数是这样的。
代码语言:javascript复制client_body_temp_path path [level1 [level2 [level3]]];
这个 path 写相对路径或者绝对路径都可以,如果是相对路径,那么它对应的路径就是运行的 Nginx 程序所在的目录,和日志那些一样。参数后面的 level1 之类的参数,表示的是子目录的结构,最多可以支持三层,比如下面这样的配置。
代码语言:javascript复制client_body_temp_path /spool/nginx/client_temp 1 2;
存储临时文件的路径就是:
代码语言:javascript复制/spool/nginx/client_temp/7/45/00000123457
client_body_in_file_only
这个配置是用于决定 Nginx 是否将客户端请求正文整个写入到临时文件当中。
代码语言:javascript复制client_body_in_file_only on | clean | off;
当它的配置指令值设置为 on 时,请求处理结束后不会删除临时文件。当指令值设置为 clean 时,请求处理结束后会删除临时文件。设置为 off 就是不记录到文件中啦!默认值是 off 。
好了,结合上面三个配置,我们进行一波测试。
代码语言:javascript复制// nginx.conf
#client_body_buffer_size 10;
client_body_temp_path client_body_temptest;
client_body_in_file_only on;
先忽略第一个,因为我们开启了 client_body_in_file_only
,所以所有的请求体中的内容都会被记录。指定的目录是当前运行 Nginx 的目录下面的 client_body_temptest 目录,其实就是给默认目录后面加了个 test 。接着,咱们还需要定义一个可以发送 POST 请求的动态页面,由于目前的环境还没有安装 PHP ,所以咱们就随便代理一个地址。(如果直接使用 POST 请求静态页面,会报出 405 错误,Nginx 默认是禁止 POST 静态页面的)
location /request_body {
proxy_pass http://localhost:8001;
}
注意哦,这是我们随便代理的,8001 是不存在的,因此直接访问这个页面是会报错的。接下来,用 Postman 发送一个 Post 请求,使用哪种参数形式都可以,这里我使用的是 raw 。
请求之后,就可以看到 Nginx 的运行目录下多了个 client_body_temptest 目录,进去后就看到了下面这样的内容。
代码语言:javascript复制// POST 请求 http://192.168.56.88/request_body 之后
// /usr/local/nginx/client_body_temptest
[root@localhost client_body_temptest]# ll
total 140
-rw-------. 1 www www 60453 Jul 30 03:13 0000000001
试着打开文件,你就会发现里面正是我们发送请求时写在请求体中的内容。好了,接下来怎么测 client_body_buffer_size
呢?它的意思是当请求体的大小超过了设置的缓冲值时,使用文件来保存请求体内容。注意,我们现在开启了 client_body_in_file_only
,因此所有文件都被保存下来,所以我们要注释掉它,然后再打开 client_body_buffer_size
的注释,并把值设小点。(有问题看评论)
// nginx.conf
client_body_buffer_size 10;
client_body_temp_path client_body_temptest;
#client_body_in_file_only on;
再次请求页面,在 client_body_temptest 目录下并没有生成新的文件,这要怎么验证呢?其实很简单,用 ll -a
看一下当前目录的修改时间就好了,或者再次开启 client_body_in_file_only ,会发现文件序号其实一直在增长。
// POST 请求 http://192.168.56.88/request_body 之后
// /usr/local/nginx/client_body_temptest
[root@localhost client_body_temptest]# ll -a
total 20
drwx------. 2 www root 24 Jul 30 03:30 .
drwxr-xr-x. 11 root root 167 Jul 30 02:31 ..
-rw-------. 1 www www 18074 Jul 30 03:13 0000000001
第一个 . 表示当前目录,看到它的修改时间发生了变化。现在你再把 client_body_buffer_size
的值调大,只要大过你测试的请求体的长度就好了。完了再测试一下,看看这个目录的修改时间会不会发生变化。如果没有发生变化,就说明请求体在缓冲中处理了,没有在这里创建过临时文件。
上面三个配置要注意的几点是:
client_body_temp_path
指定的目录要有 Nginx 运行用户的权限,就是我们第一篇文章中那个user
指定的用户。- 出现
xxx upstream response is buffered to a temporary file xxxx while reading upstream
这样的错误,就是请求体频繁使用文件存储,可能是client_body_buffer_size
的值设小了,可以适当调大,具体大小根据内存占用情况来确定。 - 如果不在乎内存,就把
client_body_buffer_size
和后面要讲的client_max_body_size
设置成一样,完全不走文件保存了。 client_body_in_file_only
非调试测试的情况下不要开,完全走文件肯定影响效率。
client_body_in_single_buffer
这个配置项可以确定 Nginx 将整个客户端请求正文是否保存在一块缓冲中。
代码语言:javascript复制client_body_in_single_buffer on | off;
它的默认值是 off ,推荐在使用 $request_body
变量时使用,可以节省引入的拷贝操作。
client_body_timeout
用于定义读取客户端请求正文的超时时间。
代码语言:javascript复制client_body_timeout time;
默认值是 60 秒,超时是指相邻两次读操作之间的最大时间间隔,而不是整个请求正文完成传输的最大时间。如果客户端在这段时间内没有传输任何数据,Nginx 将返回408(Request Time-out) 错误到客户端。啥意思呢?连接建立了,60s 内啥玩意也不发,直接就报错呗,这个我也不知道怎么测,所以咱们保持默认就好啦。
client_max_body_size
它设置允许客户端请求正文的最大长度,这个配置应该是比较常见的,特别是做过上传大文件的同学。
代码语言:javascript复制client_max_body_size size;
它的默认值是 1m ,和 PHP 中的 upload_max_filesize
以及 post_max_size
类似,不过 PHP 中的默认值是 2m 。如果要上传大文件,这两边都要修改。请求的长度由 “Content-Length” 请求头指定。如果请求的长度超过设定值,nginx将返回错误 413(Request Entity Too Large)到客户端。请注意浏览器不能正确显示这个错误。设置成 0 可以使nginx不检查客户端请求正文的长度。
这个好测,直接找个大于 1m 的文件用 POST 上传到我们前面那个 /request_body 路径下,然后再修改这个值,超过我们选择的文件大小,再试一次就知道它的效果啦!或者直接设置成小的值,比如 1k ,然后随便找个大于 1k 的测试一下会不会返回 413 错误。
请求限流
Nginx 的请求限流部分,主要限的是速度,也就是流量大小。注意,这里是限流,不是限制,限制是限制请求的连接数量 ,后面还有专门的请求限制模块。限流这里最典型的案例就是百度网盘的会员和非会员的下载速度的区别。通过 Nginx 我们也可以方便地对我们自己的流量速度进行限制,我们先来看看这个配置相关的指令。
limit_except
在指定的 location 下面,配置 limit_except 后,可以按指定的 HTTP 方法进行请求限制。
代码语言:javascript复制limit_except method ... { ... }
在 Apache 下配置虚拟主机的时候,经常会有 Deny 和 Allow 这些相关的配置,而在 Nginx 中,其实我们通常很少去配置这个。毕竟在 Apache 下面,大部分情况下通常我们也是直接 Allow all; 的。
method 参数是用于指定不由这些限制条件进行过滤的 HTTP 方法,可选值有 GET、 HEAD、 POST、 PUT、 DELETE、 MKCOL、 COPY、 MOVE、 OPTIONS、 PROPFIND、 PROPPATCH、 LOCK、 UNLOCK 或者 PATCH。
指定 method 为 GET 方法的同时,Nginx 会自动添加 HEAD 方法。那么其他 HTTP 方法的请求就会由指令引导的配置块中的ngx_http_access_module 模块和 ngx_http_auth_basic_module 模块的指令来限制访问。这两个模块我们后面会单独学习。
那么咱们就来配置一个。为了能够测试 POST 之类的请求,需要连接一下 PHP ,所以咱们就使用之前配置好的 PHP 配置。
代码语言:javascript复制location ~ .php$ {
root html;
limit_except GET {
allow 192.168.56.101;
deny all;
}
fastcgi_pass unix:/var/sock/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
在 html/1.php 这个文件中,我们直接打印了 $_SERVER
的信息,因此正常访问的话,会输出 PHP 这边的 SERVER 相关内容,这里就不多说了。
我们的主机是虚拟机的主机,IP 是 192.168.56.1 ,在配置中,我们 deny 了 all ,但是开放了 .101 可以访问。划重点了,上面的 GET 表示除了 GET 之外的,比如 POST 之类的请求,就会走下面的限制约束,第一次接触的小伙伴可能真的会搞反,以为是写在这里的才被限制,其实是反过来的哦,写在这里的是不被限制的。因此,在现在的情况下,.1 主机 GET 访问 http://192.168.56.88/1.php 应该是没问题了,那么 POST 访问会出现什么呢?
大家可以试试,直接返回的就是 403 Forbidden ,也就是没有权限。也就是说,deny 是生效的。现在我们再开一台虚拟机,IP 设置成 192.168.56.101 ,然后使用 CURL 请求。
代码语言:javascript复制// 192.168.56.101
[root@localhost ~]# curl -X POST 'http://192.168.56.88/1.php'
Array
(
[USER] => www
[HOME] => /home/www
…………
)
没有任何问题,POST 也可以正常请求,说明 allow 对 .101 这台虚拟机是生效的。有兴趣的小伙伴可以再开一台虚拟机,设置成别的 IP ,再次进行测试,看看效果怎么样。
limit_rate
接下来我们就来看看怎么限制流量,这个配置也比较简单。
代码语言:javascript复制limit_rate rate;
默认值是 0 ,单位是 字节/秒 ,默认 0 就表示不限制。Nginx 按连接限速,所以如果某个客户端同时开启了两个连接,那么客户端的整体速率是这条指令设置值的 2 倍。
从 1.17.0 版本开始参数值可以包含变量,如果要根据特定条件限制速率的话,它可能很有用:
代码语言:javascript复制map $slow $rate {
1 4k;
2 8k;
}
limit_rate $rate;
另外我们也可以直接使用 $limit_rate
变量设置流量限制(1.17.0后不推荐)。如果想在特定条件下限制响应传输速率,可以使用这个功能:
server {
if ($slow) {
set $limit_rate 4k;
}
...
}
变量相关的内容我们在后面的文章中再详细说,一会也会简单演示一下。
最后还可以通过 “X-Accel-Limit-Rate” 响应头来完成速率限制。这种机制可以用 proxy_ignore_headers 指令和 fastcgi_ignore_headers 指令关闭。
上面全是官网的说明内容,具体的演示我们将结合下面的配置指令一起测试。
limit_rate_after
这个指令用于设置不限速传输的响应大小。
代码语言:javascript复制limit_rate_after size;
默认 0 ,单位是字节,当传输量大于此值时,超出部分将根据 limit_rate 的规则限速传送。
测试限流
好了,咱们来试试吧。首先得搞个大点的文件,这样传输过程才会持续一段时间,就挑个之前我录过的视频好了。把它放到之前准备好的 /usr/local/nginx/html/mp4/ 目录下并改名为 1.mp4 为了好记好测。接着就配置一个 location 。
代码语言:javascript复制location /limitmp4 {
alias /usr/local/nginx/html/mp4/;
}
现在访问 /limitmp4/1.mp4 ,会发现速度很快,直接下载视频,速度也很惊人,毕竟咱们是在本地的虚拟机上。然后咱们就来进行限流。
代码语言:javascript复制location /limitmp4 {
alias /usr/local/nginx/html/mp4/;
limit_rate 1k;
}
配置 limit_rate 设置为 1k ,之后重载 Nginx 并进行访问。很明显,连播放都开始卡顿了,下载速度也直接变慢了。
代码语言:javascript复制location /limitmp4 {
alias /usr/local/nginx/html/mp4/;
limit_rate 1m;
limit_rate_after 1000m;
}
继续加上 limit_rate_after 限制到 1000m ,limit_rate 设置为 1m ,也就是 1G 以上的文件才会限制流量,再次访问,速度又恢复到了之前的水平。现在你可以随意修改这两个参数了,比如将 limit_rate_after 改成 200m ,我们的文件有 500m ,这时限速又开始了,不过速度比限制在 1k 的时候要好很多,因为现在的传输速度在 1m(这里有问题,详见视频)。
接下来,我们测试一下 $limit_rate
变量的限制效果。
#set $limit_rate 1k;
location /limitmp4 {
alias /usr/local/nginx/html/mp4/;
#limit_rate 1m;
#limit_rate_after 400m;
set $limit_rate 1k;
}
location /limitmp4_2 {
alias /usr/local/nginx/html/mp4/;
}
直接设置一个 limit_rate 变量,并给其赋值为 1k ,然后访问链接,速度被限制了。然后我们开启另一个 location ,速度正常,可以看到 limit_rate 也是有作用域范围的。现在可以再打开最上面的,也就是在 server 作用域下的
最后, X-Accel-Limit-Rate 头是针对上游服务器的,也就是反向代理或者负载均衡那边返回的内容是否将对应的响应头返回给客户端的,这个不好测,资料也不多,大概了解一下就好啦。
说了这么半天,到底限流的意义何在呢?除了百度网盘那种收费的作用外,限流还可以保护我们的带宽,避免某一个连接将整台服务器的带宽占满,比如我们下载东西的时候。另外就是在大促高并发的场景下,保障后端程序带宽的稳定性,同样也是避免带宽被占用挤压影响其它业务。
将来我们在学习更多架构方面知识的时候再来详细的研究吧,目前我的认知只到这个水平,反正 Nginx 中是有这个功能的,这一点大家还是要牢记。
总结
今天的内容真正的配置指令没几个,请求正文有六个,请求限流只有三个,更多的还是在进行一些测试。毕竟都是自己之前从来没怎么配过玩过的东西。而且我发现,请求限流相关的内容还是非常有意思的,对于大文件上传下载也有了一些想法,直接用 Nginx 就可以避免带宽被某几个用户的大文件操作占满。确实还是收获满满,各位别急,后面还有好玩的东西呢,千万别错过了。
参考文档:
http://nginx.org/en/docs/http/ngx_http_core_module.html