HTTP缓存工作流程详解

HTTP首部信息

request请求首部格式

     method url version

     headers #通常需要跟主题之间有一空白行

          host:www.test.com 

          Access-Encoding:gzip    

rsponse 起始格式

     

     

HTTP协议本身是无状态的,必须要基于tcp协议进行工作,无状态导致了每个资源的请求

    比如:每个页面包含了很多图片的资源,每个图片都是一个资源,在一个页面中包含了几十个图片,每个图片都是单独请求的,也可能包含了css或xml等 每个都需要单独请求,也就意味着打开一个页面需要N次的tcp三次握手 四次断开,因此有了keepalive,通过keepalive机制进行长连接,tcp三次握手后可以请求N个资源然后断开,所以这使得了tcp加速其性能的机制

    但是用户的每次访问这个页面包括刷新以后,而后又重新打开,如果每次访问到同一网站某些相同资源的时候,都需要到服务器上去请求,无论对客户端服务器端甚至是带宽等都会是资源浪费,因此要提高用户体验,加速资源访问的效率,我们都应该提供缓存机制

 

private cache (私有缓存)

    用户访问的资源有可能是私有信息,因此可以在用户的浏览器中开辟一私有缓存空间。用户每次访问资源取到以后将其资源存放在浏览器空间里,但是这样只能为单一用户实现加速目的,私有缓存主要目的就是用来缓存私有缓存对某个用户自身访问从而起到加速作用

 

public cache 公用缓存

    面对客户端的是缓存代理,代理可以进行缓存的功能。首先代理能够接收到用户的请求,请求进来后先检索本地是否将请求缓存下来,如果缓存中有数据,那么直接将缓存的数据返回给用户,否则由代理服务器到原始服务器取数据,如果可以缓存,先缓存本地然后响应给客户端。在此同时,其他客户端进来请求,查找本地缓存发现缓存中存在数据则时间返回给用户。

 

那么如何实现让客户端都使用同一缓存

如下所示

public cache 公用缓存

面对客户端的是缓存代理,代理可以进行缓存的功能,首先代理能够接收到用户的请求,请求进来后先检索本地是否将请求缓存下来

如果缓存中有数据,那么直接将缓存的数据返回给用户,否则由代理服务器到原始服务器取数据,如果可以缓存,先缓存本地然后响应给客户端

同时,其他客户端进来请求,查找本地缓存发现缓存中存在数据则时间返回给用户

 

HTTP缓存工作流程详解_第1张图片

 

 

可以设置二级代理缓存,直接与客户端连接,由二级缓存直接去源站或一级缓存去取数据

首先用户发起请求,先到离自己最近的代理,代理服务器先检索本身缓存,如果存在则直接返回;

如果没有数据,则直接去找另外离自己最近的节点服务器,如果存在则返回如果没有则到源站找数据;

那么这时源站的前端也有缓存服务器,如果缓存中有则直接返回,如果没有数据则去查询后端服务器,最后缓存本地返回结果

 

但问题是如何保证不同的客户端访问的是不同的服务器

可以对其做智能DNS,域名都是同样的,域名解析的时候能够判断用户的来源(view),如果来源是同一区域的解析的固然是这个域名则返回的是离客户最近的服务器的地址,而不是原始服务器地址

同样的其他组返回的是离最近的服务器地址,那从而这么几个缓存本身之间可以共享缓存的话那么效果会更好

 

HTTP缓存工作流程详解_第2张图片

首先,第一组服务端发现没有数据则去取数据,缓存后而后第二个服务端去请求离它最近的服务端,而后发现没有但是这个服务器发现它去访问另外一个与它对等的服务器的速度比较快,而源站的服务器较慢,如果在较快的缓存服务器取得数据会更快,而这些缓存服务器的关系是对等的,这样我们可以成为源站的代理为一级代理,其他为二级代理

那么所有的二级代理的层次都是一样的,被称为兄弟服务器(sibling server)

 

这些兄弟服务器之间是完全可以共享缓存对象的

而且只要跟缓存服务器本身定制好策略,它也会知道哪个服务器最近、哪个代价最大、哪个服务器最快等

这样的网络被称为CDN

 

 

缓存命中率

缓存命中率计算方式:请求总次数 和 缓存服务器上的缓存次数 对比 即可

命中率越高就说明缓存服务器的返回比例越高,命中率越高越好

如果前端服务器命中率在70% 而在源站代理服务器命中率在90% 那么缓存服务器还能接收多少个请求:

大多数的事情都能在代理上解决 ,到原始服务器的流量就可能很小了,

 

缓存的失效

毕竟缓存不是权威的,比如图片删除 文章删除等

但如果说此前的缓存的对象已经被缓存下来,而后在源站已经将资源删除了,情况很常见

那么这时候需要设置缓存时间周期

 

作为缓存服务器来讲,静态内容可以缓存,动态内容也可以缓存,但是有些动态内容不能永久缓存也不应该永久缓存

比如:

