Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

浏览器缓存浅析 #5

Closed
zhengweikeng opened this issue Mar 22, 2016 · 1 comment
Closed

浏览器缓存浅析 #5

zhengweikeng opened this issue Mar 22, 2016 · 1 comment
Labels

Comments

@zhengweikeng
Copy link
Owner

做web的人,很多时候都会涉及到浏览器缓存,尤其是在网站性能调优的时候。因此自己想梳理一下这方面的知识。

如何定义一个资源是否应该缓存,缓存多久,缓存到期后如何处理,这些都是由HTTP协议定义好的,浏览器则为我们实现了它。

服务器通过在响应中插入指定的头部来告诉浏览器需要做的缓存操作,这些头部包括:Expires,Cache-Control,Last-Modified.If-Modified-Since,Etag。

接下来我们就来看看这些头部的含义。

Expires

这个头部是在HTTP1.0中定义的,它指定了一个绝对的过期日期,如果过期日期已经过了,就说明文档不再新鲜了。如下所示:
Expires: Fri,05 Jul 2015, 05:00:00 GMT
这里服务器的响应头中定义了缓存的过期时间为2015年7月5日的凌晨5点,在这个时间之前,浏览器都不会去向服务器请求。
但是使用Expires存在服务器端时间和浏览器时间不一致的问题,我们基本上是不会使用这个头部,因此它也不是我们介绍的重点,点到即可。

Cache-Control

既然我们说Expires是不推荐的用法,那么肯定要有个替代方案,这个替代者就是Cache-Control。
它是在HTTP1.1中定义的,它定义了一个文档从第一次生成开始到不再新鲜、无法使用为止的最长使用期,并且以秒为单位。用法如下所示:
Cache-Control: max-age=484200
这个响应头告诉了浏览器,在第一次响应后的484200秒里,如果再次请求资源时,不需要再次向服务器发起请求,而是直接使用缓存。

Cache-Control除了定义最长使用时间外,还有其他定义方式。

  1. no-cahe:所有内容都不会被缓存,每次都向服务器发起请求。可以具体指定某个字段,如no-cache=set-cookie,它告知浏览器当遇到set-cookie时不要使用缓存内容,而是去向服务器请求。
  2. no-store:让浏览器直接向服务器发起请求。和no-chche的区别就是,前者每次的请求和响应都不会被缓存,都是一次全新的操作。no-cache则还是会缓存,而且请求时还是会先拿到缓存内容,然后不做验证直接去向服务器发起请求。
  3. public:所有内容都将被缓存
  4. private:告知浏览器只缓存单个用户的响应,可以指定具体字段,private=username,此时名为username的标头内容,将不会被共享缓存。

以上这些是常用的Cache-Control的定义,当然还有其他的定义方式,如must-revalidate、proxy-revalidate等这些,但是貌似使用的不是很多,所以也不打算阐述。结尾会给出相应的介绍链接,需要使用的时候可以去参考一下。

上图(图片来自网络)!!
cache-control

Last-Modified

该头部配合cache-control使用,它标志了资源的最后修改时间,即服务器在响应中回带有一个Last-Modified的头部,告诉浏览器该资源的最后修改时间。

我们知道,在第一次请求页面时,会通过cache-control的指示缓存资源,同时会设置资源的Last-Modified时间。
当再一次请求页面的资源时,根据max-age指定的时间,在这个时间期间,每次向服务器获取资源时,会直接获取缓存内容,并且响应码为200 OK(from cache)。

若资源已经过期,则会向服务器发起请求,此时请求中会带上If-Modified-Since请求头,值为Last-Modified的值。

服务器收到If-Modified-Since的请求头,此时服务器将根据该字段的值进行一定的逻辑判断。
如果资源没有变更,则返回304告知浏览器直接使用缓存。
如果资源已经变更,则返回最新的资源,并且响应码为200,还会发送最新的Last-Modified字段。
若响应包中Cache-Control:max-age 或 Expires 字段,则会重新设置缓存的过期时间,于是,浏览器又可以不需要向服务器发起请求了。

Etag

