tomcat文件緩存分析[一個bug]

=====================================
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;
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章