http缓存
# 缓存的重要性
- 一个优秀的缓存策略,可以缩短网页请求资源的距离,减少延迟,缓存文件可以重复利用所以还可以减少带宽,降低网路负荷
# 浏览器缓存
浏览器启用缓存的优点:减少页面加载时间,减少服务器负载
浏览器是否使用缓存,缓存多久,是由服务器控制的
- 即服务器响应的 响应头 中,某些字段指明了缓存的关键信息
浏览器缓存的分类
- 强缓存 和 协商缓存
# 强缓存
Expires, Cache-Control
返回的状态码 200
network => size =>
会显示 from-cache (from-disk-cache),(from-memory-cache)强缓存的实现:通过( Expires ) 或者 ( Cache-Control ) 这两个 ( http response header ) 来实现的,他们用都是用来表示资源在客服端存在的 有效期
# Expires
http1.0 提出,响应头中的一个字段,绝对时间,用 GMT 格式的字符串表示
注意:expires 是和浏览器本地的时间做对比,是一个绝对时间点,是一个 GMT 时间
Expires 是优化中最理想的情况,因为它根本不会产生请求,所以后端也就无需考虑查询快慢
Expires 的原理
# Expires 的原理
- 浏览器第一次向服务器请求资源,浏览器在请求资源的同时,在 responder 响应头中加上 Expires 字段
- 浏览器在接收到这个资源后,将这个资源和所有 response header 一起缓存起来 - 所以,缓存命中的请求返回的 header 并不是来自服务器,而是来自之前缓存的 header
- 浏览器再次请求这个资源时,先从缓存中寻找,找到这个资源后,拿出 Expires 跟当前的请求时间做比较 - 如果当前请求时间,在 Expires 指定的时间之前,就能命中强缓存,否则不能 - 注意:Expires 是和浏览器本地时间作对比
- 如果未命中缓存,则浏览器直接从服务器获取资源,并更新 response header 中的 Expires
expires 是较老的强缓存管理 header,是服务器返回的一个绝对时间,在服务器时间与客服端时间相差较大时,Expires 缓存管理容易出问题(比如:随便修改客户端时间,就能影响命中结果),所以在 http1.1 中,提出了新的 header => Cache-Control,一个相对时间,以秒为单位,用数值表示
# Cache-Control
http1.1 提出,响应头中的一个字段,相对时间,以秒为单位,用数值表示
注意:Cache-Control 也是和浏览器本地时间做对比,以秒为单位的时间段
Cache-Control 可以指定:public 和 private
- private:,Cache-Control 的默认取值,
表示该资源仅仅属于发出请求的最终用户,这将禁止中间服务器(如代理服务器)缓存此类资源,对于包含用户个人信息的文件,可以设置private - public:
允许所有服务器缓存该资源 - must-revalidate: 只有当资源被视为陈旧时才需要重新验证。
- max-age: 123123 // 一个时间段,单位是 s。
- no-cache:并不意味着不缓存。实际上,在每次请求中使用任何缓存响应之前,它都意味着“使用服务器重新验证”,相当于 max-age=0,must-revalidate,走协商缓存。
- no-store:实际上是完全不缓存指令,并且旨在防止以任何形式的缓存存储该表示。
no-cache 和 no-store 以及 max-age=0 的区别 :
no-cache
Cache-Control 为 no-cache。表示必须重新去获取请求。由服务端来判断是否使用缓存,即可以在本地缓存,可以在代理服务器缓存,但是这个缓存要服务器验证才可以使用
no-store
是真正的不进行缓存。即彻底得禁用缓冲,本地和代理服务器都不缓冲,每次都从服务器获取
max-age=0
max-age>0 时 直接从游览器缓存中提取 max-age<=0 时 向 server 发送 http 请求确认 ,该资源是否有修改 有的话 返回 200 ,无的话 返回 304.(即max-age=0 表示不管 response 怎么设置,在重新获取资源之前,先检验 ETag/Last-Modified)
- private:,Cache-Control 的默认取值,
Cache-control: no-cache,private,max-age=123123
Cache-Control 的原理
# Cache-Control 的原理
- 浏览器第一次向服务器请求资源,服务器在返回资源的同时,在 responder 的 header 中加上 Cache-Control 字段
- 浏览器在接收到这个资源后,会将这个资源和所有的 response header 一起缓存起来,所以,缓存命中的请求返回的 header 并不是来自服务器,而是来自之前缓存的 header
- 浏览器再次请求这个资源时,先从缓存中寻找,找到这个资源后,拿出 Cache-Control 和当前请求的时间做比较。如果当前请求时间,在 Cache-Control 表示的时间段内,就能命中强缓存,否则不能。
- 如果缓存未命中,则浏览器直接从服务器获取资源,并更新 response header 中的 Cache-Control
# 强缓存 Expires 和 Cache-Control 总结
Expires 和 Cache-Control 可以开启一个,也可以同时开启
当 Expires 和 Cache-Control 同时开启时,Cache-Control 优先级高于 Expires
Cache-Control 可以指定 private 和 public,表示是否允许中间服务器缓存该资源
expires 是一个用 GMT 时间表示的时间点,Cach-Control 是用秒表示的时间段,都是和浏览器本地时间做对比
# 协商缓存
Last-Modified(If-Modified-Since),ETag(If-None-Match)
返回状态码 304
协商缓存的原理:当浏览器对某个资源的请求没有命中强缓存,就会发一个请求到服务器,验证协商缓存是否命中,如果协商缓存命中,请求响应返回的 http 状态为 304,并且会显示一个 Not Modified 的字符串表示资源未被修改
modified: 是修改的意思
# Last-Modified 和 If-Modified-Since
- Last-Modified 和 If-Modified-Since 都是根据 服务器时间 返回的 header
- 响应头:Last-Modified
- 请求头:If-Modified-Since
- 原理
# Last-Modified If-None-Match
浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在 response 的 header 加上 Last-Modified 的 header - 这个 header 表示这个资源在服务器上的最后修改时间
浏览器再次跟服务器请求这个资源时,在 request 的 header 上加上 If-Modified-Since 的 header - 这个 header 的值就是上一次请求时返回的 Last-Modified 的值
服务器再次收到资源请求时,根据浏览器传过来 If-Modified-Since 和资源在服务器上的最后修改时间判断资源是否有变化 - 如果没有变化则返回 304 Not Modified,但是不会返回资源内容; - 如果有变化,就正常返回资源内容。
- 当服务器返回 304 Not Modified 的响应时,response header 中不会再添加 Last-Modified 的 header,因为既然资源没有变化,那么 Last-Modified 也就不会改变
浏览器收到 304 的响应后,就会从缓存中加载资源
如果协商缓存没有命中,浏览器直接从服务器加载资源时,Last-Modified Header 在重新加载的时候会被更新。下次请求时,If-Modified-Since 会启用上次返回的 Last-Modified 值。
# ETag 和 If-None-Match
- 只要资源有变化 ETag 这个字符串就不一样,和修改时间没有关系,所以很好的补充了 Last-Modified 的问题
- 响应头:ETag
- 请求头:If-None-Match
- 原理
# ETag 和 If-None-Match
- 浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在 response 的 header 加上 ETag 的 header - 这个 header 是服务器根据当前请求的资源生成的一个唯一标识,这个唯一标识是一个字符串 - 只要资源有变化这个串就不同,跟最后修改时间没有关系,所以能很好的补充 Last-Modified 的问题
- 浏览器再次跟服务器请求这个资源时,在 request 的 header 上加上 If-None-Match 的 header, - 这个 header 的值就是上一次请求时返回的 ETag 的值
- 服务器再次收到资源请求时,根据浏览器传过来 If-None-Match 然后再根据资源生成一个新的 ETag - 如果没有变化则返回 304 Not Modified,但是不会返回资源内容 - 如果有变化,就正常返回资源内容。
- 与 Last-Modified 不一样的是,当服务器返回 304 Not Modified 的响应时。 由于 ETag 重新生成过,response header 中还会把这个 ETag 返回,即使这个 ETag 跟之前的没有变
- 浏览器收到 304 的响应后,就会从缓存中加载资源。
# Last-Modified(If-Modified-Since) 和 ETag(If-None-Match) 的区别
ETag 的优势
- ETag 和 Last-Modified 非常相似,都是用来判断一个参数,从而决定是否启用缓存。
但是ETag相对于Last-Modified也有其优势,可以更加准确的判断文件内容是否被修改,从而在实际操作中实用程度也更高。
状态码为 200 from cache 和 304 Not modified 的区别
1、请求状态码为 200 from cache: 表示该资源已经被缓存过,并且在有效期内,所以不再向浏览器发出请求,直接使用本地缓存。

