解密浏览器缓存机制

简介: Web 缓存是在 HTTP 协议中常见的一种机制,可以有效的提升客户端响应速度、降低源站负载带宽。Web 缓存的一个重要特性就是层次性,因此从客户端到源站服务器中间可能会包含多级缓存,包括本地客户端缓存、公共代理缓存、Web 服务器缓存等。本文主要为大家讲解本地浏览器缓存相关机制。

浏览器缓存是节省用户流量,提升加载效率的常用方法;但同时它也会带来获取到历史脏数据等风险,今天我们就来详细介绍下浏览器缓存相关内容。

分类

浏览器的缓存主要包括两种缓存:强缓存、验证缓存。

1. 强缓存

强缓存是指浏览器不与服务器进行任何交互请求,直接将浏览器的缓存数据(包括缓存数据的 Response 头信息)返回给用户。这种缓存给用户的响应是最快的,但同时也是风险性较高的。因为该类缓存没有进行任何的校验即直接反馈给用户,是可能存在有历史的脏数据。当浏览器的请求出现同时以下两个现象时该次请求就是强缓存:

  1. 浏览器返回200 (From Cache):
    image


  1. 请求 Response 头中的 Date 字段所表示的时间小于当前时间:

                                     image


强缓存主要是受 Cache-Control:max-age 和 Expires 头两个 Http 响应头控制的。 Cache-Control 头和 Expires 头都是都是缓存数据的有效期的信息。只不过 HTTP/1.0+ 的 Expires 头是采用的绝对 GMT 时间,而 HTTP/1.1 的 Cache-Control:max-age 则是采用的相对时间进行存储。在实际使用中我们更倾向于使用 Cache-Control:max-age 头,因为 Expires 头记录的是服务器端设置的绝对时间;如果客户端与服务器之间的时间差别较大的话可能会导致有偏差;并且当 Cache-Control:max-age 和 Expires 头同时存在的情况下, Cache-Control 头将覆盖 Expires 头。当请求发起的时间仍然在 Cache-Control 或者 Expires 设置的有效期内的话则将直接读取缓存数据。

读取强缓存的数据将是最快响应数据的方法,因为该次请求没有产生任何实际的公网访问,而仅仅是获取本地的数据即可。

2. 验证缓存

验证缓存(又叫协商缓存)是指浏览器根据缓存资源的 Last-Modified 字段和 Etag 字段得到 If-Modified-Since 和 If-None-Match 字段加入 Request 头中向服务器进行验证源站服务器的资源是否有更新过,如果服务器端收到该请求并且发现服务器端资源没有进行变更即会返回 304 Not Modified 响应头。


                                                image

  下面分别介绍这几个字段的意义:
  Last-Modified / If-Modified-Since :在客户端第一次向服务器端发起请求时,服务器返回数据并置状态码为200,同时将该文件最后修改的 GMT 时间记录在 Last-Modified 头中返回客户端。下次客户端请求验证缓存数据时就会将缓存数据中的 Last-Modified 字段记录为请求头中的 If-Modified-Since 字段向服务器端询问在该时间点后服务器的文件是否有做过更新,如果没有更新即返回 304 Not Modified 响应头并读取缓存数据,而如果服务器文件该时间点后更新过则需要重新将服务器的文件传输给客户端并返回200状态码,同时该文件的 Last-Modified 时间也将是服务器文件现在更新的时间。下图就是一个客户端发送 If-Modified-Since 请求头给服务器端,然后服务器端验证完成后返回304给客户端。



                                                                      image

  Etag / If-None-Match : HTTP 协议规格说明定义 ETag 为“被请求变量的实体值”。另一种说法是, ETag 是一个可以与 Web 资源关联的记号(token)。 HTTP/1.1 并没有要求具体 ETag 中间需要存放什么内容或者实现方法,有一些 ETag 是通过文件资源的 MD5 值来进行标识的。与 Last-Modified 一样也是判断服务器文件是否有做过更新,其也是将上次缓存数据中的 ETag 记录为请求头中的 If-None-Match 头向服务器验证服务器文件的 ETag 是否更新过。 ETag 验证主要解决以下几点 Last-Modified 无法解决的问题:
  1. 网站文件周期性更新但并不改变文件内容(仅修改 Last-Modified 时间),对于这些文件仍然希望可以使用缓存数据;
  2. 网站文件更新频率较快,小于秒级的更新频率通过 Last-Modified 无法识别;
  3. 服务器无法准确得到 Last-Modified 时间,需要 ETag 标识文件。
    下图就是根据 If-None-Match 验证服务器端的 ETag 后返回304的示例:



                                                                      image