该头部和Last-Modified的作用类似,区别我们待会再说。先来看看它的使用方式。

同样的,在第一次请求页面资源,会缓存页面,设置缓存时间,此时若服务器返回了Etag字段,浏览器则会保存Etag的字段和值。这个值是个特殊串,大概是像这样的值,“x123cef”。
Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。当然我们可以更改这种算法,例如使用MD5。

当资源过期时,在对服务器发起的请求头中会带有一个If-None-Match的请求头,值为Etag的值。

服务器收到If-None-Match的值,同样的会将本地资源的校验值和If-None-Match的值进行比对。
相同,说明资源没有更改,返回304。
不同,说明资源已经更改,返回最新资源,响应码为200,并带有最新的Etag值。

若同时使用了Last-Modified和Etag,只要有一方认为资源没有变动,就会进行304响应。

Last-Modified和Etag的区别

  1. 首先Last-Modified只能精确到秒,有些时候,文件会在1秒内被更改很多次,使用Last-Modified则无法准确的标志文件的更改时间。
  2. 有时会定时生成一些文件,但是内容是不变的,或者仅仅修改变动的时间,此时我们并希望浏览器还是使用缓存的资源,Last-Modified则无法满足我们了。
  3. Etag又服务器或者开发者生成的一个唯一特殊标志值,可以更加有效的控制缓存。资源变更则更新该值,没有变更则不更新该值,简洁粗暴。

当然两者是可以通用的,此时我们应该让服务器优先验证Etag,再去验证Last-Modified,再决定是否返回304。

不同的页面打开方式产生的请求区别

主要有以下两点要注意:

  1. 手动刷新页面(F5刷新),浏览器会直接认为缓存已经过期,即使缓存并没有过期,在请求中加上字段:Cache-Control:max-age=0,发包向服务器查询是否有文件是否有更新。
  2. 强制刷新页面(ctrl+F5刷新),浏览器会直接忽略本地缓存内容,即使本地有缓存可用,在请求中加上字段:Cache-Control:no-cache(或 Pragma:no-cache),发包向服务重新拉取文件。
    其他的直接上图(图片来自网络):
    browser_active

消灭304

经过上述,我们知道当缓存过期时,浏览器会想服务器再次发起请求,询问资源是否真正过期,服务器响应304说没有过期还可以继续使用缓存,还可以设置新的过期时间。
这时我们发现,询问的过程还是需要发起一起HTTP请求,得到的结果是继续使用缓存。PC端还好,对于移动端,一次请求还是有代价的,因此我们需要有一种方式来消灭这种304。

此时,在前端我们采取的方式是,对这些静态文件,如js,css的文件名中加入版本名或者MD5值,如bundle.d5d02a02.js,bundle.v1.js,并且设置Cache-Control:max-age=31536000,即1年的使用时间。这样1年内我们都不会发起新的请求。
如果资源文件被修改了,则使用新的MD5值或者版本名进行命名,如bundle.d43d12d3.js,bundle.v2.js。
这样便能有效的避免304了。

总结

我们通过图片来总的看一下使用了缓存的浏览器和服务器动作。
这是第一次请求:
no-cache
再次请求页面:
cache

我们再通过查看请求头和响应头的内容的方式来看看具体请求和响应的东西。
资料来自《H5 缓存机制浅析 移动端 Web 加载性能优化》
首次请求:
first_access
缓存有效期内请求:200(from cache)
in_cache_time
缓存过期后请求:304(Not Modified)
304

参考资料

大公司里怎样开发和部署前端代码?

H5 缓存机制浅析 移动端 Web 加载性能优化

再记:浏览器缓存200(from cache)和304小结

透过浏览器看HTTP缓存

你应该了解的 一些web缓存相关的概念

@honger05
Copy link

honger05 commented Dec 1, 2016

我也是这样做的,但有个疑问。

经历几个版本的更新之后,之前留下来的缓存文件什么时候会被浏览器清除?

一个文件,你告诉浏览器缓存1年,但下个版本发布之后,这个文件已经不会被用到了。

继续累加下去,浏览器容量不够了会怎么样?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants