My Little World

关于缓存

缓存的好处

利用缓存,可减少对源服务器的访问,因此也就节省了通信流量和通信时间
从而减少了延迟(加快页面打开速度),降低网络负载,保证稳定性(服务能正常使用)

浏览器本地存储与服务器端存储之间的区别

其实数据既可以在浏览器本地存储,也可以在服务器端存储。

浏览器端可以保存一些数据,需要的时候直接从本地获取,sessionStorage、localStorage和cookie都由浏览器存储在本地的数据。

服务器端也可以保存所有用户的所有数据,但需要的时候浏览器要向服务器请求数据。
1.服务器端可以保存用户的持久数据,如数据库和云存储将用户的大量数据保存在服务器端。
2.服务器端也可以保存用户的临时会话数据。服务器端的session机制,如jsp的 session 对象,数据保存在服务器上。实现上,服务器和浏览器之间仅需传递session id即可,服务器根据session id找到对应用户的session对象。会话数据仅在一段时间内有效,这个时间就是server端设置的session有效期。

服务器端保存所有的用户的数据,所以服务器端的开销较大,而浏览器端保存则把不同用户需要的数据分布保存在用户各自的浏览器中。
浏览器端一般只用来存储小数据,而服务器可以存储大数据或小数据。
服务器存储数据安全一些,浏览器只适合存储一般数据。

http协议里面的缓存机制

相关首部字段

Expires:资源失效日期
收到该字段后会缓存资源,再将缓存的资源给客户端,对于以后的请求,在该时间之前,响应缓存资源,当超过该时间后,缓存服务器在请求发来时,会向服务器请求资源
漏洞:用户时间和服务器时间不一致的话,会造成用户资源更新不及时

cache-control:通过配置参数,设置缓存机制,相关参数如下:
public:允许多用户共享缓存
private:只允许特定用户使用缓存
no-cache:在请求中配置,告诉服务器,我不要缓存的响应,中间服务器必须从源服务器取资源;
在响应中配置,告诉中间服务器不能对响应进行缓存,源服务器以后对中间服务器提出的有效性校验不在进行确认
no-store:请求/响应中含有机密信息,不允许缓存资源
max-age:缓存保存时长
缓存保存到现在的时长小于该值,就可以返回缓存,否则像源服务器请求资源

last-modified:资源最终修改时间
服务器会比较请求的该值与资源最终修改时间,不一致的话就返回新资源,一致的话,就返回304,not modified
漏洞:时间精确度到秒,可能存在同一秒保存两次文件,无法区分,
也可能因为通过cdn,造成服务器上文件修改时间并不一致

E-Tag:服务器为每份资源提供的唯一性标识,相当于文件MD5值
服务器会跟剧请求的if-no-match与资源ttag比较,不一致返回新资源,一致返回304

服务器处理机制:
当首部字段Cache-Control有指定max-age指令,Expires也存在时,会优先处理max-age,用于判断资源是否失效
当判断max-age失效后,会去判断last-modified和E-Tag,如果服务器仅支持http1.0,则仅根据last-modified做校验
如果服务器支持http1.1,则先根据E-Tag做校验,然后在根据last-modified做校验
相关链接

资源缓存

HTML等页面文件:短效缓存,本地服务器
更新策略:使用no-cache

css、js:长效缓存,CDN
更新策略:在URI上添加更新标识(md5,timestamp,version)
MD5:不同文件MD5值如果相同,有覆盖危险
timestamp:文件修改/上线时间,因为可能存在不正确性,所以需要其他操作判断文件是否有变化
version:每次都需要更新版本号

Image:长效缓存,CDN
更新策略:使用随机名字

数据缓存

在web storage出现前,缓存都放在cookie里面,但存储数量有限,而且每次请求都会携带一堆数据,就会占用宽带
工作原理
当浏览器访问服务器时,服务器可以将少量数据以set-cookie消息头的方式发送给浏览器,浏览器会将这些数据临时保存下来。
当浏览器再次访问服务器,会将之前保存的这些数据以cookie消息头的方式发送给服务器。
cookie的限制
a. cookie可以被用户禁止。
b. cookie不安全(对于敏感数据,需要加密)。
c. cookie只能保存少量的数据(大约是4k)。
d. cookie的数量也有限制(大约是几百个)。
e.cookie只能保存字符串
生存时间问题
缺省情况下,浏览器会把cookie保存在内存里面,只要浏览器不关闭,cookie就会一直存在,浏览器关闭,cookie就会被销毁。
相关链接
相关链接