在论坛发布一篇帖子,帖子下面有两个回复,用户刷新后可能出现10个回复,这种内容就不能长久缓存,但是原始帖子本身倒是可以缓存,那么就会出现这种结果:

·有些内容可缓存,而有些不能缓存;

·有些内容可以缓存几分钟,而有些内容可以缓存很长时间;

 

所以作为web对象来讲,因为一个web页面有多个对象的组成的从而根据对象的生成的工作原理不一样,那么它的缓存的时间也不一样的

当客户端请求的时候就算没有共享缓存服务器,那也必须控制缓存时长的

当客户端请求的时候就算没有公共缓存服务器,也必须控制缓存机制的,因为客户端浏览器也是有缓存的,所以缓存几乎在web服务上是恒久存在的

 

服务器端如果是动态页面的话,那么每个页面生成的时候,可以自行控制它的缓存时长,通过http首部告诉客户端如何进行缓存

在http1.0 也支持缓存,只不过缓存首部非常粗糙

http 1.1 极大增强了缓存能力的,增加了很多可以灵活控制缓存的机制

http 1.1在缓存服务器上尽管服务器告知浏览器可以缓存多久,但公用服务器应该缓存其数据

作为管理人员来讲必须去定制缓存策略,不应该遵循源站给出的策略

 

条件式请求首部

if-modified-since

服务器将缓存文件的缓存时间与请求本地时间戳比较后,如果发现没有改认为此资源可以继续使用,而且不用再次通知客户端被称为 not-modified

所以not- modified的响应码是304 只要客户端收到304响应码就认为没有改变,那么它的缓存将会继续使用,由于发一次新请求就意味着页面缓存时长又可以继续使用

如果资源在设置时长周期内更改了资源,那么客户端则不会知道其已经过期,会仍然用之前的数据使用的,这就是非条件式的劣势了

有了条件式请求,那么我们每次都可以发起新请求,服务器也不用响应内容只告知我们是否更改即可,所以这种情况下对服务器带宽占用量是非常小

因此尽可能得到比较新的结果

 

因此我们如果使用expires(HTTP1.0较弱的缓存控制机制)还有一些缺陷,比如:

有些服务器时间是不一致的,所有的结果都是根据本地的时间返回的,所以用这种计时法也不是非常靠谱

所以在http1.1中又引入一个新的机制,为cache-cotrol

 

cache-cotrol

其有了一个新的机制为max-age,最大缓存时长,这就是一个相对计时方法

所以是说http 1.1对缓存功能做了极大的改进,就算具备了条件式请求功能,if modified since

但有些网站内容的修改频度非常高,因为有些内容是动态生成的 以毫秒级别来管理

第一次请求给了时间戳,第二次请求虽然内容改了 但是最近一次修改时间戳没有变

例:

假如目前时间是10:00:00

但是出于第1毫秒上,请求的页面,其将页面返回给我们,他们将页面最近一次修改时间10:10:00 返回给我们的资源,到10:10:10 又去请求此资源,那么他的时间戳还是10:00:00 

这时我们去请求,首先询问服务器:在10:00:00后是否被修改,服务器端虽然修改了资源但是会对比时间戳,发现时间戳没有改变,那么直接返回响应代码,但实际上资源已经改变了

如果对于某些频度变化非常高的站点来讲,有可能不适用的

那http1.1又引入了一个新的机制叫Etag

 

Etag

一旦时间发送改变,则etag也随之发生改变,当客户端请求的时候不再去对比时间戳,而是询问egtag标签是否对应

如果对应则返回not-modified

如果不对应则返回新的数据,所以新的客户端请求用的是新的首部

 

最常用的请求首部

Not-modified/If-modfied-since

Etag/If-none-match

都是缓存中要用到的跟缓存相关的http首部信息,除此之外,尤其是第一组Not-modified/If-modfied-since用到是最多的

 

 

缓存相关的http首部

HTTP协议提供了多个首部用以实现页面缓存及缓存失效的相关功能,这其中最常用的有:
(1)Expires:用于指定某web对象的过期日期/时间,通常为GMT格式;一般不应该将此设定的未来过长的时间,一年的长度对大多场景来说足矣;其常用于为纯静态内容如JavaScripts样式表或图片指定缓存周期;

(2)Cache-Control:用于定义所有的缓存机制都必须遵循的缓存指示,这些指示是一些特定的指令,包括public、private、no-cache(表示可以存储,但在重新验正其有效性之前不能用于响应客户端请求)、no-store、max-age、s-maxage以及must-revalidate等;Cache-Control中设定的时间会覆盖Expires中指定的时间;


(3)Etag:响应首部,用于在响应报文中为某web资源定义版本标识符;

(4)Last-Mofified:响应首部,用于回应客户端关于Last-Modified-Since或If-None-Match首部的请求,以通知客户端其请求的web对象最近的修改时间;

(5)If-Modified-Since:条件式请求首部,如果在此首部指定的时间后其请求的web内容发生了更改,则服务器响应更改后的内容,否则,则响应304(not modified);

