http学习 缓存代理
一、前言
之前谈到缓存时,主要讲了客户端(浏览器)上的缓存控制,它能够减少响应时间、节约带宽,提升客户端的用户体验。
但 HTTP 传输链路上,不只是客户端有缓存,服务器上的缓存也是非常有价值的,可以让请求不必走完整个后续处理流程,“就近”获得响应结果。
特别是对于那些“读多写少”的数据,例如突发热点新闻、爆款商品的详情页,一秒钟内可能有成千上万次的请求。即使仅仅缓存数秒钟,也能够把巨大的访问流量挡在外面,让 RPS(request per second)降低好几个数量级,减轻应用服务器的并发压力,对性能的改善是非常显著的。
HTTP 的服务器缓存功能主要由代理服务器来实现(即缓存代理),而源服务器系统内部虽然也经常有各种缓存(如 Memcache、Redis、Varnish 等),但与 HTTP 没有太多关系,所以这里暂且不说。
二、缓存代理服务
在没有缓存的时候,代理服务器每次都是直接转发客户端和服务器的报文,中间不会存储任何数据,只有最简单的中转功能。
加入了缓存后,代理服务收到源服务器发来的响应数据后需要做两件事:第一个当然是把报文转发给客户端,而第二个就是把报文存入自己的 Cache 里。下一次再有相同的请求,代理服务器就可以直接发送 304 或者缓存数据,不必再从源服务器那里获取。这样就降低了客户端的等待时间,同时节约了源服务器的网络带宽。
三、源服务器的缓存控制
4 种服务器端的“Cache-Control”属性:max-age、no-store、no-cache 和 must-revalidate,可以约束客户端,也可以约束代理。
但客户端和代理是不一样的,客户端的缓存只是用户自己使用,而代理的缓存可能会为非常多的客户端提供服务。所以,需要对它的缓存再多一些限制条件。
首先,我们要区分客户端上的缓存和代理上的缓存,可以使用两个新属性“private”和“public”。
- “private”表示缓存只能在客户端保存,是用户“私有”的,不能放在代理上与别人共享。
- “public”的意思就是缓存完全开放,谁都可以存,谁都可以用。
其次,缓存失效后的重新验证也要区分开(即使用条件请求“Last-modified”和“ETag”)
- “must-revalidate”是只要过期就必须回源服务器验证
- “proxy-revalidate”只要求代理的缓存过期后必须验证,客户端不必回源,只验证到代理这个环节就行了。
缓存的生存时间可以使用新的“s-maxage”(s 是 share 的意思,注意 maxage 中间没有“-”),只限定在代理上能够存多久,而客户端仍然使用“max-age”。
还有一个代理专用的属性“no-transform”。代理有时候会对缓存下来的数据做一些优化,比如把图片生成 png、webp 等几种格式,方便今后的请求处理,而“no-transform”就会禁止这样做,不许“偷偷摸摸搞小动作”。
举个栗子:
水果上贴着标签“private, max-age=5”。这就是说水果不能放进冷柜,必须直接给顾客,保鲜期 5 天,过期了还得去超市重新进货。
冻鱼上贴着标签“public, max-age=5, s-maxage=10”。这个的意思就是可以在冰柜里存 10 天,但顾客那里只能存 5 天,过期了可以来便利店取,只要在 10 天之内就不必再找超市。
排骨上贴着标签“max-age=30, proxy-revalidate, no-transform”。因为缓存默认是 public 的,那么它在便利店和顾客的冰箱里就都可以存 30 天,过期后便利店必须去超市进新货,而且不能擅自把“大排”改成“小排”。
下面的流程图是完整的服务器端缓存控制策略,可以同时控制客户端和代理。
另外,源服务器在设置完“Cache-Control”后必须要为报文加上“Last-modified”或“ETag”字段。否则,客户端和代理后面就无法使用条件请求来验证缓存是否有效,也就不会有 304 缓存重定向。
四、客户端的缓存控制
客户端在 HTTP 缓存体系里要面对的是代理和源服务器,也必须区别对待。
max-age、no-store、no-cache 已经介绍过了,它们也是同样作用于代理和源服务器。
关于缓存的生存时间,多了两个新属性“max-stale”和“min-fresh”。
“max-stale”的意思是如果代理上的缓存过期了也可以接受,但不能过期太多,超过 x 秒也会不要。
“min-fresh”的意思是缓存必须有效,而且必须在 x 秒后依然有效。
比如,草莓上贴着标签“max-age=5”,现在已经在冰柜里存了 7 天。如果有请求“max-stale=2”,意思是过期两天也能接受,所以刚好能卖出去。
但要是“min-fresh=1”,这是绝对不允许过期的,就不会买走。这时如果有另外一个菠萝是“max-age=10”,那么“7+1<10”,在一天之后还是新鲜的,所以就能卖出去。
有的时候客户端还会发出一个特别的“only-if-cached”属性,表示只接受代理缓存的数据,不接受源服务器的响应。如果代理上没有缓存或者缓存过期,就应该给客户端返回一个 504(Gateway Timeout)。
其他问题
两个相关的问题。
第一个是“Vary”字段,它是内容协商的结果,相当于报文的一个版本标记。
同一个请求,经过内容协商后可能会有不同的字符集、编码、浏览器等版本。比如,“Vary: Accept-Encoding”“Vary: User-Agent”,缓存代理必须要存储这些不同的版本。
当再收到相同的请求时,代理就读取缓存里的“Vary”,对比请求头里相应的“ Accept-Encoding”“User-Agent”等字段,如果和上一个请求的完全匹配,比如都是“gzip”“Chrome”,就表示版本一致,可以返回缓存的数据。
另一个问题是“Purge”,也就是“缓存清理”,它对于代理也是非常重要的功能,例如:过期的数据应该及时淘汰,避免占用空间;源站的资源有更新,需要删除旧版本,主动换成最新版(即刷新);有时候会缓存了一些本不该存储的信息,例如网络谣言或者危险链接,必须尽快把它们删除。
清理缓存的方法有很多,比较常用的一种做法是使用自定义请求方法“PURGE”,发给代理服务器,要求删除 URI 对应的缓存数据。
小结
- 计算机领域里最常用的性能优化手段是“时空转换”,也就是“时间换空间”或者“空间换时间”,HTTP 缓存属于后者;
- 缓存代理是增加了缓存功能的代理服务,缓存源服务器的数据,分发给下游的客户端;
- “Cache-Control”字段也可以控制缓存代理,常用的有“private”“s-maxage”“no-transform”等,同样必须配合“Last-modified”“ETag”等字段才能使用;
- 缓存代理有时候也会带来负面影响,缓存不良数据,需要及时刷新或删除。
问答
1、加入了代理后 HTTP 的缓存复杂了很多,试着用自己的语言把这些知识再整理一下,画出有缓存代理时浏览器的工作流程图,加深理解。
答:
浏览器拿到一个网址的时候,先判断是否允许缓存,允许会先查看本地缓存:
- 有缓存并在缓存可用期那直接拿来用。
- 缓存不存在或者不可用 那需要请求。
浏览器拿到host,判断:
- ip+port
那直接请求对应的服务器
- 域名
那开展一系列的dns递归查询:先拿dns缓存,没有缓存->本地dns服务器->根dns服务器->顶级dns服务器->权威dns服务器->GSLB,查到ip返回最优ip组实现负载均衡,浏览器随机或者轮询取一个ip开始它的http请求之旅。
浏览器判断该网页是否允许缓存,然后添加Cache-Control的各种字段
- no-store是否允许缓存/no-cache缓存必须进行验证/noly-if-cached只接受代理的缓存等
- max-age最大生存时间 max-stale 短时间过期可用 min-fresh 最短有效时间等
- If-Modified-Since/if-None-Match/Last-modified/ETag等字段用于判断服务端是否有更新。
然后将请求发给代理服务器。请求代理服务器,如果是第一次,要经历浏览器和代理服务器的3次tcp握手进行连接,连接成功,发送http请求。
代理服务器拿到请求,首先查看是否允许缓存,允许那就查看自己本地缓存有没有,通过查看max-age/max-stale/min-fresh等信息判断是否过期,没有过期直接拿来用,将数据返回给客户端。如果过期了,代理服务器将用客户端的请求,再次像真实服务器进行请求。如果也是第一次连接,需要经历代理服务器和真实服务器的3次tcp握手,连接成功,发送请求。
真实服务器收到请求之后,通过if-Modified-Since/Last-Modified/if-None-Match/ETag等字段判断是否有更新,没有更新,直接返回304。如果有更新,则将数据打包http response 返回。
返回头字段会添加Cache-Control字段,用来判断缓存的控制策略以及生存周期,
- no-store不允许缓存/no-cache使用缓存必须先验证/must-revalidate缓存不过期可用过期必须重新请求验证/proxy-revalidate缓存过期只要求代理进行请求验证
- private不能在代理层保存只能在客户端保存/public缓存完全开放 s-maxage缓存在代理上可以缓存的时间 no-transform不允许代理对缓存做任何的改动
然后根据业务需求判断该地址是不是需要重定向,如果需要是短期的重定向还是永久的重定向,按需将状态码修改为301或者302。最后真实服务器将数据打包成http相应 回给代理服务器。
代理服务器收到真实服务器的回应数据,首先会查看Cache-Control里的字段,是否允许它进行缓存,如果是private,代理服务器不进行缓存,直接返回给客户端。public则根据s-maxage/no-transform进行缓存,如果可以优化并且代理服务器需要优化,那可能会先优化数据,否则同时将数据回发给客户端。
客户端收到数据,如果是304,则直接拿缓存数据进行渲染,并修改相关缓存变量,比如时间,以及缓存使用策略。如果收到了301或者302,那么客户端会再次发起新的url请求,进行跳转到最终的页面。
最后,底层tcp 经过4次挥手,完成关闭连接。
2、缓存的时间策略很重要,太大太小都不好,你觉得应该如何设置呢?
答:
我觉得需要根据具体情况来定:
- 如果缓存的内容不变,那可以把缓存时间设置为永久。
- 如果缓存的内容会变化,但周期较长,可以根据她的变化周期来设置,比如:一天或一周
- 如果缓存的内容变化频繁,那缓存的过期时间就需要更短了,比如:一分钟
- 如果缓存的内容随时变化,且没啥规律,那还是不用用了
总之是根据场景来的核心是在提速的愿望能实现的前提下,数据也是最新的,否则不如不用缓存,从另一个角度来讲不用缓存几乎是不可能的,缓存在处处使用着,因为计算机本身就在各种各样的使用着缓存。如果完全不用直接从磁盘获取数据,也可以认为是使用缓存的一种特殊情况,缓存的过期时间为零即使即过期。