tomcat文件缓存分析[一个bug]

本文深入探讨了Apache Tomcat 5.5.26版本中的文件缓存机制,特别是针对`ServletContext.getResourceAsStream`方法的工作原理。分析了CacheEntry对象的创建与验证过程,指出在缓存文件属性时未立即获取文件最后修改时间的问题,及其导致的缓存更新错误。

=====================================
apache tomcat中文件的缓存机制
=====================================
一. 环境
 version: apache tomcat 5.5.26
 
二. 缓存机制[ServletContext.getResourceAsStream]
 1. 根据给出的path到缓存中查询CacheEntry; 以下是从ProxyDirContext类中摘出来的方法
  protected CacheEntry cacheLookup(String name) {
   if (cache == null)
    return (null);
   if (name == null)
    name = "";
   for (int i = 0; i < nonCacheable.length; i++) {
    if (name.startsWith(nonCacheable[i])) {
     return (null);
    }
   }
   CacheEntry cacheEntry = cache.lookup(name);
   if (cacheEntry == null) {
    cacheEntry = new CacheEntry();
    cacheEntry.name = name;
    // Load entry
    cacheLoad(cacheEntry);
    //注释:要特别注意cacheLoad这个方法, 它缓存文件最后修改时间时,并没有取真正的时间,仅仅缓存了
    //文件属性对象[这时候的修改时间为-1, 还没有, 只有等到下次对比的时候才取]
   } else {
    if (!validate(cacheEntry)) {
     if (!revalidate(cacheEntry)) {
      //注释: revalidate这个方法要注意,它是用文件的大小和最后修改时间来比较的
      
      cacheUnload(cacheEntry.name);
      return (null);
      //注释: 它清除缓存之后,并没有马上将新的内容加载到缓存中.
     } else {
      cacheEntry.timestamp =
       System.currentTimeMillis() + cacheTTL;
     }
    }
    cacheEntry.accessCount++;
   }
   return (cacheEntry);
  } 
  
  protected void cacheLoad(CacheEntry entry) {

   String name = entry.name;

   // Retrieve missing info
   boolean exists = true;

   //注释:出问题的点就在下面,它只将文件的属性对象缓存了,
   //并没有取文件的最后修改时间[它的初始化值是-1, 只有调用
   //之后,才会取得真正的最后修改时间].
   // Retrieving attributes
   if (entry.attributes == null) {
    try {
     Attributes attributes = dirContext.getAttributes(entry.name);
     if (!(attributes instanceof ResourceAttributes)) {
      entry.attributes =
       new ResourceAttributes(attributes);
     } else {
      entry.attributes = (ResourceAttributes) attributes;
     }
    } catch (NamingException e) {
     exists = false;
    }
   }

   ....
  }
  
 2. 如果找到CacheEntry, 则将CacheEntry.resource返回;
如果没有找到,则到文件系统加载指定的文件;

 从以上的分析来看,它的缓存有以下几个特点:
 > 从缓存中获取的时候,它是根据path获取一个CacheEntry对象,如果有,还需要验证这个对象是否有效,如果无效,就直接删除;
 > 删除缓存的内容时,并不马上将新的内容添加到缓存中,而只是简单的将缓存清空而已;
 > 建立缓存的时候,它将缓存对象[文件或资源]的属性也缓存了[但当时并没有取出最后修改时间]
 > 判断一个缓存对象是否有效的依据是:文件大小和文件的最后修改时间.
 
 由于tomcat在生成缓存对象的时候,只是将缓存对象的属性对象缓存起来,并没有获取"最后修改时间属性", 所以导致下一次对比缓存的
时候,缓存中的"最后修改时间属性"就是当前文件的最后修改时间.
 例如:
 [原始文件, 最后修改时间: 001]
 第一次访问: 缓存不存在, 生成缓存, 并生成缓存属性[缓存属性中的最后修改时间: -1]
 [第一次修改文件, 最后修改时间: 002]
 第二次访问: 缓存存在, 取得缓存对象, 对比时间和大小, 由于缓存属性的最后修改时间为-1, 所以取当前文件的最后修改时间
假设文件大小没有改变,所以就直接使用缓存中的内容[缓存属性中的最后修改时间: 002]
 [第二次修改文件, 最后修改时间: 003]
 第三次访问: 缓存存在,从缓存中取, 由于缓存中的最后修改时间与当前的最后修改时间不一样,所以清缓存;
 [第三次修改文件, 最后修改时间:004]
 第四次访问: 缓存不存在, 重复第一次访问的逻辑
 
 因此, 按以上逻辑, 第2, 5, 8, 11...(3*n-1, n为自然数)次访问的时候会出问题,没有取到最新的内容,而是上一次的缓存结果.
 这个问题的根据原因是:对时间属性的缓存,并没有缓存真正的时间(不知道tomcat出于什么原因,为什么缓存的时候不将最后修改时间
取出来?)
 
三. 说明
 1. CacheEntry上的关键属性:
  > resource或context: 表示缓存的内容
  > attributes: 表示缓存文件的一些属性;
  > name: 缓存文件的path;
 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值