localStorage

大小有限制,数据不安全/不可信(有xss风险,存爆,读取不成功,被损坏),不应当存放重要数据,并且要做好容错
可以用于跨页面通讯
永久性的本地存储
将数据保存在客户端硬件设备上,不管它是什么,意思就是下次打开计算机时候数据还在
localStorage提供了四个方法来进行对本地存储做相关操作。
(1)setItem(key,value):添加本地存储数据。
(2)getItem(key):通过key获取相应的Value。
(3)removeItem(key):通过key删除本地数据。
(4)clear():清空数据。

SessionStorage

类似localStorage,但在窗口关闭后不存在
会话级别的本地存储
通过此对象可以直接操作存储在浏览器中的会话级别的WebStorage。
存储在sessionStorage中的数据首先是Key-Value形式的
另外就是它跟浏览器当前会话相关,当会话结束后,数据会自动清除,跟未设置过期时间的Cookie类似。即窗口关闭,数据清除
sessionStorage提供了四个方法来进行对本地存储做相关操作。
(1)setItem(key,value):添加本地存储数据。
(2)getItem(key):通过key获取相应的Value。
(3)removeItem(key):通过key删除本地数据。
(4)clear():清空数据。
注意区别session机制
session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。
当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识(称为session id),如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(检索不到,会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应
中随cookie返回给客户端保存。

Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中
但cookie可以被人为的禁止,则必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。

经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面。还有一种技术叫做表单隐藏字段。就是服务器
会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器

共同点:都是保存在浏览器端,且同源的。
区别:
1.cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。而sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下。
2.存储大小限制也不同,cookie数据不能超过4k,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
3.数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。
4.作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。
5.Web Storage 支持事件通知机制,可以将数据更新的通知发送给监听者。
6.Web Storage 的 api 接口使用更方便。
Cache-control参数解析

web SQL

在浏览器端创建的一个本地的数据库,而且支持标准的SQL的CRUD操作,让离线的Web应用更加方便的存储结构化的数据
Web SQL Database 规范中定义的三个核心方法:
openDatabase:这个方法使用现有数据库或新建数据库来创建数据库对象
transaction:这个方法允许我们根据情况控制事务提交或回滚
executeSql:这个方法用于执行SQL 查询,用SQL语句做查询索引工作
使用链接
实例参考
实际上已经被废弃

indexDB

适用持续化的数据,不会被清除的数据
也相当于在本地创建数据库,通过调用API接口进行数据操作,
在IndexedDB大部分操作并不是常用的调用方法,返回结果的模式,而是请求——响应的模式,比如打开数据库的操作
不会返回一个DB对象的句柄,得到的是一个IDBOpenDBRequest对象,而希望得到的DB对象在其result属性中
对数据的所有操作,增删改查,均通过调用api函数实现
MDN接口文档
使用教程1
使用教程2
持续化存储,可在 web worker中使用

离线缓存

离线缓存之App Cache

AppCache就是对app内存缓存的方案,具体表现为当请求某个文件时不是从网络获取该文件,而是从本地获取。
应用程序缓存为应用带来三个优势:
离线浏览 - 用户可在应用离线时使用它们
速度 - 已缓存资源加载得更快
减少服务器负载 - 浏览器将只从服务器下载更新过或更改过的资源

缺点:
可能会缓存错误页面,甚至缓存运营商的劫持广告
更新中有一个文件失败了,就会全部退回上一个版本
由于是Lazy更新,如果后台接口有break change将会是大麻烦
manifest文件也有可能不小心被缓存了

manifest 文件是简单的文本文件,它告知浏览器被缓存的内容(以及不缓存的内容)。
manifest 文件可分为三个部分:
CACHE MANIFEST - 在此标题下列出的文件将在首次下载后进行缓存,等价于CACHE:
NETWORK - 在此标题下列出的文件需要与服务器的连接,且不会被缓存
FALLBACK - 在此标题下列出的文件规定当页面无法访问时的回退页面(比如 404 页面
机制分析

离线缓存之service worker

service worker 相当于在浏览器的请求层建了一个服务器,所有的请求都会进入service worker进行处理,决定资源使用缓存还是从新获取服务器资源
service worker可以:
后台消息传递
网络代理,转发请求,伪造响应
离线缓存
消息推送

使用时必须使用HTTPS请求协议

在 installing 状态中,Service Worker 脚本中的 install 事件被执行。通常在安装事件中,为 document 缓存静态文件
处于 activating 状态期间,Service Worker 脚本中的 activate 事件被执行。通常在 activate 事件中,清理 cache 中的文件
如果 Service Worker 处于激活态,就可以监听事件性事件 —— fetch 和 message。
service worker可以通过fetch事件拦截请求,并且给出自己的响应。
页面和serviceWorker之间可以通过posetMessage()方法发送消息,发送的消息可以通过message事件接收到。

w3c提供了一个新的fetch api,用于取代XMLHttpRequest,与XMLHttpRequest最大不同有两点:

  1. fetch()方法返回的是Promise对象,通过then方法进行连续调用,减少嵌套。ES6的Promise在成为标准之后,会越来越方便开发人员。
  2. 提供了Request、Response对象,如果做过后端开发,对Request、Response应该比较熟悉。前端要发起请求可以通过url发起,也可以使用Request对象发起,而且Request可以复用。但是Response用在哪里呢?在service worker出现之前,前端确实不会自己给自己发消息,但是有了service worker,就可以在拦截请求之后根据需要发回自己的响应,对页面而言,这个普通的请求结果并没有区别,这是Response的一处应用。

1.注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//在页面文件注册
if (navigator.serviceWorker) {
// 注册Service Worker scope表示作用的页面的path
// register函数返回Promise
navigator.serviceWorker.register('./service-worker.js', {scope: './'})
.then(function (registration) {
console.log(registration);
})
.catch(function (e) {
console.error(e);
})
} else {
console.log('Service Worker is not supported in this browser.')
}

上面的代码检查 service worker API 是否可用,如果可用, /service-worker.js 这个文件将会作为 service worker 被注册。
scope表示作用的页面的path

2.安装使用
service-worker.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
importScripts('js/cache-polyfill.js'); // cache 扩展

var CACHE_VERSION = 'app-v1'; // 缓存文件的版本
var CACHE_FILES = [ // 需要缓存的页面文件
'/',
'images/background.jpeg',
'js/app.js',
'css/styles.css'
];


self.addEventListener('install', function (event) { // 监听worker的install事件
event.waitUntil( // 延迟install事件直到缓存初始化完成
caches.open(CACHE_VERSION) //开启一个缓存
.then(function (cache) {
console.log('Opened cache');
return cache.addAll(CACHE_FILES); //缓存文件
//这个过程是通过一连串 promise (caches.open 和 cache.addAll)完成的。
//event.waitUntil 会拿到一个 promise ,并使用其来获取安装耗费的时间以及是否安装成功。
//如果所有的文件都缓存成功,service worker 就安装成功了。
//如果任何一个文件下载失败,那么安装步骤就会失败。这个方式依赖于自己指定的资源,但这意味着,需要非常仔细地确定哪些文件需要被缓存。
//指定了太多文件的话,会增加失败率
//可以在 install 事件中执行其他操作,甚至忽略 install 事件
})
);
});

self.addEventListener('activate', function (event) { // 监听worker的activate事件,正在激活状态会触发该事件,更新service worker时,也会触发
event.waitUntil( // 延迟activate事件直到
caches.keys().then(function(keys){
return Promise.all(keys.map(function(key, i){ // 清除旧版本缓存
if(key !== CACHE_VERSION){
return caches.delete(keys[i]);
//需要在 activate 的 callback 中进行 cache 管理,来清理老的 cache。
//在 activate 而不是 install 的时候进行的原因,是如果在 install 的时候进行清理,
//那么老的 service worker 仍然在控制页面,他们依赖的缓存就失效了,因此就会突然被停止
}
}))
})
)
});



self.addEventListener('fetch', function (event) { // 截取页面的资源请求
event.respondWith( // 返回页面的资源请求
caches.match(event.request).then(function(res){ // 判断缓存是否命中
if(res){ // 返回缓存中的资源
return res;
}
requestBackend(event); // 执行请求备份操作
})
)
});

function requestBackend(event){ // 请求备份操作
var url = event.request.clone();
return fetch(url).then(function(res){ // 请求线上资源
//if not a valid response send the error
//确保 response 有效
//检查 response 的状态是200
//确保 response 的类型是 basic 类型的,这说明请求是同源的,这意味着第三方的请求不能被缓存。
if(!res || res.status !== 200 || res.type !== 'basic'){
return res;
}

var response = res.clone();
//如果检查通过会clone 这个请求。
//这么做的原因是如果 response 是一个 Stream,那么它的 body 只能被消费一次。
//所以为了让浏览器跟缓存都使用这个body,必须克隆这个 body,一份到浏览器,一份到缓存中缓存

caches.open(CACHE_VERSION).then(function(cache){ // 缓存从线上获取的资源
cache.put(event.request, response);
});

return res;
})
}

service worker 更新步骤:
1.更新 service worker 的 JavaScript 文件
当用户浏览你的网站时,浏览器尝试在后台重新下载 service worker 的脚本文件。
经过对比,只要服务器上的文件和本地文件有一个字节不同,这个文件就认为是新的。
2.之后更新后的 service worker 启动并触发 install 事件。
3.此时,当前页面生效的依然是老版本的 service worker,新的 service worker 会进入 “waiting” 状态。
4.当页面关闭之后,老的 service worker 会被干掉,新的 servicer worker 接管页面
5.一旦新的 service worker 生效后会触发 activate 事件。

关于 service worker 的一些注意点:
1.service worker 是一个JavaScript worker ,所以它不能直接访问 DOM 。相反, service worker 可以通过postMessage 接口与跟其相关的页面进行通信,发送消息,从而让这些页面在有需要的时候去操纵 DOM 。
2.Service worker 是一个可编程的网络代理,允许你去控制如何处理页面的网络请求。
3.Service worker 在不使用时将被终止,并会在需要的时候重新启动,因此你不能把onfetch 和 onmessage事件来作为全局依赖处理程序。如果你需要持久话一些信息并在重新启动Service worker后使用他,可以使用 IndexedDBAPI ,service worker 支持。   
4.Service worker 广泛使用了 promise
缺点:
如果安装失败了,没有一个很好的方式来知晓
fetch api()目前仅支持Service Workers中使用
fetch() 中不会被触发重定向
页面在改变 hash 时,service worker 会停止工作

Service Worker 生命周期
Service Worker那些事
深入了解 Service Worker
Service Worker初体验
serviceworke Introduction

离线缓存之 Hybrid Cache离线包

前端开发”就是使用 HTML、CSS、JS 技术给一个网站或 Web 应用开发图形用户界面
所以,前端应用本质上一个 GUI 程序,而 GUI 程序有三种典型形态:
Web : 以浏览器为运行环境,基于浏览器内核支持的编程语言、API 来实现,被浏览器解释执行
Native : 以操作系统为运行环境,基于操作系统原生支持的编程语言、API 接口实现,以二进制包的形式运行
Hybird : 基于 Native 应用提供的一个支持 HTML、CSS、JS 的容器开发的应用,相当与用开发 Web 的方式开发 Native 应用

原理:
1.o2o 在线资源抓取程序:基于 phantomjs 解析资源
2.grunt-inc-offline 增量包计算器:基于 git-diff 的增量包运算
3.离线包生成器
CACHE1
1.o2o 定时程序监听线上页面变更,将其所携带的资源(HTML、CSS、JS 和部分图片)抓取下来
2.增量包计算器会计算好与之前若干版本之间的增量文件,配合包生成器将增量包逐一构建打包,同时生成好每个增量包的 Diff Json
3.调用 Clam 命令通过 Gitlab 将资源包部署至 CDN,以备手机端更新。
4.Gitlab 仓库 的更新会触发一个 Hook 脚本,调用 tSync 服务器的接口,来通知资源变更
5.tSync 服务器沙箱完成消息封装,包括了第二步生成了的 Diff Json 文本
6.tSync 长连接将消息指令下发给手机终端
7.手机终端拼好资源文件链接,从 CDN 将增量包更新下来,随后执行 Diff Json中的指令,完成包的更新。

手机端的两个重要进程:

1.资源预加载进程:在实际访问页面之前,将资源预加载到缓存池并更新 Cache Map
2.创建 WebView 进程:只聚焦本地资源读写,别的什么也不干
手机端 touch 到网络的环节收敛到了两处,第一,Package Update Controller,第二,WebView 本身必要的网络请求
CACHE2
相关链接