缓存(二)

系统 1650 0

  在我们打开浏览器 , 决定浏览某个网页之前 ( 指人眼看到屏幕上的内容之前 ), 一般来说浏览器有几个事情要做 , 首先根据 url 请求服务器端的 html html 显示到屏幕上等等 . à 下载 css, js,-------- à , 然后解析 html,------ à 数据 ------ 接着大脑才能感受到 . à 然后眼睛才能感受到 ,-------- à ---------

在这个流程中 , 那么怎么才能让大脑尽可能快的接受到这个信息呢 , 我想最快的方式是在大脑里放一份该屏幕的拷贝 , 下次想看这份内容的时候直接拿出大 脑的拷贝就可以了 . 如果大脑容量有限 , 那我们可以考虑把这份拷贝放到眼睛里 , 如果眼睛也放不下 , 那我们可以考虑把这份拷贝放到浏览器里 , 从这个逻辑上看 , 越靠近大脑的数据越能快速的被我们接受到 .

那么本文的目的其实就是为了研究如何使用大脑和眼睛来缓存数据 ------------------------ 吃惊吧 ,ahuaxuan 瞎扯的 , 回到正题 , 上面这段调侃不是为了说明别的 , 而是为了说明越靠近用户的数据被用户感受到的速度就越快 . 也就是近与快的关系
.

接着再让我们抛开缓存先不说 , 来说说 CDN 和镜像的问题 ,CDN 的英文名字叫 CDN, 中文名字一般还是 CDN( 请换个调朗诵 ). 呵呵 ,CDN 中文名字是内 容分布网络 , 简单来说就是把内容分布出去 , 比如放到全国几个地方 , 举例来说做一个图片服务 , 上海的用户请求某个图片服务器 , 那么只需要返回某个离上海最近 CDN 节点上的图片 , 而不需要路由到北京或者云南的节点上去取数据 , 您要问为啥呢 , 因为快啊 , 上海的用户访问北京节点的数据显然在路由层次上 , 网络时间 消耗上都要多出很多 , 这说明啥呀 , 还是那个理儿 : 近就会快啊


一般来说 CDN 都是放一些图片 , 视频 , 文档之类的数据 , 那么元数据呢 , 放一块儿 , 当然也不是 , 这时候可以用镜像来解决元数据的问题 , 于是变成了上海的用户访问上海的镜像 , 北京的用户访问北京的镜像 . 这还不是就地取材比较方便嘛
.

, 说到这里 , 想必大家对近和快的关系有了一定的认识了 , 下面我们来看看如何把这种原理或者规则运用到缓存中去
.

下面让 ahuaxuan 和大家先调查一下离眼睛最近的是什么 , 显示器 ( 别跟我说是屏幕保护膜和键盘哈 , 鼠标也不行 ), 不过这些是硬件呀 , 那软的 , 非浏览器莫数了 . 也就是说如果我们把一些可以缓存在浏览器上的数据缓存到浏览器上 , 那就能加快我们的页面响应速度了 . 也就是说我们现在找到一个地方 , 也许可以放一点可以缓存的数据
.

下面我们要考察考察什么样的数据可以缓存在浏览器上 , 以及缓存在浏览器上的一些优缺点或者限制因素

什么样的数据可以缓存在浏览器上
?
浏览器上无法就几种数据 ,html,css,js,image, . 那么接着我们来看看他们的变化特性
,
html
数据很多情况下是动态的 , 但是也有很多情况下是某个时间段内可以是静态的
.
Css
一般是静态的

Js
一般也是静态的

Image
一般也是静态的
.

, 看上去后几者基本都可以缓存在浏览器 , html 是否缓存要看 html 中数据的特性了 . 那么问题来了 , 浏览器是依据什么设置来缓存 html, 或者 css, 或者 js 的呢 . 答曰 ,expires 或者 max-age.
Expires
代表该份数据缓存到浏览器上 , 直到某个时间点后过期 , max-age 表示缓存在浏览器上直到某个时间段之后过期
.

对于静态内容:设置文件头过期时间 Expires 的值为 “Never expire” (永不过期)

动态页面 , 在代码中添加 cache-control, 表示多少时间之后过期 ,
:
response.setHeader("Cache-Control", "max-age=3600");
表示 1 个小时后过期 , 即在浏览器上缓存一个小时
.

但是这样问题又来了 , 如果设置 10 天后过期 , 那我明天就要改变 ,css,js 都变了 , 咋办呐 , 答曰 , 加版本号吧 , 这样浏览器就会重新加载新的 css js
.
但是如果是动态数据 , 那就没有折了 , 所以动态数据的 max-age 一般不推荐太大 , 否则啊 , 您呐 , 就得挨个通知您得用户按一下 Ctril+F5
.

