题图:by @joewakeford
一、序Hi,大家好,我是承香墨影!
HTTP 协议在网络知识中占据了重要的地位,HTTP 协议最基础的就是请求和响应的报文头(Header),大多数 Http 协议的使用方式,都是依赖设置不同的 HTTP 请求/响应 的 Header 来实现的。
本系列《实用 HTTP》就抛开常规的 Header 讲解式的表述方式,从实际问题出发,来分析这些 Http 协议的使用方式,到底是为了解决什么问题?同时讲解它是如何设计的和它实现原理。
HTTP 协议是一种无状态的“松散协议”,它不会记录不同请求的状态,并且因为它本身包含了两端(客户端和服务端),根据请求和响应来区分,它大部分的内容都只是一个建议,其实双边是可以不遵守此建议的。例如:服务端说,这个数据缓存有一天的时效性,但是客户端可以说,我不听我不听,我就要每次去重新请求。
“这里写了建议零售价 2 元...”
“哦,不接受建议!”
说到缓存,本文就来说说 HTTP 缓存相关的内容。
二、HTTP缓存使用 2.1 为什么需要缓存缓存说白了就是为了快,无论是从磁盘到内存还是从网络到本地,都是为了在下次实用此资源的时候,能够快速响应,避免多次的 I/O 操作。
通过网络获取资源,是一件耗时的操作,较大的资源还会需要客户端和服务端之间进行多次往返通信,这不但会增加客户端响应的时间,同时还会增加网络流量。
在 HTTP 协议中,天然就有对缓存的支持,浏览器和 App 使用的开源网络库中,都是利用 HTTP 缓存来实现对资源的缓存。
浏览器是天然支持 HTTP 缓存,开源库则需要进行一些例如存规则和缓存的资源存放路径之类的简单设定。
2.2 设计一个缓存策略那如果让我们来设计缓存的策略,首先有两个重要的指标需要考虑。
1. 缓存失效
既然缓存主要是针对数据的复用,那我们就需要有一个条件来判定当前缓存的数据,是否依然有效。
总是不能一次缓存,终身使用吧,我们还需要在缓存失效之后,重新获取新的数据并进行缓存。这个前提就是,缓存都需要有一个失效的策略。
2. 减少读取
虽然缓存会有失效策略,但是这只是客户端单方面认为失效,此时应该再去服务端重新获取一遍数据。
可有些情况下,其实资源可能依然有效,并没有发生变动。那就需要有一个策略,让服务端通知客户端,当前缓存依然有效,可以继续使用。这样在减少传输流量之外,也可以加快相应时间,提高效率。
这就是一个好的缓存策略必须要考虑的地方,实际上 HTTP 缓存,也是这样设计的。
2.3 HTTP 缓存HTTP 缓存主要是通过请求和响应报文头中的对应 Header 信息,来控制缓存的策略。
这里主要涉及两个 Header:
Cache-Control:设定缓存策略,是否使用缓存,超时时间是多少。
ETag:当前返回数据的验证令牌,可能是 Hash 值也可能是其他指纹,主要用于在下次请求的时候携带上,让服务端依此判断当前数据是否有更改。
服务端在返回响应数据的时候,会在报文头中,增加用于描述当前响应的内容类型、数据长度、缓存策略(Cache-Control)、验证令牌(ETag)等信息。
例如上图就表示了一次请求响应的事务,大概客户端请求一个文件的时候,服务端返回了一个 200 的状态码,表示响应正常,响应的数据长度为 1024 个字节,建议客户端将此资源缓存最多 120 秒,并且提供了一个指纹令牌(“cxmyDev123”),用来作为当前数据的唯一标识。
2.4 ETag 数据令牌Cache-Control 中设定的 max-age 很好理解,就是设定缓存超时的时间,HTTP 缓存是限定一个超时的秒数,来确定缓存失效的时间。
上古时期还会使用 expires 来决定超时的日期,但是已经被废弃了,如果和 Cache-Control 同时存在,以 Cache-Control 为准。
在此时间间隔范围内,客户端不会再向服务端发送新的请求。当资源距离上一次缓存的时间间隔,大于 120 秒后,客户端才会再次向服务端发送请求。
假如没有数据令牌的情况下,大概步骤应该是这样的:
1. 客户端会首先找到本地缓存,然后发现它已经失效,无法再次使用。
2. 客户端再次向服务端发出新的请求,并获取完整的数据再次进行缓存。之后再刷新该缓存的超时时间。
但是这是一件效率非常低的事情,服务端并无法确定所持有的源资源什么时候会失效,所以提供的 max-age 值,只是一个参考值,是需要取平衡的,太短会导致请求频繁,太长又会导致无法及时刷新客户端资源。而此时再次请求的时候,是存在一定的概率,客户端缓存的数据和服务端上持有的数据是一致的,我们就不需要再次对此数据资源进行二次缓存,直接使用客户端之前缓存的数据即可,同时还需要刷新缓存超时时间。