作者:F5 的欧文加勒特 产品治理高级总监 2016 年 1 月 21 日
正确部署后,缓存是减速 Web 内容的最快捷方式之一。缓存不仅使内容更凑近最终用户(从而缩小提早),还缩小了对上游源服务器的申请数量,从而进步了容量并升高了带宽老本。
AWS 等寰球分布式云平台和 Route 53 等基于 DNS 的寰球负载平衡零碎的可用性使您能够创立本人的寰球内容交付网络 (CDN)。
在本文中,咱们将理解 NGINX 开源和 NGINX Plus 如何缓存和交付使用字节范畴申请拜访的流量。一个常见的用例是 HTML5 MP4 视频,其中申请应用字节范畴来实现特技播放(跳过和搜寻)视频播放。咱们的指标是实现反对字节范畴的视频传输缓存解决方案,并最大限度地缩小用户提早和上游网络流量。
编者:在 NGINX Plus R8 中引入了一一切片填充缓存切片中探讨的缓存切片办法。无关该版本中所有新性能的概述,请参阅咱们博客:NGINX Plus R8 发版。
咱们的测试框架
咱们须要一个简略的、可重现的测试框架来钻研应用 NGINX 进行缓存的代替策略。
一个简略、可反复的测试平台,用于钻研 NGINX 中的缓存策略
咱们从一个 10-MB 的测试文件开始,其中蕴含每 10 个字节的字节偏移量,以便咱们能够验证字节范畴申请是否失常工作:
origin$ perl -e 'foreach $i (0 ... 1024*1024-1) { printf"%09d\n",
$i*10 }' > 10Mb.txt
文件中的第一行如下:
origin$ head 10Mb.txt
000000000
000000010
000000020
000000030
000000040
000000050
000000060
000000070
000000080
000000090
对文件中的两头字节范畴(500,000 到 500,009)的 curl 申请会返回预期的字节范畴:
client$ curl -r 500000-500009 http://origin/10Mb.txt
000500000
当初让咱们为源服务器和 NGINX 代理缓存之间的单个连贯增加 1MB/s 的带宽限度:
origin# tc qdisc add dev eth1 handle 1: root htb default 11
origin# tc class add dev eth1 parent 1: classid 1:1 htb rate 1000Mbps
origin# tc class add dev eth1 parent 1:1 classid 1:11 htb rate 1Mbps
为了查看提早是否按预期工作,咱们间接从源服务器检索整个文件:cache$ time curl -o /tmp/foo http://origin/10Mb.txt
% Total % Received % Xferd Average Speed Time ...
Dload Upload Total ...
100 10.0M 100 10.0M 0 0 933k 0 0:00:10 ...
... Time Time Current
... Spent Left Speed
... 0:00:10 --:--:-- 933k
real 0m10.993s
user 0m0.460s
sys 0m0.127s
交付文件须要将近 11 秒,这是对边缘缓存性能的正当模仿,边缘缓存通过带宽无限的 WAN 网络从源服务器拉取大文件。
NGINX 的默认字节范畴缓存行为
一旦 NGINX 缓存了整个资源,它会间接从磁盘上的缓存正本中为字节范畴的申请提供服务。
当内容没有被缓存时会产生什么?当 NGINX 收到对未缓存内容的字节范畴申请时,它会从源服务器申请整个文件(不是字节范畴),并开始将响应流式传输到长期存储。
一旦 NGINX 收到满足客户端原始字节范畴申请所需的数据,NGINX 就会将数据发送给客户端。在后盾,NGINX 持续将残缺响应流式传输到长期存储中的文件。传输实现后,NGINX 将文件挪动到缓存中。
咱们能够通过以下简略的 NGINX 配置很容易地演示默认行为:
proxy_cache_path /tmp/mycache keys_zone=mycache:10m;
server {
listen 80;
proxy_cache mycache;
location / {proxy_pass http://origin:80;}
}
咱们首先清空缓存:
cache # rm –rf /tmp/mycache/*
而后咱们申请 10Mb.txt 的两头十个字节:
client$ time curl -r 5000000-5000009 http://cache/10Mb.txt
005000000
real 0m5.352s
user 0m0.007s
sys 0m0.002s
NGINX 向源服务器发送整个 10Mb.txt 文件的申请,并开始将其加载到缓存中。一旦申请的字节范畴被缓存,NGINX 就会将其交付给客户端。正如 time 命令所报告的,这产生在 5 秒多一点的工夫内。
在咱们之前的测试中,传送整个文件只须要 10 多秒,这意味着在将两头字节范畴传送到客户端之后,检索和缓存 10Mb.txt 的全部内容还须要大概 5 秒。虚构服务器的拜访日志记录了残缺文件中 10,486,039 字节 (10 MB) 的传输,状态码为 200:
192.168.56.10 - - [08/Dec/2015:12:04:02 -0800] "GET /10Mb.txt HTTP/1.0" 200 10486039 "-" "-" "curl/7.35.0"
如果咱们 curl 在整个文件被缓存后反复申请,响应是立刻的,因为 NGINX 从缓存中提供申请的字节范畴。
然而,这种根本配置(以及由此产生的默认行为)存在问题。如果咱们第二次申请雷同的字节范畴,在它被缓存之后但在整个文件被增加到缓存之前,NGINX 向源服务器发送一个对整个文件的新申请,并开始一个新的缓存填充操作。咱们能够应用以下命令演示此行为:
client$ while true ; do time curl -r 5000000-5000009 http://dev/10Mb.txt ; done
对源服务器的每个新申请都会触发一个新的缓存填充操作,并且缓存不会“稳定下来”,直到缓存填充操作实现而没有其余操作正在进行。
设想一下用户在视频文件公布后立刻开始观看的场景。如果缓存填充操作须要 30 秒(例如),但额定申请之间的提早小于此值,则缓存可能永远不会填充,NGINX 将持续向源服务器发送越来越多的整个文件申请。
NGINX 提供了两种缓存配置,能够无效解决这个问题:
缓存锁 ——应用此配置,在由第一个字节范畴申请触发的缓存填充操作期间,NGINX 将任何后续字节范畴申请间接转发到源服务器。缓存填充操作实现后,NGINX 为缓存中的字节范畴和整个文件的所有申请提供服务。
缓存切片 ——通过在 NGINX Plus R8 和 NGINX 开源 1.9.8 中引入的这种策略,NGINX 将文件宰割成能够疾速检索的更小的子范畴,并依据须要从源服务器申请每个子范畴。
对单个缓存填充操作应用缓存锁
以下配置在收到第一个字节范畴申请时立刻触发缓存填充,并在缓存填充操作正在进行时将所有其余申请转发到源服务器:
proxy_cache_path /tmp/mycache keys_zone=mycache:10m;
server {
listen 80;
proxy_cache mycache;
proxy_cache_valid 200 600s;
proxy_cache_lock on;
# Immediately forward requests to the origin if we are filling the cache
proxy_cache_lock_timeout 0s;
# Set the 'age' to a value larger than the expected fill time
proxy_cache_lock_age 200s;
proxy_cache_use_stale updating;
location / {proxy_pass http://origin:80;}
}
proxy_cache_lock on
– 设置缓存锁。当 NGINX 收到文件的第一个字节范畴申请时,它会从源服务器申请整个文件并启动缓存填充操作。NGINX 不会将后续的字节范畴申请转换为对整个文件的申请或启动新的缓存填充操作。相同,它将申请排队,直到第一个缓存填充操作实现或锁定超时。
proxy_cache_lock_timeout
– 管制缓存锁定多长时间(默认为 5 秒)。当超时到期时,NGINX 将每个排队的申请未经批改地转发到源服务器(作为保留标头的字节范畴申请 Range,而不是作为对整个文件的申请),并且不缓存源服务器返回的响应。
在咱们应用 10Mb.txt
进行测试的状况下,缓存填充操作可能会破费大量工夫,因而咱们将锁定超时设置为 0(零)秒,因为没有必要将申请排队。NGINX 会立刻将文件的任何字节范畴申请转发到源服务器,直到缓存填充操作实现。
proxy_cache_lock_age
– 设置缓存填充操作的最初期限。如果操作没有在指定工夫内实现,NGINX 会再向源服务器转发一个申请。它总是须要比预期的缓存填充工夫更长,因而咱们将其从默认的 5 秒减少到 200 秒。proxy_cache_use_stale updating
– 如果 NGINX 正在更新资源,则通知 NGINX 立刻应用资源的以后缓存版本。这对第一个申请(触发缓存更新)没有影响,但会减速对客户端后续申请的响应。
咱们反复咱们的测试,申请 10Mb.txt
的两头字节范畴。该文件没有被缓存,并且与之前的测试一样,time
表明 NGINX 须要 5 秒多一点的工夫能力交付申请的字节范畴(回忆一下,网络的吞吐量限度为 1 Mb/s):
client # time curl -r 5000000-5000009 http://cache/10Mb.txt
005000000
real 0m5.422s
user 0m0.007s
sys 0m0.003s
因为缓存锁定,在缓存被填充时,后续对字节范畴的申请简直立刻失去满足。NGINX 将这些申请转发到源服务器,而不尝试从缓存中满足它们:
client # time curl -r 5000000-5000009 http://cache/10Mb.txt
005000000
real 0m0.042s
user 0m0.004s
sys 0m0.004s
在源服务器拜访日志的以下摘录中,带有状态代码的条目 206 确认源服务器在缓存填充操作实现期间正在解决字节范畴申请。(咱们应用该 log_format 指令将 Range 申请标头蕴含在日志条目中,以辨认哪些申请已批改,哪些未修改。)
最初一行,带有状态码 200,对应于第一个字节范畴申请的实现。NGINX 将此批改为对整个文件的申请并触发缓存填充操作。
192.168.56.10 - - [08/Dec/2015:12:18:51 -0800] "GET /10Mb.txt HTTP/1.0" 206 343 "-" "bytes=5000000-5000009" "curl/7.35.0"
192.168.56.10 - - [08/Dec/2015:12:18:52 -0800] "GET /10Mb.txt HTTP/1.0" 206 343 "-" "bytes=5000000-5000009" "curl/7.35.0"
192.168.56.10 - - [08/Dec/2015:12:18:53 -0800] "GET /10Mb.txt HTTP/1.0" 206 343 "-" "bytes=5000000-5000009" "curl/7.35.0"
192.168.56.10 - - [08/Dec/2015:12:18:54 -0800] "GET /10Mb.txt HTTP/1.0" 206 343 "-" "bytes=5000000-5000009" "curl/7.35.0"
192.168.56.10 - - [08/Dec/2015:12:18:55 -0800] "GET /10Mb.txt HTTP/1.0" 206 343 "-" "bytes=5000000-5000009" "curl/7.35.0"
192.168.56.10 - - [08/Dec/2015:12:18:46 -0800] "GET /10Mb.txt HTTP/1.0" 200 10486039 "-" "-" "curl/7.35.0"
当咱们在整个文件被缓存后反复测试时,NGINX 会从缓存中提供任何进一步的字节范畴申请:
client # time curl -r 5000000-5000009 http://cache/10Mb.txt
005000000
real 0m0.012s
user 0m0.000s
sys 0m0.002s
应用缓存锁能够优化缓存填充操作,但代价是在缓存填充期间将所有后续用户流量发送到源服务器。
逐片填充缓存
NGINX Plus R8 和 NGINX 开源 1.9.8 中引入的 Cache Slice 模块提供了另一种填充缓存的办法,当带宽受到重大限度并且缓存填充操作须要很长时间时,这种办法更无效。
编辑器 – 无关 NGINX Plus R8 中所有新性能的概述,请参阅咱们博客上的 NGINX Plus R8。
应用缓存切片办法,NGINX 将文件分成更小的段,并在须要时申请每个段。这些段在缓存中累积,并且通过将一个或多个段的适当局部传递给客户端来满足对资源的申请。对大字节范畴(或者实际上是整个文件)的申请会触发每个所需段的子申请,这些段在从源服务器达到时被缓存。一旦所有的段都被缓存了,NGINX 就会组装来自它们的响应并将其发送给客户端。
NGINX 缓存切片详解
启用 NGINX 缓存切片的申请解决
在上面的配置片段中,该 slice 指令(在 NGINX Plus R8 和 NGINX 开源 1.9.8 中引入)通知 NGINX 将每个文件分段为 1-MB 片段。
在应用 slice 指令时,咱们还必须将 $slice_range 变量增加到 proxy_cache_key
指令中以辨别文件的片段,并且咱们必须替换 Range 申请中的标头,以便 NGINX 从源服务器申请适当的字节范畴。咱们将申请降级为 HTTP/1.1 因为 HTTP/1.0 不反对字节范畴申请。
proxy_cache_path /tmp/mycache keys_zone=mycache:10m;
server {
listen 80;
proxy_cache mycache;
slice 1m;
proxy_cache_key $host$uri$is_args$args$slice_range;
proxy_set_header Range $slice_range;
proxy_http_version 1.1;
proxy_cache_valid 200 206 1h;
location / {proxy_pass http://origin:80;}
}
和以前一样,咱们申请 10Mb.txt 中的两头字节范畴:
client$ time curl -r 5000000-5000009 http://cache/10Mb.txt
005000000
real 0m0.977s
user 0m0.000s
sys 0m0.007s
NGINX 通过申请单个 1-MB 文件段(字节范畴 4194304–5242879)来满足申请,其中蕴含申请的字节范畴 5000000–5000009。
KEY: www.example.com/10Mb.txtbytes=4194304-5242879
HTTP/1.1 206 Partial Content
Date: Tue, 08 Dec 2015 19:30:33 GMT
Server: Apache/2.4.7 (Ubuntu)
Last-Modified: Tue, 14 Jul 2015 08:29:12 GMT
ETag: "a00000-51ad1a207accc"
Accept-Ranges: bytes
Content-Length: 1048576
Vary: Accept-Encoding
Content-Range: bytes 4194304-5242879/10485760
如果一个字节范畴申请逾越多个段,NGINX 会申请所有须要的段(尚未缓存),而后从缓存的段中组装字节范畴响应。
Cache Slice 模块是为交付 HTML5 视频而开发的,它应用字节范畴申请将内容伪流到浏览器。它十分实用于初始缓存填充操作可能须要几分钟的视频资源,因为带宽受到限制,并且文件在公布后不会更改。
抉择最佳切片大小
将切片大小设置为足够小的值,以便能够疾速传输每个段(例如,在一两秒内)。这将缩小多个申请触发上述继续更新行为的可能性。
另一方面,切片大小可能太小。如果对整个文件的申请同时触发数千个小申请,则开销可能会很高,从而导致内存和文件描述符应用过多以及磁盘流动更多。
此外,因为缓存切片模块将资源拆分为独立的段,因而一旦资源被缓存,就无奈更改资源。ETag 每次从源端接管到一个段时,该模块都会验证资源的标头,如果 ETag 产生更改,NGINX 会停止事务,因为底层缓存版本当初已损坏。咱们建议您仅对公布后不会更改的大文件(例如视频文件)应用缓存切片。
概括
如果您应用字节范畴交付大量资源,缓存锁定和缓存切片技术都能够最大限度地缩小网络流量并为您的用户提供杰出的内容交付性能。
如果缓存填充操作能够疾速执行,并且您能够在填充过程中承受到源服务器的流量峰值,请应用缓存锁定技术。
如果缓存填充操作十分慢且内容稳固(不更改),请应用新的缓存切片技术。