2、状态码为 304 Not modified: 表示浏览器虽然发现了本地有该资源的缓存,但是不确定是否是最新的,于是想服务器询问,若服务器认为浏览器的缓存版本还可用(即还未更新),那么便会返回 304,继续使用本地的缓存。

# 强缓存和协商缓存的区别
协商缓存跟强缓存不一样,强缓存不发请求到服务器,所以有时候资源更新了浏览器还不知道,但是协商缓存会发请求到服务器,所以资源是否更新,服务器肯定知道。
大部分 web 服务器都默认开启协商缓存,而且是同时启用 Last-Modified,If-Modified-Since 和 ETag、If-None-Match
Last-Modified,If-Modified-Since 和 ETag、If-None-Match 一般都是同时启用,这是为了处理 Last-Modified 不可靠的情况
分布式系统里多台机器间文件的 Last-Modified 必须保持一致,以免负载均衡到不同机器导致比对失败
分布式系统尽量关闭掉 ETag(每台机器生成的 ETag 都会不一样)
# 浏览器缓存判断的流程
第一次正常请求后,缓存了资源和所有 header 的前提下
在资源缓存后,在缓存过期失效之前,如果再次请求该资源,默认先检查强缓存
- 强缓存命中,则直接读取
- 未命中强缓存,则发送请求到服务器,再检查是否命中协商缓存
未命中强缓存,再发请求到服务器检查是否命中协商缓存
- 协商缓存命中,则告诉浏览器还是可以从缓存读取
- 未命中协商缓存,才从服务器返回最新的资源

