0
点赞
收藏
分享

微信扫一扫

Nginx slice模块 用分片提升缓存效率

鱼满舱 2022-08-17 阅读 84


Nginx的slice模块可以将一个请求分解成多个子请求,每个子请求返回响应内容的一个片段,让大文件的缓存更有效率。

 

HTTP Range请求

HTTP客户端下载文件时,如果发生了网络中断,必须重新向服务器发起HTTP请求,这时客户端已经有了文件的一部分,只需要请求剩余的内容,而不需要传输整个文件,Range请求就可以用来处理这种问题。

如果HTTP请求的头部有Range字段,如下面所示:

Range: bytes=1024-2047

表示客户端请求文件的第1025到第2048个字节,这时服务器只会响应文件的这部分内容,响应的状态码为206,表示返回的是响应的一部分。如果服务器不支持Range请求,仍然会返回整个文件,这时状态码仍是200。

 

Nginx启用slice模块

ngx_http_slice_filter_module模块默认没有编译到Nginx程序中,需要编译时添加--with-http_slice_module选项。

编译完成后, 需要在Nginx配置文件中开启,配置如下所示

location / {
slice 1m;
proxy_cache cache;
proxy_cache_key $uri$is_args$args$slice_range;
proxy_set_header Range $slice_range;
proxy_cache_valid 200 206 1h;
proxy_pass http://localhost:8000;
}

slice指令设置分片的大小为1m。 这里使用了proxy_set_header指令,在取源时的HTTP请求中添加了Range头部,向源服务器请求文件的一部分,而不是全部内容。在proxy_cache_key中添加slice_range变量这样可以分片缓存。

 

slice_range变量

slice_range这个变量作用非常特殊,这个变量的值是当前需要向源服务器请求的分片,如果分片的大小为1m,那么最开始变量的值为​​bytes=0-1048575​​​,通过配置文件中的​​proxy_set_header Range $slice_range;​​​可以知道取源时请求的Range头部为​​Range:bytes=0-1048575​​​,源服务器如果支持Range请求,便会返回响应的前1m字节,得到这个响应后slice_range变量的值变为​​bytes=1048576-2097171​​ ,再次取源时便会取后1m字节,依次直到取得全部响应内容。

 

Nginx分片的实现

Nginx的slice模块是通过挂载filter模块来起作用的,处理流程如下所示

Nginx slice模块 用分片提升缓存效率_nginx

  1. 每次取源时都会携带Range头部,
  2. 第一次取源请求前1m内容,如果响应在1m以内,或者源服务器不支持Range请求,返回状态码为200,这时会直接跳过slice模块。
  3. 在body_filter中向客户端发送得到的当前的分片,然后检查是否到达文件末尾,如果没有则生成一个子请求,子请求会向源服务器请求下一个分片,依次循环。

 

slice模块

当我们使用nginx作为反向代理,并且响应上游的响应的时候,如果上游的文件特别大。那么nginx去处理这么大的响应的时候,它的效率就比较低下了,特别是有多个请求打到暂时没有缓存的大文件的时候

这个时候nginx官方提供了slice的模块可以通过range协议将一个很大的响应分解为很多小的响应来提升我们的服务性能。

Nginx slice模块 用分片提升缓存效率_客户端_02

为0的时候表示禁用这个功能,后面跟上一个size表示通过range协议将多个大文件分解为多个小文件独立的缓存,当客户端发来的请求中已经含有range协议时可以更好的服务。

Nginx slice模块 用分片提升缓存效率_nginx_03

客户端请求100个字节,起始于150,请求内容的范围是150-249。发到nginx之后根据slice配置,比如配置为100,那么就是0-100,100-200,200-300,这样就分为了3块,但是最终这个文件有多大就切分为多少块。之后nginx就会构造两个请求,第一个请求时100-199,然后第二个请求时200-300的。这两个请求返回之后会生成两个文件,第一个100-199,200-299。然后将其组合起来生成客户端要的150-249这样一个响应。

 

过程总结

客户端向nginx请求一个10M文件,nginx进行4m的切片,整个过程大概是

  • 1.客户端向nginx请求10M
  • 2.nginx发起第一个切片(主请求)请求range:0-4194303
  • 3.第一个切片(主请求)请求的内容全部发给客户端后,在slice模块的body_filter发起第二个切片(子请求),请求range: 4194304-8388607
  • 4.第二个切片(子请求)请求的内容完全发完給客户端后,切回主请求
  • 5.主请求在slice模块的body_filter发起第三个切片(子请求),请求range: 8388608-12582911
  • 6.第三个切片(子请求)请求的内容(8388608-10485759)完全发完給客户端后,切回主请求
  • 7.主请求在slice模块的body_filter判断已经将10M的文件发給客户端,不再进行slce的模块处理

 

代理服务器不使用slice模块

#192.168.179.99是代理服务器  192.168.179.100为上游服务器

#192.168.179.99配置
proxy_cache_path /data/nginx/tmpcache3 levels=2:2 keys_zone=nginx_cache:10m loader_threshold=300
loader_files=200 max_size=200m inactive=1m;