(6)If-None-Match:条件式请求首部;web服务器为某web内容定义了Etag首部,客户端请求时能获取并保存这个首部的值(即标签);而后在后续的请求中会通过If-None-Match首部附加其认可的标签列表并让服务器端检验其原始内容是否有可以与此列表中的某标签匹配的标签;如果有,则响应304,否则,则返回原始内容;

(7)Vary:响应首部,原始服务器根据请求来源的不同响应的可能会有所不同的首部,最常用的是Vary:Accept-Encoding,用于通知缓存机制其内容看起来可能不同于用户请求时Accept-Encoding-header首部标识的编码格式;

(8)Age:缓存服务器可以发送的一个额外的响应首部,用于指定响应的有效期限;浏览器通常根据此首部决定内容的缓存时长;如果响应报文首部还使用了max-age指令,那么缓存的有效时长为“max-age减去Age”的结果;

 

观察http首部

HTTP缓存工作流程详解_第3张图片

可以看到,我们为了打开一个页面,每个资源都是单独获取的

第一列为资源路径

第二列为请求方法GET

第三列为资源获取状态 200 为正常 302为重定向

第四列为类型

第五列为发起者 从哪里访问到哪里

第六列为大小 大多数为fornt cache

第七列为资源获取的时长,可看到好多资源都是并行获取的

第八列为争取时间线,请求主页面本身一共请求了多长时间

 

资源并不是同时获取的,而后浏览器的并行速度也是有限的

而每个资源从请求开始到结束可能会经过很长时间,如果是经过域名进行请求的,而都会有DNS解析记录,如下所示

HTTP缓存工作流程详解_第4张图片

   

我们随便选择一个资源

 

并查看首部信息

HTTP缓存工作流程详解_第5张图片

 

查看响应结果response

HTTP缓存工作流程详解_第6张图片

 HTTP缓存工作流程详解_第7张图片

支持Gzip压缩,并且支持deflate sdch编码格式

支持的语言 zh-CN等

cache-control   为no-cache (允许使用缓存,但是必须使用验证)

connection     可以使用长连接

cookie          略

host            为请求的主机名称

Pragma          仍然缓存头部

user-agent     用户浏览器的类型

 

服务器构建响应的信息:

HTTP缓存工作流程详解_第8张图片

cache-control 发送控制首部 为 no-store 不能缓存

使用gzip压缩

类型为html文档

访问时间及过期时间,说明不能缓存

if-modified-since set   自动这个时间之后检测是否发生修改

keepalive       保存的最长连接为多久

load-balancing 是否做负载均衡

server         用的哪个web容器

set-cookie     生成cookie

vary            说明客户端如果使用缓存的话是否检测浏览器支持压缩机制

 

http过期机制如何控制缓存功能

HTTP缓存工作流程详解_第9张图片

 

用户请求/foo 这么一个url,经过1.1的http协议进行请求

首先发送至公共缓存,如果cache没有数据则代理到源服务器,

源服务器构建响应至cache

再由cache构建响应给客户端

 

于是,客户端第二次发送请求了:

第二次请求的同样的内容,发送请求后,cache收到请求

发现cache数据中已存在此数据,注意的是这里不会发送任何请求的,因为资源没有过期,没有过期就意味着资源是不会去进行验证的,所以直接构建响应给用户

如果过期了则直接向源服务器发送请求

 

基于Etag/if-none-match & LastModfied/If-modified-since控制缓存机制

HTTP缓存工作流程详解_第10张图片

如果资源没有修改 第二次请求,cache服务器需要将用户的请求转发至源服务器,但是在请求中加了if-modified-since:thu 

自从thu 这个时间戳是否发生改变

如果没有改变源服务器则发送304,告知cache没有改变,于是cache自行将自身的缓存中将数据拿出,构建响应报文并传输出去,这段过程只是验证过程,而没有任何的数据传输

 

如果数据修改了,服务器端会响应 200 ok 

意味着请求的资源存在,而且回应last-modified: sun 一个新的时间戳

因此将数据返回给cache

这段期间一定会有数据在传输

 

事实上我们可以将这些组合起来应用

但是需要注意的是expiration 的优先级是高于validation的

 

组合使用

HTTP缓存工作流程详解_第11张图片

可以看到,服务器发送的既有过期时长又有etag

如果没有过期时长的话每次应该发if-none-match

但是这里又有cache-control,意味着在cache-control过期之前,etag是不会发生作用的

所以在cache-control过期之前,其不会在源服务器上去验证

只要没有过期则直接构建响应

 

但如果过期才会去验证if-none-match ,如果none-match没有修改则直接返回304给cache server 

 

所以组合使用有两种缓存机制效果,这就是所谓过期时间优于校验方式

if none match 、if modified since 都属于校验方式

只要有过期时间,在过期之前都不会重新验证

 

但是如果加了cache-control 里面有必须验证的参数则必须对其进行验证

  

如果过期则:

HTTP缓存工作流程详解_第12张图片

 

如果过期了则 if-none-match

如果没有修改则返回304

 

在过期之前一直是不请求的

 

 

END,感谢各位