# Nginx http 缓存设置
Nginx 提供了 expires、etag、if-modified-since 指令来实现浏览器缓存控制。(http 模块中可以通过 add_header 来设置 cache-control)
location / {
...
add_header Cache-Control max-age=3600;
...
}
2
3
4
5
# expires
如果我们使用 Nginx 作为静态资源服务器,那么可以使用 expires 进行缓存控制。
location /img {
alias /export/img/;
expires 1d;
}
2
3
4
当我们访问静态资源时,如,将得到类似如下的响应头。

对于静态资源会自动添加ETag,可以通过添加etag off指令禁止生成ETag。如果是静态文件,那么Last-Modified值为文件的最后修改时间。Expires是根据当前服务器系统时间算出来的。如上 Nginx 配置的计算逻辑(实际计算逻辑比这个多,具体参考官方文档)。
if (expires == NGX_HTTP_EXPIRES_ACCESS||r->headers_out.last_modified_ time == -1) {
max_age = expires_time;
expires_time += now;
}
2
3
4
# if-modified-since
此指令用于指定 Nginx 如何拿服务端的Last-Modified和浏览器端的if-modified-since时间进行比较,默认if_modified_since exact表示精确匹配,也可以使用if_modified_since_before表示只要文件的最后修改时间早于或等于浏览器端的if-modified-since时间,就返回 304。
# Nginx 的缓存服务
使用 Nginx 作为反向代理时,请求会先进入 Nginx,然后 Nginx 将请求转发给后端应用,如下图所示。