一般来说静态数据需要缓存 , 我们一般通过 webserver( apache,lighttpd 之流 ), 只需要配置一下即可 , 而动态数据是比较重 要的 , 因为其改变的周期段 , 而且只能由 servlet 容器或者 fastcgi 之类的服务器返回 , 所以其应付大量并发的能力是有限的 . 那么这里理论上可能有 一个瓶颈 , 就是如果访问的独立用户较多 , 那么这份动态数据还是会被请求 1* 用户数 = n , 那么我们可以想象 , 一样的请求对于我们的 servlet 容器或者 fastcgi 来说其实是多余的 , 我们可以想一个方法 , 把这些一样的请求挡在 servlet 容器或者 fastcgi 进程之前
.

正如在第一说中说到的 , 在浏览器和 servlet 容器或者 fastcgi 进程之间 , 还有很大的空间可以发挥 , 在这一部分的缓存 ,ahuaxuan 称之为
webcache.

目前在 webcache , 最流行的估计就属 squid , 然后还有 varnish 等等 . 为了有一个比较直观的感受 , 我们来看看下面这张图呗
:

缓存(二)
 
从这张图上 , 我们可以看出 , 浏览器 1 在请求了一份数据之后 , 其实这份数据已经在 webcache 上了 , 浏览器再来请求 2 的时候 , 请求到了 webcache 这层就返回了 , 这样就降低了 servlet container 的压力了 . 虽然说我们在 servlet 容器上也是可以建 page cache, 但是毕竟 servlet 本身的并发能力有限 .( 如何在 servlet container 上使用 page cache : http://www.iteye.com/topic/128458 )

而且更重要的是一般 webcache 的并发能力要比 servlet container 或者 fastcgi process 要高出很多 ( 没办法 , 谁叫它是专业的 cache ). 所以使用 webcache 也能够提供更高访问量的服务 . 一举多得 , 何乐而不为呢 . 但是声明一下 , 您呐 , 别以为上 面这种方式是标准方式 , 我们还有 webserver, 负载均衡器等等 , 上图只是为了便于说明本文的论点 , 而且互连网需求和解决方案层出不穷 , 切不可以胡搬 乱套 , 还是要分析分析再分析 , 思考 , 思科再思考 .

说到这里即使以前没有接触过得筒子大概也明白了 web cache 得作用了 . 下面我们再来看看如何使用 web cache , 呵呵 , 其实和浏览器上缓存数据得方式一样 . 也是通过在 response header 中指定 expires 或者 max-age 来实现的 .( 但是据 ahuaxuan 观察在使用 squid 的时候有一个要求 , 浏览器的请求必须满足 http 的语义 , 也就是说只有 method=get 的时候 web cache 才能缓存数据 , 如果是 post, 那么 web cache 认为这个是一个创建数据的请求 , 并不会缓存其返回结果
.)

Squid,
如果您要系统的学习 squid, 请看
:
http://www.squid-cache.org/       http://blog.s135.com/book/squid/

补充 , 在有些情况下 ,web cache 中的数据很有可能是有状态的 . 比如根据浏览器的 locale 返回不同的数据 , 那么虽然访问的 url 是一样的 , 但是返回的值却是不一样的 , 咋办 , 别担心 , 我们有 vary, 只要在 response 里指定 vary 参数为 accept-language ok. 您也可以指定为 cookie 中的值 , 就完全看您的需要了 . 如果您还是不明白 vary 的作用 , 请看 : http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44

总结 :
说到这里 , 关于近和快的话题也基本可以结束了 ( 这个话题再写下去就变成裹脚布了 ). 所以一般情况下 , 我们可以认为有如下事实 :” == ”, 但是 近和快并不只是表现在人的体验上 , 如果换个角度 , 速度的感受者不是人 , 而是机器 , 那么我们也可以这么认为 local cache remote cache 更靠近 cpu, 所以 local cache 的速度更快 ( 当然他们的功能不是重叠的 , 各自适用的场景不一样而已 , 而且很多情况下他们可以配合使用 , 在后续的文章中将会讨论这个问题

).

如何在只使用 tomcat 的情况下 , 自动缓存 js css 或者 image 等文件 .
第一步 : 写一个 filter, 可以根据路径的正则来判断该路径的请求是否需要设置 max-age:

    public class CacheFilter implements Filter{  
  
    private static transient Log logger = LogFactory.getLog(CacheFilter.class);  
      
    private Integer cacheTime = 3600 * 24;  
    private List<Pattern> patternList = new ArrayList<Pattern>();  
      
    private static ResourceBundle rb = ResourceBundle.getBundle("cache-pattern");  
    public void destroy() {  
          
    }  
  
    public void doFilter(ServletRequest rq, ServletResponse rqs,  
            FilterChain fc) throws IOException, ServletException {  
          
        fc.doFilter(rq, rqs);  
        if (rq instanceof HttpServletRequest && rqs instanceof HttpServletResponse) {  
            HttpServletRequest request = (HttpServletRequest) rq;  
            HttpServletResponse response = (HttpServletResponse) rqs;  
              
            if (matchPattern(request.getRequestURI())) {  
                response.setHeader("Cache-Control", "max-age=" + cacheTime);  
                if (logger.isDebugEnabled()) {  
                    StringBuilder sb = new StringBuilder();  
                    sb.append(" set cache control for uri = ").append(request.getRequestURI());  
                    sb.append(" and the cache time is ").append(cacheTime).append(" second");  
                    logger.debug(sb.toString());  
                }  
            }  
          
        } else {  
            if (logger.isWarnEnabled()) {  
                logger.warn("---- the request instance is not instanceof HttpServletRequest ---");  
                logger.warn("---- the response instance is not instanceof HttpServletResponse ---");  
            }  
        }  
          
    }  
  
    public void init(FilterConfig arg0) throws ServletException {  
        Enumeration<String> keys = rb.getKeys();  
        while (keys.hasMoreElements()) {  
            String p = keys.nextElement();  
            String value = rb.getString(p);  
            patternList.add(Pattern.compile(value, Pattern.CASE_INSENSITIVE));  
              
            if (logger.isInfoEnabled()) {  
                logger.info(">>>>>>>>>>> init the cache pattern " + value);  
            }  
        }  
          
        if (arg0 != null) {  
            String ct = arg0.getInitParameter("cache-time");  
            if (!"".equals(ct) && null != ct) {  
                cacheTime = new Integer(ct);  
                if (logger.isInfoEnabled()) {  
                    logger.info(">>>>>>>>>> the cache time is " + cacheTime);  
                }  
            }  
        }  
    }  
      
    private boolean matchPattern(String url) {  
        for (Pattern pattern : patternList) {  
            if (pattern.matcher(url).matches()) {  
                return true;  
            }  
        }  
          
        return false;  
    }  
  
    public static void main(String [] args) throws ServletException {  
        CacheFilter cf = new CacheFilter();  
        cf.init(null);  
        System.out.println(cf.matchPattern("/css/prototype.CSS"));  
    }  
}  

  

 

第二步 : classpath 路径下创建一个 cache-pattern.properties 文件 , 内容如下 :

Java 代码

1 = .*ext-all.js  

2 = .*prototype.js  

3 = .*/css/.*\\.css  

1 = .*ext-all.js

2 = .*prototype.js

3 = .*/css/.*\\.css


在这个配置文件中 , 您可以根据 js css 的路径来配置哪些目录 , 或者哪些文件需要设置 max-age.

第三步 :
web.xml 添加如下内容 :

Java 代码

<filter>  

         <filter-name>cache-filter</filter-name>  

         <filter-class>com.filter.CacheFilter</filter-class>  

         <init-param>  

            <param-name>cache-time</param-name>  

            <param-value>86000</param-value>  

        </init-param>  

    </filter>  

  

<filter-mapping>  

        <filter-name>cache-filter</filter-name>  

        <url-pattern>*.js</url-pattern>  

    </filter-mapping>  

      

    <filter-mapping>  

        <filter-name>cache-filter</filter-name>  

        <url-pattern>*.css</url-pattern>  

</filter-mapping>  

<filter>

          <filter-name>cache-filter</filter-name>

          <filter-class>com.filter.CacheFilter</filter-class>

          <init-param>

            <param-name>cache-time</param-name>

            <param-value>86000</param-value>

        </init-param>

    </filter>

 

<filter-mapping>

        <filter-name>cache-filter</filter-name>

        <url-pattern>*.js</url-pattern>

    </filter-mapping>

   

    <filter-mapping>

        <filter-name>cache-filter</filter-name>

        <url-pattern>*.css</url-pattern>

</filter-mapping>



如此 3 , 就可以将 js css 文件缓存于无形 . 快哉 .

仓卒之间成文 , 再加上 ahuaxuan 水平有限 , 本文如有纰漏之处 , 还望各位看官您不吝指正 , 先谢过了 .

 

 

 

 

 

 

 

 

 

缓存(二)


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论