从上面的描述中可以查看到 304 是将本地缓存的 Last-Modified 和 ETag 与服务器端进行校验,因此校验缓存相比于强缓存不会出现读取本地脏数据的情况,而校验缓存的请求时间相比于强缓存较慢,而相比于完整获取服务器端的文件是较小的。因为校验缓存仍然是需要发请求到服务器端,但是 304 响应内容数据较小,因此比直接获取源文件更高效。

浏览器行为

不仅仅服务器端的 ETag 或者 Last-Modified 头会影响浏览器缓存策略,同时浏览器本身的请求头也同样会影响缓存策略。例如:浏览器有多种刷新行为可以影响下次请求对缓存数据的行为,并且不同的浏览器对于同样的刷新操作也有不同的情况。究其根本原因都是浏览器在发起请求的时候所带的 Catch-Control 的头信息来决定的。下面是 HTTP/1.1 文档中 13.2.6 Disambiguating Multiple Responses 的一段文档描述了该问题 [1]:

When a client tries to revalidate a cache entry, and the response it receives contains a Date header that appears to be older than the one for the existing entry, then the client SHOULD repeat the request unconditionally, and include

   Cache-Control: max-age=0

to force any intermediate caches to validate their copies directly with the origin server, or

   Cache-Control: no-cache

to force any intermediate caches to obtain a new copy from the origin server.

从上面该文档可以知晓:如果浏览器想忽略强缓存的数据而直接获取验证缓存的数据的话是需要在请求头中加上 Cache-Control:max-age=0;而浏览器如果想忽略强缓存和鉴权缓存,直接获取源服务器的内容而缓存数据就需要在请求头中加上 Cache-Control:no-cache 的头。下面是在 Chrome 下测试的结果图:
                                   image
                                                                                     浏览器强缓存


                                   image
                                                                                     浏览器验证缓存


                                   image
                                                                                     浏览器不缓存


上面的测试分别是通过在地址栏回车重新键入地址、 F5 刷新以及 Ctrl+F5 刷新的测试结果。从上面的测试结果图中可以查看到与上述的结论一致;并且对于 Cache-Control: no-cache 的测试中为了兼容 HTTP/1.0 加上了 Pragma:no-cache 的头信息,其作用于前者是一致的。因此不同的浏览器对于不同的刷新操作有不同的处理逻辑也是由 Request 头中的 Cache-Control 头决定的。

流程总结

下面将根据两张图 [2] [3] 总结浏览器缓存的处理逻辑。

 (1)当浏览器向服务器端发送请求的时候首先查看本地浏览器缓存数据中是否有缓存数据,如果没有缓存数据则会向 Web 服务器(这里的 Web 服务器是广义的概念,有可能并不是源站服务器,有可能是 CDN 等缓存数据)请求对应的数据并将获取的响应数据以及一些对应的 Response 头信息缓存到本地(包括 Expires 、Cache-Control 等头信息),如果有缓存数据则执行(2);

                                                               image


(2)当本地有缓存数据并且 Request 头中没有设置 Cache-Control:no-cache 和 Cache-Control:max-age=0 头信息的话就需要查看该缓存的 Cache-Control 头和 Expires 头查看该缓存数据是否新鲜,如果没有过期则直接读取强缓存数据返回给浏览器,返回状态码 200(From Cache) 。如果有设置上述的两个 Cache-Control 头或者强缓存数据已经过期则执行(3);

 (3)如果请求头中没有 Cache-Control:no-cache 头信息的话则客户端带着 If-Modified-Since 和 If-None-Match 参数向服务器发起验证,如果服务器端验证发现没有进行更新的话则直接返回 304 Not  Modified 头和本地的缓存数据返回给客户端。如果请求头带了 Cache-Control:no-cache 或者源站做了更新则执行(4);
 (4)如果请求头中有 Cache-Control:no-cache 或者验证缓存校验发现源站数据更新了,则需要从服务器重新获取数据并将其存入浏览器缓存并返回200状态码。

                                     image

常见问题

