HTTP 缓存在HTTP 整个体系结构中是非常重要的一部分,当我们访问一个资源的时候,如果本地有“已缓存的副本”,就可以直接从本地提取这个资源。使用缓存有以下优点:
1、缓存减少了冗余数据的传输,节省了网络费用
2、缓存可以缓解网络瓶颈的问题
3、缓存降低了对原始服务器的要求。服务器可以更快地相应,避免过载的出现
冗余数据的传输:
当我们访问服务器界面时,服务器会多次传输同一份文档,每次传送给一个客户端。一些相同的字节会在网络中一次一次的传输。这些冗余的传输会耗费网络的贷款,还会加重服务器的负载。当我们使用缓存的时候就可以通过保留服务器副本的方式解决这个问题。
带宽瓶颈:
很多网络为本地网络客户端挺的贷款比为远程服务器提供的带宽要宽,那么当我们访问一个资源的时候,就会按照服务器的慢速的带宽来给我们传输数据。如果我们在快速的局域网中缓存一份副本,那么我们就会很快的从局域网中拿到副本相应请求。这种方式对传输比较大的文件效果最好。
瞬间拥堵:
缓存在破坏瞬间拥堵的时候效果也是很显著的,例如我们微博上面的一个事件很火,所有人都去访问这个web文档,就会出现瞬间拥塞。由此造成的过多流量可能会使网络和服务器昌盛瞬间的崩溃。
缓存的命中与非命中:
上面了解了缓存带来的优势,但是缓存也有他的局限性,例如我们不能为服务器中的所有资源都创建副本,而且所有的副本都不一定是有效的。
命中:使用已有的副本为某些到达缓存的请求提供服务,被称为缓存命中
未命中:一个请求没有副本可用,而被转发到原始服务器,被称为缓存未命中
缓存验证
上面说到了,我们本地存在的副本也不是有效的,那我们的缓存是怎么怎么验证的呢,我们先看一张图片
HTTP 验证缓存流程
缓存的拓扑结构
缓存的详细处理步骤
1、接收:缓存从网络中读取抵达的报文
2、解析:缓存对报文进行解析,提取出 URL 和各种首部
3、查询:缓存查看是否有本地副本可用,如果没有就获取一份副本,并保存在本地
4、新鲜度检测:在本地有缓存的情况下,我们要验证我们的缓存是否过期,这就是新鲜度检测。如果过期了,我们就询问服务器这个资源是否有更新
5、创建相应:缓存会用新的首部和已缓存的主体来构建一条相应报文
6、发送:缓存通过网络将相应发回给客户端
7、日志:缓存可选地创建一个日志文件来描述这个事务
下面看一张图片:
保持文档的新鲜度
上面我们知道我们在使用缓存的时候,我们需要判断资源是否过期,那 HTTP 是如何保证这个流程的呢?
首先通过 HTTP Cache-Control 首部和 Expires 首部,HTTP 让原始服务器向每个文档上面附加了一个过期日期。如果本地没有超过这个时间,那么就代表我们的缓存是 新鲜的。
从上面可以看到,Expires 的值是特定的日志,而 Cache-Control 里面存储的是 max-age=484200 ,那这个 484200 表示的就是多少秒之后过期,是一个相对时间,而
Expires 是一个绝对时间,在 HTTP 0.9 的时候用的是 Expires ,但是考虑到服务器的时间和客户端的时间可能会不一致,所以在 HTTP 1.0的时候就改为了 Cache-Control 这种
形式的了。
在缓存文档过期之前缓存可以以任意频率使用这些副本,而无需与服务器联系。除非客户端请求中包含阻止提供已缓存或为验证资源的首部,但如果文档过期,缓存就必须与服务器
进行核对,询问文档是否被改过,如果被修改过,就要获取一个新鲜的副本(次副本会带有过期时间)
服务端验证缓存是否过期
上面我们只是简单的介绍了一下服务器验证缓存是否过期的方法,下面会详细介绍以下服务端验证缓存过期流程
主要是通过两种形式验证缓存是否过期:
1、If-Modified-Since
当我们第一次请求服务端的时候,服务端会给我们返回一个 Last-Modified 这个字段,这个字段中存储的是资源最后的修改日期,当我们在处理请求的时候我们需要把这个字段缓存起来,
以便下一次服务端验证缓存用。当我们第二次请求的时候我们会传递一个 If-Modified-Since 字段,这个字段里面的值就是我们刚刚存储的 Last-Modified 的值,在请求之前这段时间,如果
资源被修改了,那么他的修改日期会改变,也就是说当我们请求的之后服务端拿到 If-Modified-Since 这个值之后会与服务端本地的最后修改时间进行比对,如果时间一直,说明缓存没过期,
返回 304 Not Modified ,并在头部返回 Date。如果过期了,那么就会返回 200 OK 并查询结果
注意:有些web服务器并没有将 If-Modified-Since 作为真正的日期来进行比对。相反,他们在 If-Modified-Since 和最后修改日期之间进行字符串匹配。
2、If-None-Match:实体标签在验证
在某些情况下面实用 If-Modified-Since 是不够的:
有些资源可能被周期性地重写,但实际上数据是一样的
有些文档可能被修改了,但是修改的内容不重要
有些服务器无法判定页面的最后修改日期
为了解决上面所说的几个问题,HTTP 引入了 If-None-Match。
当我们第一次请求数据的时候,服务端会返回一个 ETag 这个头部字段,这个字段的意思就是标识资源的版本,我们需要把这个 ETag 对应的值存起来,当我们第二次请求的时候我们在 header 中加入
If-None-Match 这个 key,然后值为刚刚存起来 ETag 对应的值。当服务器拿到这个 If-None-Match 的值的时候,会与服务器本地的值进行校验,如果一致,那么就说明缓存有效,直接返回304,如果不一致
就说明缓存无效直接返回200并查询数据。
okhttp 中的 CacheInterceptor:
主要就是通过上面两个方法去进行存储和获取缓存,就是通过上面的两个主要方法,实现了我们 Android 端的私有缓存。