NGINX系列(七) 用NGINX实现缓存服务器(CDN)
NGINX系列(七) 用NGINX实现缓存服务器(CDN)
1 | 作者:李晓辉 |
环境介绍
操作系统 | IP地址 | 主机名 | NGINX版本 | 角色 |
---|---|---|---|---|
Rocky9.4 | 192.168.8.3 | loadbalance.xiaohui.cn | 1.26.2 | CDN缓存服务器 |
Rocky9.4 | 192.168.8.4 | web1.xiaohui.cn | 1.26.2 | 普通业务服务器/源站 |
Rocky9.4 | 192.168.8.5 | web2.xiaohui.cn | 1.26.2 | 普通业务服务器/源站 |
什么是内容分发网络CDN
CDN,内容分发网络,是一种分布式的服务器系统,旨在提高网站内容的传输速度和可靠性。它通过将内容复制并存储到多个地理位置的服务器上,让用户能够从离自己最近的服务器获取内容,从而提高访问速度。
在CDN中,有一些术语需要了解:
CDN节点和源站是内容分发网络(CDN)中的两个重要概念。
CDN节点
CDN节点(或称边缘节点)是分布在全球各地的服务器,这些服务器缓存网站内容并将其传输给靠近用户的设备。通过将内容存储在多个地理位置的节点上,CDN能够显著减少延迟,提升内容加载速度。例如,当你在中国访问一个位于美国的服务器上的网站时,CDN节点可以将内容从一个离你更近的节点传输给你,从而加快访问速度。
CDN节点的主要优点包括:
减少延迟:由于节点靠近用户,数据传输距离短,因此响应时间更快。
提高可靠性:多个节点可以提供冗余,避免单点故障。
分担负载:通过将流量分布到不同节点上,减轻源站的负载压力。
源站
源站是网站内容的原始服务器,它存储和提供所有的内容。当CDN节点无法从缓存中获取所需内容时,会向源站请求这些内容并将其缓存到节点中。源站通常位于数据中心,负责处理所有内容的更新和管理。例如,源站可能位于一个网站的主服务器上,负责存储所有的网页、图片和视频文件。
源站的主要职责包括:
提供原始内容:当CDN节点没有缓存内容时,源站是内容的唯一来源。
内容管理:源站负责所有内容的更新、修改和删除。
处理请求:在缓存失效或更新时,源站接收并处理来自CDN节点的请求。
NGINX缓存功能介绍
NGINX作为一个高性能的反向代理服务器,可以轻松配置来实现缓存功能。通过缓存,NGINX可以在客户端请求内容时,从本地缓存中直接返回内容,而不需要每次都访问源服务器,这大大减轻了源服务器的负载并提高了响应速度。
使用NGINX作为缓存服务器可以大幅提高网站的响应速度,并减轻源服务器的压力。然而,也需要注意缓存的管理和更新策略,确保用户能够获取到最新的内容,我们本篇文章就是用NGINX来实现一下缓存服务器,用实战案例来帮助大家理解整个过程。
当大量客户端同时向缓存服务器请求同一内容,而此内容尚未被缓存服务器存储时,所有请求会直接提交给源服务器,这可能导致源服务器压力过大。为了解决这个问题,NGINX 提供了 proxy_cache_lock
参数。此参数允许第一个请求去访问源服务器并将内容缓存到本地,其余请求会在本地缓存可用后,从缓存中获取该内容,从而减少源服务器的压力。
NGINX缓存服务器实战案例
常规缓存案例
我们的配置文件如下:
1 | cat > /etc/nginx/conf.d/cache.conf <<'EOF' |
proxy_cache_path:
/data/cache: 指定缓存存储路径。
levels=1:2: 定义缓存文件的目录层级结构,第一层为1位,第二层为2位,这有助于防止单个目录中的文件过多。
keys_zone=lixiaohui:20m: 定义一个名为“lixiaohui”的共享内存区域,用于存储缓存键,大小为20MB。
max_size=50g: 定义缓存区域的最大存储空间为50GB。
inactive=168h: 如果缓存文件在168小时(7天)内未被访问,则将其删除。
server 块:
listen 80 default_server: 在80端口上监听,并将此服务器设置为默认服务器。
server_name cache.xiaohui.cn: 指定服务器名称为
loadbalance.xiaohui.cn
。
location / 块:
proxy_cache lixiaohui: 使用名为“lixiaohui”的缓存区域。
proxy_cache_valid any 168h: 对任何响应代码缓存168小时(7天)。
proxy_pass http://host2.xiaohui.cn: 将请求转发到后端服务器
http://web1.xiaohui.cn
。proxy_cache_lock on: 启用缓存锁定,以防止多个相同请求同时访问后端服务器。
proxy_cache_lock_age 3s: 缓存锁的最大持续时间为3秒。
proxy_cache_lock_timeout 6s: 请求等待缓存锁的最大时间为6秒。
X-Cache-Status
,用于指示响应是从缓存中获取的还是通过代理服务器获取的。
启动服务
将缓存目录建立好,然后启动服务
1 | mkdir -p /data/cache |
1 | systemctl restart nginx |
验证效果
这一次访问,我们发现X-Cache-Status: MISS
,也就是说,没有命中缓存服务器,本次内容从源站获得
1 | [root@loadbalance ~]# curl -I http://loadbalance.xiaohui.cn/ |
这一次访问,我们发现X-Cache-Status: HIT
,也就是说,这一次命中缓存服务器,本次内容从源站获得
1 | [root@loadbalance ~]# |
我们看看缓存目录中,是否有了本次内容
果然,有了我们的web1 page
这个后端页面,并缓存成功
1 | [root@loadbalance ~]# ls /data/cache/ -R |
优化缓存锁定案例
当大量客户端同时向缓存服务器请求某个内容,而该内容尚未被缓存服务器缓存时,这些请求会同时提交到真实服务器,导致真实服务器压力过大。这种情况下,可能会引发性能问题甚至服务器崩溃。为了解决这个问题,NGINX 提供了 proxy_cache_lock
参数。这个参数的作用是,只允许第一个请求访问真实服务器并缓存内容,而其他后续请求则会等待第一个请求完成并从缓存中获取内容。
具体来说,当 proxy_cache_lock
参数启用后,如果多个客户端同时请求一个尚未缓存的内容,NGINX 会锁定第一个请求,使其能够访问真实服务器并缓存返回的内容。其他请求则会被挂起,直到第一个请求完成并将内容缓存到本地。这样一来,只有一次请求会直接到达真实服务器,极大地减轻了真实服务器的负载,提升了系统的性能和稳定性。
简而言之,启用 proxy_cache_lock
参数可以有效防止缓存失效时的高并发请求同时涌向真实服务器,从而保护服务器资源并提高网站的响应速度。
下面这个配置文件定义了一个 NGINX 服务器,监听 loadbalance.xiaohui.cn
域名上的80端口。请求被转发到上游服务器 http://web1.xiaohui.cn
并被缓存。缓存路径为 /data/cache
,最大大小为50GB。如果缓存未命中,NGINX 将锁定第一个请求访问上游服务器,其他请求等待并从缓存中获取内容,避免对上游服务器的过大压力。自定义响应头 X-Cache-Status
用于指示响应是否来自缓存,以方便进行调试和监控。
1 | cat > /etc/nginx/conf.d/cache.conf <<'EOF' |
proxy_cache_path
/data/cache: 缓存存储的路径。
levels=1:2: 缓存目录的层次结构。第一个层级有1个字符长的子目录,第二个层级有2个字符长的子目录。
keys_zone=lixiaohui:20m: 定义一个名为
lixiaohui
的缓存区域,大小为20MB,用于存储缓存键及其元数据。max_size=50g: 缓存最大大小为50GB。
inactive=168h: 规定缓存内容在168小时(7天)内没有访问时会被自动移除。
server 块
listen 80 default_server: 服务器监听80端口,作为默认服务器。
server_name loadbalance.xiaohui.cn: 服务器的域名为
loadbalance.xiaohui.cn
。
location 块
proxy_cache lixiaohui: 启用
lixiaohui
缓存区域。proxy_cache_valid any 168h: 对所有响应类型设置缓存有效期为168小时(7天)。
proxy_pass http://web1.xiaohui.cn: 将请求转发到上游服务器
http://web1.xiaohui.cn
。proxy_cache_lock on: 启用缓存锁定,防止缓存失效时多个请求同时访问上游服务器。
proxy_cache_lock_age 3s: 锁定时间为3秒。
proxy_cache_lock_timeout 6s: 锁定请求的超时时间为6秒。
add_header X-Cache-Status $upstream_cache_status always
: 添加自定义响应头X-Cache-Status
,指示响应是否来自缓存,其中$upstream_cache_status
可为HIT
、MISS
或BYPASS
等状态。
测试效果
这里是步骤要点:
准备测试环境:
- 确保 NGINX 配置已正确加载并运行。
发送并发请求:
- 使用ab工具发送并发请求,模拟大量客户端同时请求未缓存的内容。
观察效果:
查看上游服务器的请求日志,确认只有第一个请求直接传递到上游服务器,其他请求从缓存中获取内容。
查看 NGINX 的访问日志,确认缓存命中情况。
第一步,我们先清空缓存目录,并重启nginx,确保没有任何缓存
1 | rm -rf /data/cache/* |
第二步,发送20个请求
1 | ab -n 20 -c 20 http://loadbalance.xiaohui.cn/ |
看看请求结果和是否缓存
1 | ab -n 20 -c 20 http://loadbalance.xiaohui.cn/ |
1 | This is ApacheBench, Version 2.3 <$Revision: 1913912 $> |
我们来看看是否生成了缓存
1 | [root@loadbalance ~]# ls /data/cache/ -R |
第三步,观察结果,登录web1服务器,然后看看日志,一共有几个请求过来
经过时间的对比,发现就只有一个流量到达源站,其他的19个请求并没有真的发向源站,的确减轻了源站压力
1 | [root@web1 ~]# tail /var/log/nginx/access.log -f |
定义和优化缓存key
缓存key概述
proxy_cache_key
是一个用于定义缓存键的指令。缓存键是 NGINX 用来唯一标识缓存内容的键值。当请求到达 NGINX 时,它会根据缓存键来查找对应的缓存内容。proxy_cache_key
的值通常由多个变量构成,如 URL、主机名、查询参数等,以确保每个请求都有唯一的缓存键。
proxy_cache_key
的主要作用是通过定义唯一的缓存键来管理缓存内容。通过指定不同的缓存键,可以灵活地控制缓存策略。例如,可以为同一 URL 的不同查询参数生成不同的缓存内容,避免缓存冲突。此外,通过定制缓存键,还可以对特定条件下的请求进行精准缓存。
应用场景
按 URL 缓存:
默认情况下,
proxy_cache_key
使用$scheme$proxy_host$request_uri
作为缓存键,即使用协议、主机名和请求路径。这种设置适用于大多数普通请求,每个不同的 URL 都会有独立的缓存。
proxy_cache_key
使用协议、主机名和请求路径作为缓存键。
1 | proxy_cache_path /data/cache levels=1:2 keys_zone=lixiaohui:20m max_size=50g inactive=168h; |
按查询参数缓存:
这种配置适用于需要对不同查询参数生成不同缓存内容的情况。例如,如果请求的 URL 为 http://loadbalance.xiaohui.cn/path?param1=value1¶m2=value2
,那么缓存键将包括查询参数,从而确保每个不同的查询参数组合都有独立的缓存内容。
- 当 URL 具有多个查询参数时,可以通过
proxy_cache_key
指定哪些查询参数会影响缓存键。例如:
1 | proxy_cache_path /data/cache levels=1:2 keys_zone=lixiaohui:20m max_size=50g inactive=168h; |
按用户代理缓存:
这种配置适用于需要为不同客户端生成独立缓存内容的情况。例如,如果一个请求来自移动设备,另一个请求来自桌面浏览器,即使请求的 URL 相同,缓存键也会因用户代理不同而不同,从而确保为每个客户端生成独立的缓存内容。
在这种情况下,即使请求的 URL 相同,两个请求的缓存键会不同,因为用户代理不同。这样可以为移动设备和桌面设备分别生成和缓存内容,确保每个设备类型都获得最佳的用户体验。例如,移动端可能会接收到经过优化的响应,适配较小屏幕和较慢的网络连接,而桌面端则可能接收到更高分辨率的内容。
- 可以根据用户代理(User-Agent)定义缓存键,以便为不同的客户端生成不同的缓存内容。例如,针对移动和桌面浏览器的缓存:
1 | proxy_cache_path /data/cache levels=1:2 keys_zone=lixiaohui:20m max_size=50g inactive=168h; |
多语言支持:
这种配置适用于支持多语言的网站。通过将语言参数包含在缓存键中,确保为每种语言生成独立的缓存内容。例如,如果请求的 URL 为 http://loadbalance.xiaohui.cn/path?lang=zh
,则缓存键会包含 lang=zh
,从而生成中文内容的缓存。同样,如果请求的 URL 为 http://loadbalance.xiaohui.cn/path?lang=en
,则缓存键会包含 lang=en
,从而生成英文内容的缓存。
- 对于支持多语言的网站,可以根据语言参数生成不同的缓存键。例如:
1 | proxy_cache_path /data/cache levels=1:2 keys_zone=lixiaohui:20m max_size=50g inactive=168h; |
跳过缓存实战案例
在一些场景中,可能需要绕过缓存来获取最新的内容。例如,当管理员在调试或更新网站时,或者当用户需要最新的实时数据时,可以通过在请求中添加特定的头部信息来绕过缓存。通过使用 proxy_cache_bypass
指令,你可以根据这些头部信息灵活地控制缓存策略。
- 当管理员或开发人员在调试或更新网站时,可以在请求中添加特定的头部信息,例如
X-Cache-Bypass: 1
,来确保获取最新的内容。
1 | cat > /etc/nginx/conf.d/cache.conf <<'EOF' |
验证效果
访问两次,发现第二次开始,缓存状态就是HIT
1 | curl -I http://loadbalance.xiaohui.cn/ |
我们加入一个X-Cache-Bypass: 1
,发现已经绕过缓存了
1 | [root@loadbalance ~]# curl -I -H "X-Cache-Bypass: 1" http://loadbalance.xiaohui.cn/ |
缓存切片
缓存切片是一种通过将大文件分割成较小的片段来减少客户端和服务器负载的技术。这样,客户端和服务器不需要一次性处理整个大文件,而是处理多个较小的文件块。这种方法有效地降低了传输大文件的压力,提高了传输效率和可靠性。
通过缓存切片,客户端可以根据需要请求固定大小的片段,而服务器则根据这些请求提供相应的片段。这种方式不仅优化了网络传输,还提高了缓存的命中率,因为较小的片段更容易被缓存和复用。
配置文件
在这个配置中,我们把大文件切成了1M的小文件进行传输
1 | cat > /etc/nginx/conf.d/cache.conf <<'EOF' |
缓存切片相关指令
proxy_cache_use_stale
:作用:在上游服务器返回指定的错误或超时时,使用过期的缓存内容响应客户端。
参数:
error
:在出现错误时使用过期缓存。timeout
:在上游服务器响应超时时使用过期缓存。updating
:当缓存正在更新时使用过期缓存。http_500
、http_502
、http_503
、http_504
:当上游服务器返回这些 HTTP 状态码时使用过期缓存。
proxy_cache_background_update
:作用:启用后台异步更新缓存功能,当缓存过期时,缓存会在后台更新。
参数:
on
启用后台更新。
proxy_cache_min_uses
:作用:在内容被缓存前,请求次数必须达到的最小值。
参数:
1
表示内容至少被请求一次后才会被缓存。
proxy_cache_bypass
:作用:指定哪些请求不应使用缓存。
参数:
$http_pragma
基于Pragma
请求头的值决定是否绕过缓存。通常用于防止客户端缓存内容(例如在调试时使用Pragma: no-cache
)。
proxy_cache_lock
:作用:防止缓存失效时的高并发请求同时涌向上游服务器。
参数:
on
启用缓存锁定。
slice
:作用:启用缓存切片,将大文件分割成较小的片段。
参数:
1m
表示每个请求的片段大小为 1MB。
proxy_set_header Range $slice_range
:作用:设置请求头,指定请求的范围。
参数:
Range $slice_range
设置请求范围头。
proxy_cache_key $uri$is_args$args$slice_range
:作用:定义缓存键,包含 URI、查询参数和切片范围。
参数:
$uri
:请求的 URI。$is_args
:如果存在查询字符串,则返回问号?
,否则为空字符串。$args
:查询字符串(查询参数)。$slice_range
:切片范围。
add_header
定义:添加响应头。
参数:
X-Cache-Status $upstream_cache_status always
:添加自定义响应头X-Cache-Status
,指示响应是否来自缓存。
location @slice
定义:处理内部切片请求。
参数:
internal
:标识该位置为内部使用,仅用于代理内部请求。proxy_pass http://192.168.8.4
:将切片请求代理到上游服务器。proxy_set_header Range $slice_range
:设置切片请求的范围头。
测试效果
先在web1服务器上创建一个大文件
1 | [root@web1 ~]# dd if=/dev/random of=/usr/share/nginx/html/index.html bs=10M count=10 |
我们去访问缓存服务器,看看效果
可以看到 http响应码是206 Partial Content,Content-Length: 1048576,Content-Range: bytes 0-1048575/104857600,我们请求的长度是0-1048575,本文件最长为104857600
1 | [root@loadbalance ~]# curl -I -H "Range: bytes=0-1048575" http://loadbalance.xiaohui.cn/index.html |
可以看到本次访问是第一次访问,并未命中缓存,我们去web1上看一下日志,发现代码为206,返回给缓存服务器的长度为1048576
1 | [root@web1 ~]# tail /var/log/nginx/access.log -f |