首先我画了一个粗糙的图形,用来表示我们使用web请求所经历的过程。大家将就看下
从图可以看出,当用户发起一个网络请求。首先会经过浏览器的缓存,然后经过静态资源的CDN缓存,然后是Nginx等反向代理缓存,再是web缓存,最后还有数据库缓存。下面我们一一来看。
一.Browser Cache
http协议有自己的缓存协商机制。
对于http1.0 使用的是基于iso-time的 expired响应头去判断资源过期与否。
http1.1使用三种
1.是基于修改精确时间的Last-Modifie
2基于文件相对时间的max-age(这个资源你可以用多久)
from cache 是没有和服务器确认,一般是运维去除了e-tag或者说是使用了长缓存
另外一方面,可以把缓存分成三种策略,分别是存储策略,协商策略和过期策略。
last-modify,eTag属于协商策略,协商策略用于重新验证缓存资源是否有效, 需要发一次http请求,用于判断当前的缓存资源是否有效。 强缓存只有cache-control(http1.1) 和 pragma(http1.0)的 max-age等策略命中。 也就是说last-modify,eTag 这种协商缓存并不会减少http请求,而是减少了请求时间和字节数。
对于http1.0 的Expires 其实是过期策略,用于判断当前浏览器存储的缓存是否过期,Pragma指定缓存机制. http1.0 已经淘汰,仅供了解
cache-control 指定缓存机制,需要单独看待,他可以覆盖其他header的值,何解?
语法为:"Cache-Control : cache-directive".
Cache-directive共有如下12种(其中请求中指令7种,响应中指令9种):
Cache-directive | 描述 | 存储策略 | 过期策略 | 请求字段 | 响应字段 |
---|---|---|---|---|---|
public | 资源将被客户端和代理服务器缓存 | ✔️ | ✔️ | ||
private | 资源仅被客户端缓存,代理服务器不缓存 | ✔️ | ✔️ | ||
no-store | 请求和响应都不缓存 | ✔️ | ✔️ | ✔️ | |
no-cache | 相当于max-age:0,must-revalidate 即资源被缓存,但是缓存立刻过期,同时下次访问时强制验证资源有效性 |
✔️ | ✔️ | ✔️ | ✔️ |
max-age | 缓存资源,但是在指定时间(单位为秒)后缓存过期 | ✔️ | ✔️ | ✔️ | ✔️ |
s-maxage | 同上,依赖public设置,覆盖max-age,且只在代理服务器上有效. | ✔️ | ✔️ | ✔️ | |
max-stale | 指定时间内,即使缓存过时,资源依然有效 | ✔️ | ✔️ | ||
min-fresh | 缓存的资源至少要保持指定时间的新鲜期 | ✔️ | ✔️ | ||
must-revalidation/proxy-revalidation | 如果缓存失效,强制重新向服务器(或代理)发起验证(因为max-stale等字段可能改变缓存的失效时间) | ✔️ | ✔️ | ||
only-if-cached | 仅仅返回已经缓存的资源,不访问网络,若无缓存则返回504 | ✔️ | |||
no-transform | 强制要求代理服务器不要对资源进行转换,禁止代理服务器对Content-Encoding ,Content-Range ,Content-Type 字段的修改(因此代理的gzip压缩将不被允许) |
✔️ | ✔️ |
二.CDN Cache
CND是为了不同的网络环境都可以获得一致的高速体验而服务的。将一些静态资源放在cdn可以使用户访问这个静态资源的时候选择网络状态最好的。合理使用cdn可以大大提高访问速度。但是cdn也有很麻烦的地方,就是发布的时候,浏览器缓存可能读取的是旧版本的,这时候传统方法是使用query,但是query也有很多问题,文件发布顺序啥的会影响,而且需要手工加入版本号。 这里推荐webpack build 加hash。 我们目前是使用的webpack 插件 webpack-html-plugin
三.Revert Proxy Cache
这里说的是反向代理(我觉得叫服务端代理可能更容易理解)反向代理有保护服务器安全(将外部请求转发给内部服务器,同时将内部服务器响应转发给Client),缓存加速和实现负载均衡的作用。现在我们的站点测试环境使用的是Nginx1.8.x 。而生产环境使用的是淘宝Tengineer
四.web Server Cache
我们系统使用的tomcat服务器,所以在这里讲一下tomcat服务器的缓存策略。
tomcat默认是只对静态组员进行缓存对jsp是不缓存的。当前端发送一个请求时,服务端会根据这个资源的特征进行不同的缓存策略。 本质上是存到一个map中,将etag或者last-modified作为value(前面讲浏览器缓存有说过),将资源的url作为一个key存储,当资源发生变化的时候我们去更新这个map的value,当下次资源被请求会比较etag或者last-modified是不是最新的数据,如果是返回304.不是的话返回200和对应内容
tomcat源码中的!checkIfHeaders(request,response,cacheEntry.attributes)) 是整个机制的核心,用来判断资源是否需要重新获取,返回false就重新获取
protected boolean checkIfHeaders(HttpServletRequest request,
HttpServletResponse response,
ResourceAttributes resourceAttributes)
throws IOException {
return checkIfMatch(request,resourceAttributes)
&& checkIfModifiedSince(request,resourceAttributes)
&& checkIfNoneMatch(request,resourceAttributes)
&& checkIfUnmodifiedSince(request,resourceAttributes);
}
这里四个check都返回true(都认为资源被改变了),那么checkIfHeaders久返回true,对应就要重新获取而不走缓存
五.membercache,redis etc
在这里使用memcached大大缓解了数据库的读压力,当然对于写还是会造成负担的(更新数据, 需要同时更新数据库和缓存),好在大部分应用都是读远大于写,使用simple spring memcached大大简化了缓存配置,ssm在key生成规则和list缓存上做的很好。有时间我也会进一步了解下。
六.code cache
环境:reflux + react
使用react-router-loader实现按需打包加载运用在SPA一定程度解决了单页应用程序性能问题。使用后基本上每个页面初次访问都在200kb~300kb,而且静态资源可以得到缓存。但现在存在一个问题就是用户在多页面切换的时候回发送很多请求。以我的页面为例,每次要发送6个请求。可能就有600-700ms延迟。下图是切换三次页面的网络请求结果
在这里我打算做一次代码缓存,也就说只有第一次加载我从服务器拿,其他时候我是从code cache中拿。 只有用户强制刷新页面才去后台拿数据。
对于get操作
首先将cache挂载到store上,这里利用了Store单例的特性,即应用只有一份数据,这样不会出现脏数据的情况,然后get之前判断缓存中有没有,如果有直接返回,否则发送一个网络请求
如果请求成功,并且缓存不存在就添加缓存(第一次请求缓存是不存在的)。如果存在缓存不做处理。
对于update操作
直接在请求成功后的回调方法中更新对应缓存。
可以看下效果
看下优化后的结果:
只有第一次请求会加载6次以后都是不会请求的
参考资料
[浏览器缓存机制剖析](https://juejin.im/post/58eacff90ce4630058668257)
原文链接:https://www.f2er.com/react/307088.html