前言
开启 HTTP 缓存是提升前端性能的常见手段之一。通过缓存,浏览器可以临时存储资源,在后续请求中直接使用本地副本,从而有效减少 HTTP 请求次数,显著缩短网页加载时间。以下是 HTTP 缓存的几个关键点:
1、减少重复请求:浏览器存储已请求过的资源,避免重复请求服务器。
2、设置缓存头:通过服务器端的 Cache-Control、Expires 或 ETag 响应头,指导浏览器如何缓存资源。
3、区分静态与动态资源:静态资源(如图片、CSS、JS 文件)可以设置更长的缓存时间,动态资源则需较短的缓存时间或采用验证缓存策略。
4、利用浏览器缓存:配置服务端缓存策略,让浏览器自动处理资源缓存。
5、更新缓存策略:资源更新时,可以通过修改文件名或使用 Last-Modified 和 ETag 验证缓存。
6、监控缓存效果:借助开发者工具监控资源缓存情况,确保缓存策略按预期生效。
NGINX 服务器端配置 HTTP 缓存
与启用 Gzip 压缩类似,HTTP 缓存的开启主要通过 NGINX 服务器的配置完成。本文将以 NGINX 为例,介绍如何高效配置 HTTP 缓存。
配置 Expires 头 ,设置一个长久的 Expires ,采用高效的缓存政策提供静态资源
-
cache_expiration.conf
建议将缓存配置单独存储到文件(如 cache_expiration.conf),并通过 nginx.conf 引入:pyhttp { # 其它配置 # Specify file cache expiration. include web_performance/cache_expiration.conf; }
cache_expiration.conf 示例:
pymap $sent_http_content_type $expires { default 1M; # 默认缓存 1 个月 '' off;# 无内容 ~*text/css 1y;# CSS 缓存 1 年 # Data interchange ~*application/atom\+xml 1h; ~*application/rdf\+xml 1h; ~*application/rss\+xml 1h; ~*application/json 0; # JSON 不缓存 ~*application/ld\+json 0; ~*application/schema\+json 0; ~*application/geo\+json 0; ~*application/xml 0; ~*text/calendar 0; ~*text/xml 0; # Favicon (cannot be renamed!) and cursor images ~*image/vnd.microsoft.icon 1w; ~*image/x-icon 1w; # HTML ~*text/html 0; # JavaScript ~*application/javascript 1y; ~*application/x-javascript 1y; ~*text/javascript 1y; # Manifest files ~*application/manifest\+json 1w; ~*application/x-web-app-manifest\+json 0; ~*text/cache-manifest 0; # Markdown ~*text/markdown 0; # Media files ~*audio/ 1M; ~*image/ 1M; ~*video/ 1M; # WebAssembly ~*application/wasm 1y; # Web fonts ~*font/ 1M; ~*application/vnd.ms-fontobject 1M; ~*application/x-font-ttf 1M; ~*application/x-font-woff 1M; ~*application/font-woff 1M; ~*application/font-woff2 1M; # Other ~*text/x-cross-domain-policy 1w; } # 时间根据变量 $expires 的配置匹配 expires $expires;
-
区分资源类型和缓存时长
静态资源(CSS、JS、图片等)设置较长的缓存时间。
动态内容(如 JSON、HTML)设置较短缓存时间或禁用缓存。.css、.js 文件缓存 1 年。 .json 数据不缓存。
-
cache-control.conf
可通过 cache-control.conf 设置 Cache-Control 头:pymap $sent_http_content_type $cache_control { default 'public, immutable, stale-while-revalidate'; # No content '' 'no-store'; # Manifest files ~*application/manifest\+json 'public'; ~*text/cache-manifest ''; # `no-cache` (*) # Assets ~*image/svg\+xml 'public, immutable, stale-while-revalidate'; # Data interchange ~*application/(atom|rdf|rss)\+xml 'public, stale-while-revalidate'; # Documents ~*text/html 'private, must-revalidate'; ~*text/markdown 'private, must-revalidate'; ~*text/calendar 'private, must-revalidate'; # Data ~*json ''; # `no-cache` (*) ~*xml ''; # `no-cache` (*) }
在 nginx.conf 中引入:
pyhttp { # 省略其它配置... # Add Cache-Control. include web_performance/cache-control.conf; }
HTTP 缓存配置完毕后,在我们请求的静态资源的服务器响应头中就可以看到 Expires 首部字段信息了:
不怎么变动的资源,尽量给一个长久的 Expires。HTML 页面通常视作动态资源 ,建议是不设置 Expires 头,否则在指定时间内永远没法取到更新后的 js 和 css 或者其它静态资源。HTML 缓存另一个策略是:不缓存html
如果不希望配置一个全局的静态资源的 Expires 头,可以去掉cache_expiration.conf文件最底部的配置:
c
# expires $expires
调整后,在 nginx.conf 配置文件中引入的就只是针对不同静态文件过期时间变量 $expires 的配置。这样就不会出现 NGINX 服务器上所有配置的所有 Web 站点都使用相同的 Expires 头的缓存配置,但又都可以访问 $expires 变量了。
HTTP 缓存相关首部字段
-
Expires 首部字段
Expires 用来指定资源的过期时间,格式为 HTTP 日期。示例:
pyWed, 23 Jul 2025 12:37:27 GMT
Expires 头要求服务器和客户端的时间要严格同步。如果本地电脑调整了时间,超过了 Exipres 头设置的时间,也会使缓存过期。 另外,Exipres 头会经常检测过期时间,并且一旦过期了,又需要再服务器中配置提供一个新的日期。所以会有 Exipres 头设置的时间尽量长的策略。
注意:Expires 依赖客户端与服务器时间同步,不够精确,现代项目更推荐使用 Cache-Control。
-
Cache-Control 首部字段
Cache-Control 使用 max-age 指令指定组件被缓存多久。它以秒为单位定义更新时间。如果从资源被请求开始过去的秒数小于 max-age,浏览器就会使用缓存版本。它可以消除 Expires 头对于服务器和客户端的时间必须同步的限制。
对于不支持 Cache-Control 首部字段的浏览器,我们仍然需要 Expires 头,因此一般都会同时设置 Cache-Control 和 Expires 首部字段。 在支持 Cache-Control 头的浏览器,max-age 指定将重写 Expires 头。 max-age 指定的设置策略也和 Expires 头一致,尽量设置一个较长的时间。最大可以设置 10 年,一般都设置至少 30 天以上。
注意:
缓存持续时间过长的一个风险就是您的用户不会看到静态文件的更新。若要避免此问题,可以将构建工具配置为在静态资源文件名中嵌入一个哈希,以使每个版本都是唯一的,从而提示浏览器从服务器提取新版本。
Cache-Control
提供更灵活的缓存机制。常见指令包括:-
public
:允许资源被所有用户缓存,包括 CDN。 -
private
:资源只能被客户端缓存,不能被代理服务器缓存。 -
no-cache
:每次请求都需向服务器验证缓存是否有效。 -
no-store
:禁止缓存。 -
immutable
:资源不会变化,避免重复验证。 -
max-age=秒数
:设置缓存的最大有效期。public:表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存。表示相应会被缓存,并且在多用户间共享。默认是 public。
private:表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它),可以缓存响应内容。响应只作为私有的缓存,不能在用户间共享。如果要求HTTP认证,响应会自动设置为private。
no-cache:在释放缓存副本之前,强制高速缓存将请求提交给原始服务器进行验证。指定不缓存响应,表明资源不进行缓存。但是设置了no-cache之后并不代表浏览器不缓存,而是在缓存前要向服务器确认资源是否被更改。因此有的时候只设置no-cache防止缓存还是不够保险,还可以加上private指令,将过期时间设为过去的时间。
only-if-cached:表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新的拷贝.
no-store:缓存不应存储有关客户端请求或服务器响应的任何内容。表示绝对禁止缓存!
no-transform:不得对资源进行转换或转变。Content-Encoding, Content-Range, Content-Type 等 HTTP 头不能由代理修改。例如,非透明代理可以对图像格式进行转换,以便节省缓存空间或者减少缓慢链路上的流量。no-transform 指令不允许这样做。
示例:
clocation / { proxy_pass http://aaa; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 省略其它配置... # 不缓存 index.html expires -1; add_header Cache-Control no-store; # fix history router model in VUE try_files $uri $uri/ /index.html; error_page 404 /index.html; }
在 NGINX 服务器中配置了 Expires 头,在配置反向代理指定了 HTTP1.1 协议后,NGINX 服务器会使用 Cache-Control 头重写 Expires 头,过期时间就是 Expires 头配置的时间。因此如果不是像 HTML 文件这样需要禁止缓存,没有额外的 Cache-Control 头配置。
如果资源变化和新鲜度很重要,但仍想获得缓存的一些速度优势,请使用 no-cache。浏览器仍会缓存设置为 no-cache 的资源,但会先向服务器进行检查,以确保该资源仍为最新资源。
-
-
Last-Modified 与 ETag
服务器在检测缓存的资源是否和原始服务器上的资源匹配,使用的是: 比较最新修改日期(Last-Modified Date) 比较实体标签(ETag)
客户端会在后续请求中使用 If-Modified-Since 验证缓存是否更新。
cLast-Modified: Tue, 25 Jun 2024 20:20:53 GMT
设置了 Expires 头后,浏览器会缓存资源和它的最新修改日期。再次请求同一资源时,浏览器会使用 If-Modified-Since 头将最新修改日期回传到原始服务器进行比较。如果匹配则返回 304 响应,而不会重新下载组件。
-
ETag
ETag:为资源生成唯一标识符,用于缓存校验。示例:
cETag: "5d8c72a3-3e8"
客户端会在后续请求中使用 If-None-Match 验证。
它是提供了另一种方式检测缓存的资源是否与原始服务器上的资源是否匹配。 ETag 的检测机制要比最新修改时间更加灵活。例如,如果实体依据 User-Agent 或者 Accept-Language 头而改变,实体的状态可以反应在 ETag 中。也就是说 ETag 的唯一字符串的值会发生改变。 ETag 的验证机制是在次访问同一资源的时候,它会使用 If-None-Match 头将 ETag 传回原服务器。如果匹配则返回 304 响应,而不会重新下载资源。
-
ETag 的问题
通常的 Web 服务器的架构设计都是做了高可用的配置的,是由多台服务器组成的集群构建而成。例如我本地的测试站点的负载均衡的配置:
cupstream cc { server 127.0.0.1:8080; server 127.0.0.1:8081; server 127.0.0.1:8082; }
ETag 有个问题,当浏览器分别从两台不同的后端集群服务器中请求同一资源的时候,两台不同的服务器的 ETag 是不会一致的。
当然,我们可以通过在负载平衡的配置中添加 keepalive 或者设置服务器的 weight 权重,让同一客户端尽量从同一服务器获取资源,但还是无法保证会切换服务器请求资源。
-
示例
以下是一个整合了 Expires 和 Cache-Control 的完整示例:
nginxhttp { include mime.types; default_type application/octet-stream; # 静态资源缓存策略 map $sent_http_content_type $expires { default 1M; ~*text/css 1y; ~*application/javascript 1y; ~*image/ 1M; ~*text/html 0; } map $sent_http_content_type $cache_control { default 'public, immutable, max-age=2592000'; ~*text/html 'private, must-revalidate'; ~*application/json 'no-cache'; } server { listen 80; server_name example.com; location / { root /var/www/html; index index.html; expires $expires; add_header Cache-Control $cache_control; } } }
注意事项
-
文件名版本化 :静态资源文件更新时,建议在文件名中添加版本号(如
style.v1.css
),避免用户加载旧缓存。 -
开发环境禁用缓存 :在开发模式下,可通过浏览器禁用缓存,方便调试。
-
使用工具检查缓存:可以使用浏览器开发者工具(如 Chrome DevTools)分析缓存行为。
NGINX 配置反向代理缓存
普通的静态资源的 HTTP 缓存外,我们还可以配置反向代理缓存(将服务器集群中的原始资源缓存到代理服务器上),将资源都缓存到 NGINX 服务器所在的代理服务器上。
如图,用户第一次请求资源,NGINX 服务器会向集群中的服务器请求资源,然后缓存下来。
用户再次请求数据的时候,如果 NGINX 服务器已经缓存了,NGINX 服务器就会直接响应,而不用再向上游的服务器集群的服务器请求资源了。这样就进一步优化了请求的响应速度,也更进一步的优化了前端性能。
-
配置反向代理缓存
要配置反向代理缓存,需要在 NGINX 服务器上配置一个缓存区域,指定缓存路径,目录层级,共享内存的大小等信息。
-
proxy_cache_path 指令
proxy_cache_path 是 Nginx 中用于配置反向代理缓存的指令。它定义了缓存存储的位置、缓存大小、缓存的各种参数等。反向代理缓存可以极大地提高性能,减少对后端服务器的负载。
使用独立的 proxy_cache.conf 文件保存 proxy_cache_path 配置,然后在需要的地方 include 配置;
pyproxy_cache_path ./cache levels=1:2 keys_zone=cache_static:100m inactive=1h max_size=300m use_temp_path=off;
proxy_temp_path=./cache: 缓存临时目录路径;
levels=1:2: 缓存目录地层级,默认所有缓存文件都放在同一个目录下,从而影响缓存的性能,大部分场景推荐使用2级目录来存储缓存文件;
keys_zone=cache_static:100m: 在共享内存中设置一块存储区域来存放缓存的 key 和 metadata(类似使用次数),这样 nginx 可以快速判断一个 request 是否命中或者未命中缓存,1m 可以存储 8000 个 key,100m 可以存储 800000 个 key;
max_size=300m: 最大 cache 空间,如果不指定,会使用掉所有磁盘空间(disk space),当达到配额后,会删除最少使用的 cache 文件;
inactive=1d: 未被访问文件在缓存中保留时间,本配置中如果 60 分钟未被访问则不论状态是否为 expired,缓存控制程序会删掉文件,默认为10分钟;需要注意的是,inactive 和 expired 配置项的含义是不同的,expired 只是缓存过期,但不会被删除,inactive 是删除指定时间内未被访问的缓存文件;
use_temp_path=off: 如果为 off,则 nginx 会将缓存文件直接写入指定的 cache 文件中,而不是使用 temp_path 存储,official 建议为 off,避免文件在不同文件系统中不必要的拷贝;
-
nginx.conf 引用代理缓存配置
proxy_cache_path 是一个全局性的配置,通常会在 nginx.conf 配置文件中使用 include 方式引入配置:
chttp { # 省略其它配置... # 代理缓存配置 include web_performance/proxy_cache.conf; }
这样只要是此 NGINX 服务配置的 Web 站点,就都可以引用 proxy_cache.conf 的代理缓存配置了。
-
为 Web 站点的静态资源配置反向代理缓存
py# 上游的服务器负载均衡配置 include upstreams/www.yao.com.conf; server { listen [::]:80; listen 80; server_name www.cc.com; # 将 http 请求访问,跳转到相应的 https 访问路径 return 301 https://$host$request_uri; } server { # 下次介绍配置 HTTPS 和 HTTP2 # listen [::]:443 ssl http2; listen 443 ssl http2; server_name www.yao.com; # 配置证书信息 include ssl/ssl_engine.conf; include ssl/default_certificate_files.conf; include ssl/policy_intermediate.conf; # 针对首页的配置 location / { proxy_pass http://yao; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_intercept_errors on; proxy_next_upstream error timeout invalid_header http_500; proxy_connect_timeout 2; add_header X-Upstream $upstream_addr; proxy_pass_header Authorization; client_body_in_file_only clean; client_body_buffer_size 32K; client_max_body_size 150M; # 不缓存 index.html expires -1; add_header Cache-Control no-store; # fix history router model in VUE try_files $uri $uri/ /index.html; error_page 404 /index.html; } # 访问前端站点的静态文件的代理配置 # 根据自己的需要添加静态资源的后缀名 location ~* \.(js|css)$ { proxy_pass http://yao; # 通用的一些反向代理配置 proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 设置资源缓存的 zone,使用之前配置的 cache_static proxy_cache cache_static; # 设置缓存的 key proxy_cache_key $host$uri$is_args$args; # 设置状态码为 200 和 304 的响应可以进行缓存,并且缓存时间为 10 分钟 # 根据自己的站点情况配置时长,时间台长 cache_static 空间很快会消耗完 proxy_cache_valid 200 304 10m; # 调整原服务器的缓存配置 proxy_ignore_headers Expires Set-Cookie Cache-Control; proxy_hide_header Cache-Control; proxy_hide_header Set-Cookie; # 调整原服务器的 Accept-Encoding proxy_set_header Accept-Encoding 'gzip'; # 被多次用到的资源才缓存,只用一次的无需代理缓存 proxy_cache_min_uses 2; # 配置自定义头 X-Cache 显示代理缓存命中状态 add_header X-Cache $upstream_cache_status; # 根据之前的 $expires 匹配的资源文件类型设置过期时间 # 当然,你也可以自己根据需要,直接设置一个合适的过期时间 expires $expires; add_header Cache-Control 'public, no-transform'; } }
-
$upstream_cache_status 变量监测代理缓存状态
NGINX 提供了
$upstream_cache_status
这个变量来显示缓存的状态.在这里的配置中添加了一个自定义的 X-Cache 头,使用$upstream_cache_status
监测代理缓存的状态。以下是$upstream_cache_status
的可能值:MISS - 在缓存中找不到响应,因此从原始服务器获取。然后可以缓存响应; BYPASS - 响应是从原始服务器获取的,而不是从缓存中提供的,因为请求与proxy_cache_bypass指令匹配(请参阅下面的"我可以通过我的缓存打孔吗?")然后可以缓存响应; EXPIRED - 缓存中的条目已过期。响应包含来自源服务器的新内容; STALE - 内容过时,因为原始服务器未正确响应,并且已配置proxy_cache_use_stale; UPDATING- 内容过时,因为当前正在更新条目以响应先前的请求,并且配置了proxy_cache_use_stale更新; REVALIDATED - 启用了proxy_cache_revalidate指令,NGINX验证当前缓存的内容仍然有效(If-Modified-Since或If-None-Match); HIT - 响应包含直接来自缓存的有效新鲜内容;
默认情况下,NGINX 尊重源服务器的 Cache-Control 头。它不会缓存响应,缓存控制设置为 Private,No-Cache 或 No-Store 或响应头中的 Set-Cookie。NGINX 仅缓存 GET 和 HEAD 客户端请求。
py# 调整原服务器的缓存配置 proxy_ignore_headers Expires Set-Cookie Cache-Control; proxy_hide_header Cache-Control; proxy_hide_header Set-Cookie;
这段配置就是为了启用代理缓存,用以忽略源服务器的 Cache-Control 头。
另外,还特别添加了NGINX 服务器自己的 Cache-Control 头的配置:
pyadd_header Cache-Control 'public, no-transform';
表示允许响应被被缓存,并且在多用户间共享。不得对资源进行转换或转变。
再看看 X-Cache 头,也就是 $upstream_cache_status 的值已经是 HIT 状态,表示该静态资源已经被代理缓存命中了。反向代理缓存存储如下:
从截图我们可以看到,配置的2级缓存已经启用了。缓存的文件是二进制的内容,截取一段用文本编辑器打开的数据为:
这里的KEY:
pyKEY: www.yao.com/js/909.86428264.js
正是我们配置的反向代理缓存的中 proxy_cache_key 配置的数据格式:
py# 设置缓存的 key proxy_cache_key $host$uri$is_args$args;
-
proxy_cache_purge 清除反向代理缓存
若要手动清除缓存,可以使用 proxy_cache_purge 模块。配置代码如下:
py# 用于清除缓存,假设一个URL为: https://www.yao.com/#/default # 访问 https://www.cc.com/#/purge/defaut 就可清除该URL的缓存 location ~ /purge(/.*) { # 设置只允许指定的IP或IP段才可以清除URL缓存。 allow 127.0.0.1; deny all; proxy_cache_purge cache_static $host$uri$is_args$args; }
这个清理缓存的路径应该只有特定(运维)人员有权限访问,清理缓存。
-
编译安装 Nginx 并且添加 ngx_cache_purge 模块
另外,proxy_cache_purge 不是 NGINX 服务器自带的指令模块,需要手动下载编译安装。
第1步:获取 nginx
在 /etc/nginx/source 下执行,具体是在那个目录,可以自行决定:
pywget http://nginx.org/download/nginx-1.21.0.tar.gz tar -zxvf nginx-1.12.2.tar.gz
第2步:获取 ngx_cache_purge
在 /etc/nginx/source 下执行,具体是在那个目录,可以自行决定,这里建议与下载的 nginx 目录一致:
pywget https://github.com/FRiCKLE/ngx_cache_purge/archive/2.3.tar.gz tar -zxvf 2.3.tar.gz
第3步:修改 nginx 安装配置
py./configure --prefix=/etc/nginx \ --with-http_stub_status_module \ --with-http_ssl_module --with-stream \ --with-http_gzip_static_module \ --with-http_sub_module \ --with-pcre \ --add-module=../ngx_cache_purge
--add-module=.../ngx_cache_purge 这是新增的,如果要查看以前的 configure 参数,可以使用以下命令查看,然后复制后再后边加入需要添加的配置即可。
pynginx -V
第4步:编译安装
如果以前未安装过:
make && make install
编译安装完成后,配置 nginx。
如果以前已经安装了 nginx 服务器,需要先停止 nginx 服务:
py# 默认 nginx 已经配置为系统服务 service nginx stop
重新编译,在 /etc/nginx/source 下执行
pymake
将编译好的 nginx 文件覆盖到/etc/nginx/sbin/nginx:
pycp objs/nginx /etc/nginx/sbin/nginx
安装编译完成,然后按前文到需要的模块配置 proxy_cache_purge, 然后检测配置文件是否正确:
pynginx -t/T
如果配置检测通过,就可以重启 nginx 服务:
pyservice nginx start
另外,NGINX 服务器的商业版 Nginx Plus 中自带了清理缓存的指令,有兴趣的同学可以自己查阅一下相关资料。
开启 HTTP 缓存后的性能对比
-
未开启 HTTP 缓存
完成时间:1.79秒
DOMContentLoaded:442毫秒
加载时间:1.72秒
-
开启 HTTP 缓存
完成时间:677毫秒
DOMContentLoaded:190毫秒
加载时间:625秒
效果还是很明显的,特别是像我测试站点这种 SPA 页面,一切都要等 js 资源加载完成了才绘制界面,加载速度至关重要!资源加载的越快,意味着用户看到 UI 界面就越快,用户体验也就越好。