[实践系列] 主要是让咱们经过实践去加深对一些原理的理解。css
实践系列-前端路由 html
实践系列-Babel原理 前端
实践系列-Promises/A+规范 node
有兴趣的同窗能够关注 实践系列 。 求star求follow~ git
若是以为本身已经掌握浏览器缓存机制知识的同窗,能够直接看实践部分哈~github
1. DNS 缓存 // 虽然说跟标题关系不大,了解一下也不错 2. CDN 缓存 // 虽然说跟标题关系不大,了解一下也不错 3. 浏览器缓存 // 本文将重点介绍并实践
全称 Domain Name System ,即域名系统。web
万维网上做为域名和IP地址相互映射的一个分布式数据库,可以使用户更方便的访问互联网,而不用去记住可以被机器直接读取的IP数串。DNS协议运行在UDP协议之上,使用端口号53。
简单的说,经过域名,最终获得该域名对应的IP地址的过程叫作域名解析(或主机名解析)。数据库
www.dnscache.com (域名) - DNS解析 -> 11.222.33.444 (IP地址)
有dns的地方,就有缓存。浏览器、操做系统、Local DNS、根域名服务器,它们都会对DNS结果作必定程度的缓存。npm
DNS查询过程以下:json
全称 Content Delivery Network,即内容分发网络。
摘录一个形象的比喻,来理解CDN是什么。
10年前,尚未火车票代售点一说,12306.cn更是无从提及。那时候火车票还只能在火车站的售票大厅购买,而我所在的小县城并不通火车,火车票都要去市里的火车站购买,而从我家到县城再到市里,来回就是4个小时车程,简直就是浪费生命。后来就行了,小县城里出现了火车票代售点,甚至乡镇上也有了代售点,能够直接在代售点购买火车票,方便了很多,全市人民不再用在一个点苦逼的排队买票了。
简单的理解CDN就是这些代售点(缓存服务器)的承包商,他为买票者提供了便利,帮助他们在最近的地方(最近的CDN节点)用最短的时间(最短的请求时间)买到票(拿到资源),这样去火车站售票大厅排队的人也就少了。也就减轻了售票大厅的压力(起到分流做用,减轻服务器负载压力)。
用户在浏览网站的时候,CDN会选择一个离用户最近的CDN边缘节点来响应用户的请求,这样海南移动用户的请求就不会千里迢迢跑到北京电信机房的服务器(假设源站部署在北京电信机房)上了。
关于CDN缓存,在浏览器本地缓存失效后,浏览器会向CDN边缘节点发起请求。相似浏览器缓存,CDN边缘节点也存在着一套缓存机制。CDN边缘节点缓存策略因服务商不一样而不一样,但通常都会遵循http标准协议,经过http响应头中的
Cache-control: max-age //后面会提到
的字段来设置CDN边缘节点数据缓存时间。
当浏览器向CDN节点请求数据时,CDN节点会判断缓存数据是否过时,若缓存数据并无过时,则直接将缓存数据返回给客户端;不然,CDN节点就会向服务器发出回源请求,从服务器拉取最新数据,更新本地缓存,并将最新数据返回给客户端。 CDN服务商通常会提供基于文件后缀、目录多个维度来指定CDN缓存时间,为用户提供更精细化的缓存管理。
对着这张图先发呆30秒~
简单来讲,浏览器缓存其实就是浏览器保存经过HTTP获取的全部资源,是浏览器将网络资源存储在本地的一种行为。
你可能会有疑问,浏览器存储了资源,那它把资源存储在哪里呢?
MemoryCache顾名思义,就是将资源缓存到内存中,等待下次访问时不须要从新下载资源,而直接从内存中获取。Webkit早已支持memoryCache。
目前Webkit资源分红两类,一类是主资源,好比HTML页面,或者下载项,一类是派生资源,好比HTML页面中内嵌的图片或者脚本连接,分别对应代码中两个类:MainResourceLoader和SubresourceLoader。虽然Webkit支持memoryCache,可是也只是针对派生资源,它对应的类为CachedResource,用于保存原始数据(好比CSS,JS等),以及解码过的图片数据。
DiskCache顾名思义,就是将资源缓存到磁盘中,等待下次访问时不须要从新下载资源,而直接从磁盘中获取,它的直接操做对象为CurlCacheManager。
相同点 | 只能存储一些派生类资源文件 | 只能存储一些派生类资源文件 |
不一样点 | 退出进程时数据会被清除 | 退出进程时数据不会被清除 |
存储资源 | 通常脚本、字体、图片会存在内存当中 | 通常非脚本会存在内存当中,如css等 |
由于CSS文件加载一次就可渲染出来,咱们不会频繁读取它,因此它不适合缓存到内存中,可是js之类的脚本却随时可能会执行,若是脚本在磁盘当中,咱们在执行脚本的时候须要从磁盘取到内存中来,这样IO开销就很大了,有可能致使浏览器失去响应。
浏览器再向服务器请求资源时,首先判断是否命中强缓存,再判断是否命中协商缓存!
1.减小了冗余的数据传输
2.减小了服务器的负担,大大提高了网站的性能
3.加快了客户端加载网页的速度
浏览器在加载资源时,会先根据本地缓存资源的 header 中的信息判断是否命中强缓存,若是命中则直接使用缓存中的资源不会再向服务器发送请求。
这里的 header 中的信息指的是 expires 和 cahe-control.
该字段是 http1.0 时的规范,它的值为一个绝对时间的 GMT 格式的时间字符串,好比 Expires:Mon,18 Oct 2066 23:59:59 GMT。这个时间表明着这个资源的失效时间,在此时间以前,即命中缓存。这种方式有一个明显的缺点,因为失效时间是一个绝对时间,因此当服务器与客户端时间误差较大时,就会致使缓存混乱。
Cache-Control 是 http1.1 时出现的 header 信息,主要是利用该字段的 max-age 值来进行判断,它是一个相对时间,例如 Cache-Control:max-age=3600,表明着资源的有效期是 3600 秒。cache-control 除了该字段外,还有下面几个比较经常使用的设置值:
no-cache:须要进行协商缓存,发送请求到服务器确认是否使用缓存。
no-store:禁止使用缓存,每一次都要从新请求数据。
public:能够被全部的用户缓存,包括终端用户和 CDN 等中间代理服务器。
private:只能被终端用户的浏览器缓存,不容许 CDN 等中继缓存服务器对其缓存。
Cache-Control 与 Expires 能够在服务端配置同时启用,同时启用的时候 Cache-Control 优先级高。
当强缓存没有命中的时候,浏览器会发送一个请求到服务器,服务器根据 header 中的部分信息来判断是否命中缓存。若是命中,则返回 304 ,告诉浏览器资源未更新,可以使用本地的缓存。
这里的 header 中的信息指的是 Last-Modify/If-Modify-Since 和 ETag/If-None-Match.
浏览器第一次请求一个资源的时候,服务器返回的 header 中会加上 Last-Modify,Last-modify 是一个时间标识该资源的最后修改时间。
当浏览器再次请求该资源时,request 的请求头中会包含 If-Modify-Since,该值为缓存以前返回的 Last-Modify。服务器收到 If-Modify-Since 后,根据资源的最后修改时间判断是否命中缓存。
若是命中缓存,则返回 304,而且不会返回资源内容,而且不会返回 Last-Modify。
缺点:
短期内资源发生了改变,Last-Modified 并不会发生变化。
周期性变化。若是这个资源在一个周期内修改回原来的样子了,咱们认为是可使用缓存的,可是 Last-Modified 可不这样认为,所以便有了 ETag。
与 Last-Modify/If-Modify-Since 不一样的是,Etag/If-None-Match 返回的是一个校验码。ETag 能够保证每个资源是惟一的,资源变化都会致使 ETag 变化。服务器根据浏览器上送的 If-None-Match 值来判断是否命中缓存。
与 Last-Modified 不同的是,当服务器返回 304 Not Modified 的响应时,因为 ETag 从新生成过,response header 中还会把这个 ETag 返回,即便这个 ETag 跟以前的没有变化。
Last-Modified 与 ETag 是能够一块儿使用的,服务器会优先验证 ETag,一致的状况下,才会继续比对 Last-Modified,最后才决定是否返回 304。
当浏览器再次访问一个已经访问过的资源时,它会这样作:
1.看看是否命中强缓存,若是命中,就直接使用缓存了。
2.若是没有命中强缓存,就发请求到服务器检查是否命中协商缓存。
3.若是命中协商缓存,服务器会返回 304 告诉浏览器使用本地缓存。
4.不然,返回最新的资源。
talk is cheap , show me the code 。让咱们经过实践得真知~
在实践时,注意浏览器控制台Network的按钮不要打钩。
如下咱们只对强缓存的Cache-Control和协商缓存的ETag进行实践,其余小伙伴们能够本身实践~
package.json
{ "name": "webcache", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "cache": "nodemon ./index.js" }, "author": "webfansplz", "license": "MIT", "devDependencies": { "@babel/core": "^7.2.2", "@babel/preset-env": "^7.2.3", "@babel/register": "^7.0.0", "koa": "^2.6.2", "koa-static": "^5.0.0" }, "dependencies": { "nodemon": "^1.18.9" } }
.babelrc
{ "presets": [ [ "@babel/preset-env", { "targets": { "node": "current" } } ] ] }
index.js
require('@babel/register'); require('./webcache.js');
webcache.js
import Koa from 'koa'; import path from 'path'; //静态资源中间件 import resource from 'koa-static'; const app = new Koa(); const host = 'localhost'; const port = 4396; app.use(resource(path.join(__dirname, './static'))); app.listen(port, () => { console.log(`server is listen in ${host}:${port}`); });
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>前端缓存</title> <style> .web-cache img { display: block; width: 100%; } </style> </head> <body> <div class="web-cache"><img src="./web.png" /></div> </body> </html>
咱们用koa先起个web服务器,而后用koa-static这个中间件作静态资源配置,并在static文件夹下放了index.html和web.png。
Ok,接下来咱们来启动服务。
npm run cache
server is listen in localhost:4396。
接下来咱们打开浏览器输入地址:
localhost:4396
完美~(哈哈,猪仔别喷我,纯属娱乐效果)
Ok!!!接下来咱们来实践下强缓存。~
webcache.js
import Koa from 'koa'; import path from 'path'; //静态资源中间件 import resource from 'koa-static'; const app = new Koa(); const host = 'localhost'; const port = 4396; app.use(async (ctx, next) => { // 设置响应头Cache-Control 设置资源有效期为300秒 ctx.set({ 'Cache-Control': 'max-age=300' }); await next(); }); app.use(resource(path.join(__dirname, './static'))); app.listen(port, () => { console.log(`server is listen in ${host}:${port}`); });
咱们刷新页面能够看到响应头的Cache-Control变成了max-age=300。
咱们顺便来验证下三级缓存原理
咱们刚进行了网络请求,浏览器把web.png存进了磁盘和内存中。
根据三级缓存原理,咱们会先在内存中找资源,咱们来刷新页面。
咱们在红线部分看到了, from memory cache。nice~
ok,接下来,咱们关掉该页面,再从新打开。由于内存是存在进程中的,因此关闭该页面,内存中的资源也被释放掉了,磁盘中的资源是永久性的,因此还存在。
根据三级缓存原理,若是在内存中没找到资源,便会去磁盘中寻找!
from disk cache !!! ok,以上也就验证了三级缓存原理,相信你对缓存资源的存储也有了更深的理解了。
咱们刚对资源设置的有效期是300秒,咱们接下来来验证缓存是否失效。
300秒后。。。
咱们经过返回值能够看到,缓存失效了。
经过以上实践,你是否对强缓存有了更深刻的理解了呢?
Ok!!!接下来咱们来实践下协商缓存。~
因为Cache-Control的默认值就是no-cache(须要进行协商缓存,发送请求到服务器确认是否使用缓存。),因此咱们这里不用对Cache-Control进行设置!
//ETag support for Koa responses using etag. npm install koa-tag -D // etag works together with conditional-get npm install koa-conditional-get -D
咱们这里直接使用现成的插件帮咱们计算文件的ETag值,站在巨人的肩膀上!
webcache.js
import Koa from 'koa'; import path from 'path'; //静态资源中间件 import resource from 'koa-static'; import conditional from 'koa-conditional-get'; import etag from 'koa-etag'; const app = new Koa(); const host = 'localhost'; const port = 4396; // etag works together with conditional-get app.use(conditional()); app.use(etag()); app.use(resource(path.join(__dirname, './static'))); app.listen(port, () => { console.log(`server is listen in ${host}:${port}`); });
ok。第一次请求.
咱们发现返回值里面已经有了Etag值。
接下来再请求的时候,浏览器将会带上If-None-Match请求头,并赋值为上一次返回头的Etag值,而后与 此次返回值的Etag值进行对比。若是一致则命中协商缓存。返回304 Not Modified。接下来咱们来验证一下~
ok,如图所示,完美验证了上面的说法。
接下来咱们修改web.png ,来验证是否资源改变时 协商缓存策略也就失效呢?
如图所示.协商缓存的实践也验证了原理。
写文章真的是件挺累的事,若是以为有帮助到你,请给star/follow 支持下做者~