经常遇到如下问题均可能是浏览器缓存导致的问题,请大家注意留意:

  1. 添加CDN 加速源站后,客户端访问出现历史脏数据。由于添加 CDN 后浏览器到服务器端可能出现缓存的就是浏览器缓存、CDN 缓存,因此需要清楚两处的缓存后测试是否正常,如果仍然获取到脏数据有可能出现劫持的情况导致的,用户可以通过查看响应头中是否有出现 301 或者 302 的状态码查看。
  2. CDN 可以设置响应 HTTP 头中的 Cache-Control 和 Expires 头,这两个头与 HTTP 标准协议一致的设置方法: Cache-Control 可以设置相对时间,而 Expires 仅能够设置绝对 GMT 时间。在 CDN 上设置的 Cache-Control 和 Expires 头将仅影响浏览器缓存,并不影响 CDN 缓存策略。 CDN 的缓存策略需要源站的 Cache-Control 和 Expires 头决定。
  3. 当源站响应头中设置了如下的缓存策略 CDN 和浏览器都将认为是源站不允许缓存而不进行缓存:
  • Cache-Control为no-cache,no-store,private
  • Cache-Control为max-age=0
  • Pragma为no-cache

Reference

[1] Hypertext Transfer Protocol -- HTTP/1.1: https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
[2] HTTP 权威指南
[3] 缓存系列(1)——浏览器缓存协商: http://blog.csdn.net/chosen0ne/article/details/7344189
[4] http协商缓存VS强缓存: http://www.cnblogs.com/wonyun/p/5524617.html
[5] stackoverflow讨论帖: http://stackoverflow.com/questions/1046966/whats-the-difference-between-cache-control-max-age-0-and-no-cache

相关实践学习
Serverless极速搭建Hexo博客
本场景介绍如何使用阿里云函数计算服务命令行工具快速搭建一个Hexo博客。
目录
相关文章
|
3月前
|
缓存 Java 数据库连接
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
文章介绍了MyBatis的缓存机制,包括一级缓存和二级缓存的配置和使用,以及如何整合第三方缓存EHCache。详细解释了一级缓存的生命周期、二级缓存的开启条件和配置属性,以及如何通过ehcache.xml配置文件和logback.xml日志配置文件来实现EHCache的整合。
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
|
4月前
|
缓存 应用服务中间件 nginx
Web服务器的缓存机制与内容分发网络(CDN)
【8月更文第28天】随着互联网应用的发展,用户对网站响应速度的要求越来越高。为了提升用户体验,Web服务器通常会采用多种技术手段来优化页面加载速度,其中最重要的两种技术就是缓存机制和内容分发网络(CDN)。本文将深入探讨这两种技术的工作原理及其实现方法,并通过具体的代码示例加以说明。
404 1
|
12天前
|
存储 缓存 监控
后端开发中的缓存机制:深度解析与最佳实践####
本文深入探讨了后端开发中不可或缺的一环——缓存机制,旨在为读者提供一份详尽的指南,涵盖缓存的基本原理、常见类型(如内存缓存、磁盘缓存、分布式缓存等)、主流技术选型(Redis、Memcached、Ehcache等),以及在实际项目中如何根据业务需求设计并实施高效的缓存策略。不同于常规摘要的概述性质,本摘要直接点明文章将围绕“深度解析”与“最佳实践”两大核心展开,既适合初学者构建基础认知框架,也为有经验的开发者提供优化建议与实战技巧。 ####
|
8天前
|
缓存 Java 数据库连接
MyBatis缓存机制
MyBatis提供两级缓存机制:一级缓存(Local Cache)默认开启,作用范围为SqlSession,重复查询时直接从缓存读取;二级缓存(Second Level Cache)需手动开启,作用于Mapper级别,支持跨SqlSession共享数据,减少数据库访问,提升性能。
19 1
|
11天前
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
27 4
|
2月前
|
存储 缓存 负载均衡
Nginx代理缓存机制
【10月更文挑战第2天】
93 4
|
2月前
|
存储 缓存 NoSQL
深入理解后端缓存机制的重要性与实践
本文将探讨在后端开发中缓存机制的应用及其重要性。缓存,作为提高系统性能和用户体验的关键技术,对于后端开发来说至关重要。通过减少数据库访问次数和缩短响应时间,缓存可以显著提升应用程序的性能。本文将从缓存的基本概念入手,介绍常见的缓存策略和实现方式,并通过实例展示如何在后端开发中有效应用缓存技术。最后,我们将讨论缓存带来的一些挑战及其解决方案,帮助您在实际项目中更好地利用缓存机制。
|
3月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
76 8
|
3月前
|
缓存 Java Python
python垃圾回收&缓存机制
python垃圾回收&缓存机制