learn.lianglianglee.com/专栏/透视HTTP协议/22 冷链周转:HTTP的缓存代理.md.html
2022-05-11 19:04:14 +08:00

679 lines
27 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>22 冷链周转HTTP的缓存代理.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/透视HTTP协议/00 开篇词To Be a HTTP Hero.md.html">00 开篇词To Be a HTTP Hero.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/01 时势与英雄HTTP的前世今生.md.html">01 时势与英雄HTTP的前世今生.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/02 HTTP是什么HTTP又不是什么.md.html">02 HTTP是什么HTTP又不是什么.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/03 HTTP世界全览与HTTP相关的各种概念.md.html">03 HTTP世界全览与HTTP相关的各种概念.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/04 HTTP世界全览与HTTP相关的各种协议.md.html">04 HTTP世界全览与HTTP相关的各种协议.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/05 常说的“四层”和“七层”到底是什么?“五层”“六层”哪去了?.md.html">05 常说的“四层”和“七层”到底是什么?“五层”“六层”哪去了?.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/06 域名里有哪些门道?.md.html">06 域名里有哪些门道?.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/07 自己动手搭建HTTP实验环境.md.html">07 自己动手搭建HTTP实验环境.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/08 键入网址再按下回车,后面究竟发生了什么?.md.html">08 键入网址再按下回车,后面究竟发生了什么?.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/09 HTTP报文是什么样子的.md.html">09 HTTP报文是什么样子的.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/10 应该如何理解请求方法?.md.html">10 应该如何理解请求方法?.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/11 你能写出正确的网址吗?.md.html">11 你能写出正确的网址吗?.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/12 响应状态码该怎么用?.md.html">12 响应状态码该怎么用?.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/13 HTTP有哪些特点.md.html">13 HTTP有哪些特点.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/14 HTTP有哪些优点又有哪些缺点.md.html">14 HTTP有哪些优点又有哪些缺点.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/15 海纳百川HTTP的实体数据.md.html">15 海纳百川HTTP的实体数据.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/16 把大象装进冰箱HTTP传输大文件的方法.md.html">16 把大象装进冰箱HTTP传输大文件的方法.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/17 排队也要讲效率HTTP的连接管理.md.html">17 排队也要讲效率HTTP的连接管理.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/18 四通八达HTTP的重定向和跳转.md.html">18 四通八达HTTP的重定向和跳转.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/19 让我知道你是谁HTTP的Cookie机制.md.html">19 让我知道你是谁HTTP的Cookie机制.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/20 生鲜速递HTTP的缓存控制.md.html">20 生鲜速递HTTP的缓存控制.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/21 良心中间商HTTP的代理服务.md.html">21 良心中间商HTTP的代理服务.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/透视HTTP协议/22 冷链周转HTTP的缓存代理.md.html">22 冷链周转HTTP的缓存代理.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/23 HTTPS是什么SSLTLS又是什么.md.html">23 HTTPS是什么SSLTLS又是什么.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/24 固若金汤的根本(上):对称加密与非对称加密.md.html">24 固若金汤的根本(上):对称加密与非对称加密.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/25 固若金汤的根本(下):数字签名与证书.md.html">25 固若金汤的根本(下):数字签名与证书.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/26 信任始于握手TLS1.2连接过程解析.md.html">26 信任始于握手TLS1.2连接过程解析.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/27 更好更快的握手TLS1.3特性解析.md.html">27 更好更快的握手TLS1.3特性解析.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/28 连接太慢该怎么办HTTPS的优化.md.html">28 连接太慢该怎么办HTTPS的优化.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/29 我应该迁移到HTTPS吗.md.html">29 我应该迁移到HTTPS吗.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/30 时代之风HTTP2特性概览.md.html">30 时代之风HTTP2特性概览.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/31 时代之风HTTP2内核剖析.md.html">31 时代之风HTTP2内核剖析.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/32 未来之路HTTP3展望.md.html">32 未来之路HTTP3展望.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/33 我应该迁移到HTTP2吗.md.html">33 我应该迁移到HTTP2吗.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/34 Nginx高性能的Web服务器.md.html">34 Nginx高性能的Web服务器.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/35 OpenResty更灵活的Web服务器.md.html">35 OpenResty更灵活的Web服务器.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/36 WAF保护我们的网络服务.md.html">36 WAF保护我们的网络服务.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/37 CDN加速我们的网络服务.md.html">37 CDN加速我们的网络服务.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/38 WebSocket沙盒里的TCP.md.html">38 WebSocket沙盒里的TCP.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/39 HTTP性能优化面面观.md.html">39 HTTP性能优化面面观.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/40 HTTP性能优化面面观.md.html">40 HTTP性能优化面面观.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/结束语 做兴趣使然的Hero.md.html">结束语 做兴趣使然的Hero.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>22 冷链周转HTTP的缓存代理</h1>
<p>在[第 20 讲]中,我介绍了 HTTP 的缓存控制,[第 21 讲]我介绍了 HTTP 的代理服务。那么,把这两者结合起来就是这节课所要说的“<strong>缓存代理</strong>”,也就是支持缓存控制的代理服务。</p>
<p>之前谈到缓存时,主要讲了客户端(浏览器)上的缓存控制,它能够减少响应时间、节约带宽,提升客户端的用户体验。</p>
<p>但 HTTP 传输链路上,不只是客户端有缓存,服务器上的缓存也是非常有价值的,可以让请求不必走完整个后续处理流程,“就近”获得响应结果。</p>
<p>特别是对于那些“读多写少”的数据,例如突发热点新闻、爆款商品的详情页,一秒钟内可能有成千上万次的请求。即使仅仅缓存数秒钟,也能够把巨大的访问流量挡在外面,让 RPSrequest per second降低好几个数量级减轻应用服务器的并发压力对性能的改善是非常显著的。</p>
<p>HTTP 的服务器缓存功能主要由代理服务器来实现(即缓存代理),而源服务器系统内部虽然也经常有各种缓存(如 Memcache、Redis、Varnish 等),但与 HTTP 没有太多关系,所以这里暂且不说。</p>
<h2>缓存代理服务</h2>
<p>我还是沿用“生鲜速递 + 便利店”的比喻,看看缓存代理是怎么回事。</p>
<p>便利店作为超市的代理,生意非常红火,顾客和超市双方都对现状非常满意。但时间一长,超市发现还有进一步提升的空间,因为每次便利店接到顾客的请求后都要专车跑一趟超市,还是挺麻烦的。</p>
<p>干脆这样吧,给便利店配发一个大冰柜。水果海鲜什么的都可以放在冰柜里,只要产品在保鲜期内,就允许顾客直接从冰柜提货。这样便利店就可以一次进货多次出货,省去了超市之间的运输成本。</p>
<p><img src="assets/5e8d10b5758685850aeed2a473a6cdc2.png" alt="img" /></p>
<p>通过这个比喻,你可以看到:在没有缓存的时候,代理服务器每次都是直接转发客户端和服务器的报文,中间不会存储任何数据,只有最简单的中转功能。</p>
<p>加入了缓存后就不一样了。</p>
<p>代理服务收到源服务器发来的响应数据后需要做两件事。第一个当然是把报文转发给客户端,而第二个就是把报文存入自己的 Cache 里。</p>
<p>下一次再有相同的请求,代理服务器就可以直接发送 304 或者缓存数据,不必再从源服务器那里获取。这样就降低了客户端的等待时间,同时节约了源服务器的网络带宽。</p>
<p>在 HTTP 的缓存体系中,缓存代理的身份十分特殊,它“既是客户端,又是服务器”,同时也“既不是客户端,又不是服务器”。</p>
<p>说它“即是客户端又是服务器”,是因为它面向源服务器时是客户端,在面向客户端时又是服务器,所以它即可以用客户端的缓存控制策略也可以用服务器端的缓存控制策略,也就是说它可以同时使用第 20 讲的各种“Cache-Control”属性。</p>
<p>但缓存代理也“即不是客户端又不是服务器”因为它只是一个数据的“中转站”并不是真正的数据消费者和生产者所以还需要有一些新的“Cache-Control”属性来对它做特别的约束。</p>
<h2>源服务器的缓存控制</h2>
<p>[第 20 讲]介绍了 4 种服务器端的“Cache-Control”属性max-age、no_store、no_cache 和 must-revalidate你应该还有印象吧</p>
<p>这 4 种缓存属性可以约束客户端,也可以约束代理。</p>
<p>但客户端和代理是不一样的,客户端的缓存只是用户自己使用,而代理的缓存可能会为非常多的客户端提供服务。所以,需要对它的缓存再多一些限制条件。</p>
<p>首先,我们要区分客户端上的缓存和代理上的缓存,可以使用两个新属性“<strong>private</strong>”和“<strong>public</strong>”。</p>
<p>“private”表示缓存只能在客户端保存是用户“私有”的不能放在代理上与别人共享。而“public”的意思就是缓存完全开放谁都可以存谁都可以用。</p>
<p>比如你登录论坛返回的响应报文里用“Set-Cookie”添加了论坛 ID这就属于私人数据不能存在代理上。不然别人访问代理获取了被缓存的响应就麻烦了。</p>
<p>其次缓存失效后的重新验证也要区分开即使用条件请求“Last-modified”和“ETag”<strong>must-revalidate</strong>”是只要过期就必须回源服务器验证,而新的“<strong>proxy-revalidate</strong>”只要求代理的缓存过期后必须验证,客户端不必回源,只验证到代理这个环节就行了。</p>
<p>再次,缓存的生存时间可以使用新的“<strong>s-maxage</strong>s 是 share 的意思,注意 maxage 中间没有“-”只限定在代理上能够存多久而客户端仍然使用“max_age”。</p>
<p>还有一个代理专用的属性“<strong>no-transform</strong>”。代理有时候会对缓存下来的数据做一些优化,比如把图片生成 png、webp 等几种格式方便今后的请求处理而“no-transform”就会禁止这样做不许“偷偷摸摸搞小动作”。</p>
<p>这些新的缓存控制属性比较复杂,还是用“便利店冷柜”来举例好理解一些。</p>
<p>水果上贴着标签“private, max-age=5”。这就是说水果不能放进冷柜必须直接给顾客保鲜期 5 天,过期了还得去超市重新进货。</p>
<p>冻鱼上贴着标签“public, max-age=5, s-maxage=10”。这个的意思就是可以在冰柜里存 10 天,但顾客那里只能存 5 天,过期了可以来便利店取,只要在 10 天之内就不必再找超市。</p>
<p>排骨上贴着标签“max-age=30, proxy-revalidate, no-transform”。因为缓存默认是 public 的,那么它在便利店和顾客的冰箱里就都可以存 30 天,过期后便利店必须去超市进新货,而且不能擅自把“大排”改成“小排”。</p>
<p>下面的流程图是完整的服务器端缓存控制策略,可以同时控制客户端和代理。</p>
<p><img src="assets/dd65b95de96d78552a92757d58de6a37.png" alt="img" /></p>
<p>我还要提醒你一点源服务器在设置完“Cache-Control”后必须要为报文加上“Last-modified”或“ETag”字段。否则客户端和代理后面就无法使用条件请求来验证缓存是否有效也就不会有 304 缓存重定向。</p>
<h2>客户端的缓存控制</h2>
<p>说完了服务器端的缓存控制策略,稍微歇一口气,我们再来看看客户端。</p>
<p>客户端在 HTTP 缓存体系里要面对的是代理和源服务器,也必须区别对待,这里我就直接上图了,来个“看图说话”。</p>
<p><img src="assets/81b9609c5f50281ec3d53fb4d299b690.png" alt="img" /></p>
<p>max-age、no_store、no_cache 这三个属性在[第 20 讲]已经介绍过了,它们也是同样作用于代理和源服务器。</p>
<p>关于缓存的生存时间,多了两个新属性“<strong>max-stale</strong>”和“<strong>min-fresh</strong>”。</p>
<p>“max-stale”的意思是如果代理上的缓存过期了也可以接受但不能过期太多超过 x 秒也会不要。“min-fresh”的意思是缓存必须有效而且必须在 x 秒后依然有效。</p>
<p>比如草莓上贴着标签“max-age=5”现在已经在冰柜里存了 7 天。如果有请求“max-stale=2”意思是过期两天也能接受所以刚好能卖出去。</p>
<p>但要是“min-fresh=1”这是绝对不允许过期的就不会买走。这时如果有另外一个菠萝是“max-age=10”那么“7+1&lt;10”在一天之后还是新鲜的所以就能卖出去。</p>
<p>有的时候客户端还会发出一个特别的“<strong>only-if-cached</strong>”属性,表示只接受代理缓存的数据,不接受源服务器的响应。如果代理上没有缓存或者缓存过期,就应该给客户端返回一个 504Gateway Timeout</p>
<h2>实验环境</h2>
<p>信息量有些大,到这里你是不是有点头疼了,好在我们还有实验环境,用 URI“/22-1”试一下吧。</p>
<p>它设置了“Cache-Control: public, max-age=10, s-maxage=30”数据可以在浏览器里存 10 秒,在代理上存 30 秒,你可以反复刷新,看看代理和源服务器是怎么响应的,同样也可以配合 Wireshark 抓包。</p>
<p>代理在响应报文里还额外加了“X-Cache”“X-Hit”等自定义头字段表示缓存是否命中和命中率方便你观察缓存代理的工作情况。</p>
<p><img src="assets/4d210fa1adccb7299d632ed7e66391e8.png" alt="img" /></p>
<h2>其他问题</h2>
<p>缓存代理的知识就快讲完了,下面再简单说两个相关的问题。</p>
<p>第一个是“<strong>Vary</strong>”字段,在[第 15 讲]曾经说过,它是内容协商的结果,相当于报文的一个版本标记。</p>
<p>同一个请求经过内容协商后可能会有不同的字符集、编码、浏览器等版本。比如“Vary: Accept-Encoding”“Vary: User-Agent”缓存代理必须要存储这些不同的版本。</p>
<p>当再收到相同的请求时代理就读取缓存里的“Vary”对比请求头里相应的“ Accept-Encoding”“User-Agent”等字段如果和上一个请求的完全匹配比如都是“gzip”“Chrome”就表示版本一致可以返回缓存的数据。</p>
<p>另一个问题是“<strong>Purge</strong>”,也就是“缓存清理”,它对于代理也是非常重要的功能,例如:</p>
<ul>
<li>过期的数据应该及时淘汰,避免占用空间;</li>
<li>源站的资源有更新,需要删除旧版本,主动换成最新版(即刷新);</li>
<li>有时候会缓存了一些本不该存储的信息,例如网络谣言或者危险链接,必须尽快把它们删除。</li>
</ul>
<p>清理缓存的方法有很多比较常用的一种做法是使用自定义请求方法“PURGE”发给代理服务器要求删除 URI 对应的缓存数据。</p>
<h2>小结</h2>
<ol>
<li>计算机领域里最常用的性能优化手段是“时空转换”也就是“时间换空间”或者“空间换时间”HTTP 缓存属于后者;</li>
<li>缓存代理是增加了缓存功能的代理服务,缓存源服务器的数据,分发给下游的客户端;</li>
<li>“Cache-Control”字段也可以控制缓存代理常用的有“private”“s-maxage”“no-transform”等同样必须配合“Last-modified”“ETag”等字段才能使用</li>
<li>缓存代理有时候也会带来负面影响,缓存不良数据,需要及时刷新或删除。</li>
</ol>
<h2>课下作业</h2>
<ol>
<li>加入了代理后 HTTP 的缓存复杂了很多,试着用自己的语言把这些知识再整理一下,画出有缓存代理时浏览器的工作流程图,加深理解。</li>
<li>缓存的时间策略很重要,太大太小都不好,你觉得应该如何设置呢?</li>
</ol>
<p>欢迎你把自己的学习体会写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,也欢迎把文章分享给你的朋友。</p>
<p><img src="assets/54fddf71fc45f1055eff0b59b67dffb8.png" alt="unpreview" /></p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/透视HTTP协议/21 良心中间商HTTP的代理服务.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/透视HTTP协议/23 HTTPS是什么SSLTLS又是什么.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997d100b4e3cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>