server {
server_name test.net;

error_log logs/cacherr.log debug;

location /{
proxy_cache nginx_cache;
#slice 1m;
#proxy_cache_key $uri$is_args$args$slice_range;
#proxy_set_header Range $slice_range;

proxy_cache_valid 200 206 1m;
add_header X-Cache-Status $upstream_cache_status;

proxy_pass http://192.168.179.100;
}
}



#让客户端使用range协议,-r表示使用range协议会构造range的头部,我只访问3000000-3000009即3M里面的10个字节。可以看到返回也确实只有10个字节
[root@www ~]# curl -r 3000000-3000009 192.168.179.99/test.mp4 -I
HTTP/1.1 206 Partial Content
Server: nginx/1.16.1
Date: Sun, 07 Jun 2020 03:33:15 GMT
Content-Type: video/mp4
Content-Length: 10 #返回10字节
Connection: keep-alive
Last-Modified: Sun, 15 Mar 2020 06:29:56 GMT
ETag: "5e6dcb64-2746cad"
X-Cache-Status: MISS #缓存没有命中,访问到上游服务器了
Content-Range: bytes 3000000-3000009/41184429

#访问到上游但是只返回了10个字节,上游究竟发生了什么,192.168.179.100为上游服务器日志
[root@www ~]# tail -f /usr/local/nginx/logs/access.log
192.168.179.99 - - [07/Jun/2020:11:28:46 +0800] "GET /test.mp4 HTTP/1.0" 200 41184429 "-" "curl/7.29.0" "-"
#可以看到上游直接返回了41M完整的响应(这里我只访问10个字节却给我返回了41M),这是nginx做的一个优化,你只是访问了大文件range其中一小部#分,但是nginx考虑到一次性向上游取到整个响应内容,后续再访问到其他字节就可以直接使用我的缓存了。

#但是如果我们的服务是并发的,同时有多个客户去访问大文件的某一块的话就会引发很严重的问题。很多请#求都会去访问一个巨大的文件,这个时候slice模块就有了用武之地

 

代理服务器使用slice模块

使用slice该模块需要配置3个地方,slice后面要有一个单位,即分为多大的大小进行切分,如果分的特别小会造成很多文件,如果分的特别大效果就不会特别明显。这里分为1M。

proxy_cache_key   $uri$is_args$args$slice_range;  $slice_range,这样才能知道客户端请求的内容是100-199字节。

proxy_set_header  Range $slice_range;必须保证我们的range这个头部是发到上游的,发送到上游的单位是1M,即每次1M发往上游。

#192.168.179.99代理服务配置
location /{
proxy_cache nginx_cache;
slice 1m;
proxy_cache_key $uri$is_args$args$slice_range;
proxy_set_header Range $slice_range;

proxy_cache_valid 200 206 1m;
add_header X-Cache-Status $upstream_cache_status;

proxy_pass http://localhost:192.168.179.100;
}


[root@www ~]# curl -r 3000000-3000009 192.168.179.99/test.mp4 -I
HTTP/1.1 206 Partial Content
Server: nginx/1.16.1
Date: Sun, 07 Jun 2020 03:42:54 GMT
Content-Type: video/mp4
Content-Length: 10 #返回的还是10个字节
Connection: keep-alive
Last-Modified: Sun, 15 Mar 2020 06:29:56 GMT
ETag: "5e6dcb64-2746cad"
X-Cache-Status: MISS #可以看到响应还是发往上游了
Content-Range: bytes 3000000-3000009/41184429

#上游服务192.168.179.100日志
[root@www ~]# tail -f /usr/local/nginx/logs/access.log
192.168.179.99 - - [07/Jun/2020:11:42:54 +0800] "GET /test.mp4 HTTP/1.0" 206 1048576 "-" "curl/7.29.0" "-"
可以看到请求现在变为了1M,而不是41M
当上游返回巨大文件的时候,使用slice可以针对如果客户端使用断点续传,多线程下载等等含有range场景。那么slice模块是非常有用的。

 

Range的范围

请求中的Range范围可能会超过文件的大小,如第一次取源时,Nginx并不知道实际文件的大小,所以Nginx请求时总是按照分片的大小设置Range范围,如slice设置为1m,那么第一次取​​bytes=0-1048575​​​,如果文件不足1m,响应状态吗为200,表示不需要分片。如果超过1m,第二次取源时Range字段为​​bytes=1048576-2097171​​,即使这时可以知道文件实际大小。

线上使用时就遇到过一次源服务器对Range请求支持不完善的问题,文件大小为1.5m,第一次取源状态码为206,返回1m内容,第二次取源使Range字段为​​bytes=1048576-2097171​​,但是文件不足2m,源服务器发现这个范围超过了文件大小,所以返回了整个文件,状态码为200,这时Nginx就不能理解了,直接报错中断了响应。

开始以为是Nginx的问题,然后查看了下RFC文档,发现有解释这种情况

A client can limit the number of bytes requested without knowing the size of the selected representation. If the last-byte-pos value is absent, or if the value is greater than or equal to the current length of the representation data, the byte range is interpreted as the remainder of the representation (i.e., the server replaces the value of last-byte-pos with a value that is one less than the current length of the selected representation).

大致意思是说,如果请求的分片的后一个偏移超过了文件的实际大小,服务器应该返回剩余的部分内容。这个问题应该是源服务器的实现并没有按照RFC文档的要求。

举报

相关推荐

0 条评论