通常 nginx 反向代理配置
upstream backend_tomcat {
server 192.168.61.1:9080 max_fails=10 fail_timeout=10s weight=5;
}
location / {
proxy_pass http://backend_tomcat/cache$is_args$args;
}
2
3
4
5
6
7
接下来,我们可以通过如http://192.168.61.129/cache?millis=1471349916709访问 Nginx,Nginx 会将请求转发给后端应用。也就是说 Nginx 只是做了相关的转发(负载均衡),并没有对请求和响应做什么处理。
假设对后端返回的过期时间需要调整,可以添加 Expires 指令到 location。
location / {
proxy_pass http://backend_tomcat/cache$is_args$args;
expires 5s;
}
2
3
4
然后再请求相关的 URL,将得到如下响应。

过期时间相关的响应头被Expires指令更改了,但是last-modified是没有变的。
即使我们更改了缓存过期头,但 Nginx 自己没有对这些内容做代理层缓存,每次请求还是要到后端验证的,假设在过期时间内,这些验证在 Nginx 这一层验证就可以了,不需要到后端验证,这样可以减少后端很大的压力。即整体流程如下。
1.浏览器发起请求,首先到 Nginx,Nginx 根据 URL 在 Nginx 本地查找是否有代理层本地缓存。
2.Nginx 没有找到本地缓存,则访问后端获取最新的文档,并放入到 Nginx 本地缓存中,返回 200 状态码和最新的文档给浏览器。
3.Nginx 找到本地缓存,首先验证文档是否过期(Cache-Control:max-age=5),如果过期,则访问后端获取最新的文档,并放入 Nginx 本地缓存中,返回 200 状态码和最新的文档给浏览器;如果文档没有过期,即 if-modified-since 与缓存文档的 last-modified 匹配,则返回 304 状态码给浏览器。

内容不需要访问后端,即不需要后端动态计算/渲染等,直接 Nginx 代理层就把内容返回了,速度更快,内容越接近于用户速度越快。

即用户首先访问到全国各地的 CDN 节点,如果 CDN 没命中,则会回源到中央 Nginx 集群,该集群做二级缓存,如果没有命中缓存(该集群的缓存不是必须的,要根据实际命中情况等决定),则最后回源到后端应用集群。
# nginx 代理层缓存
proxy_cache_path和proxy_cache等
http {
...
proxy_cache_path /path/to/cache
levels=1:2
keys_zone=nginx_cache:1024m
max_size=10g
inactive=7d
use_temp_path=off; # 最好写在同一行
server {
...
proxy_cache_key $uri;
proxy_cache_methods GET POST;
proxy_cache nginx_cache;
proxy_cache_valid 200 206 304 301 302 7d;
set $nocache 0;
# 这里可以写相关的匹配规则,来对不同的请求进行缓存或不缓存
proxy_no_cache $nocache;
proxy_cache_bypass $nocache;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
参数说明:
- proxy_cache_path:缓存存放路径;
- levels:设置缓存文件目录层次;如果所有的缓存放入一个文件夹,则影响效率。这里设置的是 levels=1:2 ,表示两级目录;
- keys_zone:在共享内存中设置一块存储区域来存放缓存的 key 和 metadata,这样 nginx 可以快速判断一个 request 是否命中或者未命中缓存,1m 可以存储 8000 个 key,10m 可以存储 80000 个 key;
- max_size:最大的缓存空间,如果不指定,会使用掉所有的硬盘空间,当达到配额后,会删除最少使用的 cache 文件;
- inactive:删除指定时间内未被访问的缓存文件;
- use_temp_path:如果为 off,则 nginx 会将缓存文件直接写入指定的 cache 文件中,而不是使用 temp_path 存储,建议为 off,避免文件在不同文件系统中不必要的拷贝;
- proxy_cache:启用 proxy cache,指定声明好的缓存区域 keys_zone;
- proxy_cache_valid 200 206 304 301 302 7d:为对 http 状态为 200 206 304 301 302 等的缓存结果缓存 7 天;
- proxy_cache_key $uri:定义缓存唯一 key,通过唯一 key 来进行 hash 存取;
- proxy_no_cache 和 proxy_cache_bypass :当为 0 时表示缓存,不为 0 时表示不缓存。
# 附录
