java缓存技术二 -- 近与快

<from>http://hi.baidu.com/anckbx/blog/item/84b22439a2c5a72897ddd835.html

 

近水楼台先得月,向阳花木易为春--------苏麟

缓存的作用在第一论http://hi.baidu.com/%CB%BC%C3%F4%D3%

EA/blog/item/908d0cdecbc8a71b495403cc.html中已有部分阐述,下面ahuaxuan和大家一起来学习一下

缓存得另外一个重要的规则,近和快.

在我们打开浏览器,决定浏览某个网页之前(指人眼看到屏幕上的内容之前),一般来说浏览器有几个事情

要做,首先根据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/

varnish如果您想了解varnish,请看附件

补充,在有些情况下,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的速度更快(当然他们的功能不是重叠的,各自适用的场景不一样而已,而且很多情况下他们可以配

合使用,在后续的文章中将会讨论这个问题).

还是那句话:本文是ahuaxuan从自己的实践中总结出来的一些小小的心得,未参考任何文章,所以可能未

必好,未必全面,未必令您满意,欢迎拍砖.


注:
按照看一送一的原则,本文还赠送以下内容:
如何在只使用tomcat的情况下,自动缓存js和css或者image等文件.
该方法分为以下3个步骤
第一步:写一个filter,可以根据路径的正则来判断该路径的请求是否需要设置max-age:
Java代码
/**
*
* @author ahuaxuan
* @date 2008-12-4
* @version $id$
*/  
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"));   
     }   
}  

/**
*
* @author ahuaxuan
* @date 2008-12-4
* @version $id$
*/
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.tudou.tudouadsales.component.web.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>  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值