Guava學習筆記:Guava cache

Guava學習筆記:Guava cache

  緩存,在我們日常開發中是必不可少的一種解決性能問題的方法。簡單的說,cache 就是爲了提升系統性能而開闢的一塊內存空間。

  緩存的主要作用是暫時在內存中保存業務系統的數據處理結果,並且等待下次訪問使用。在日常開發的很多場合,由於受限於硬盤IO的性能或者我們自身業務系統的數據處理和獲取可能非常費時,當我們發現我們的系統這個數據請求量很大的時候,頻繁的IO和頻繁的邏輯處理會導致硬盤和CPU資源的瓶頸出現。緩存的作用就是將這些來自不易的數據保存在內存中,當有其他線程或者客戶端需要查詢相同的數據資源時,直接從緩存的內存塊中返回數據,這樣不但可以提高系統的響應時間,同時也可以節省對這些數據的處理流程的資源消耗,整體上來說,系統性能會有大大的提升。

  緩存在很多系統和架構中都用廣泛的應用,例如:

  1.CPU緩存
  2.操作系統緩存
  3.本地緩存
  4.分佈式緩存
  5.HTTP緩存
  6.數據庫緩存
  等等,可以說在計算機和網絡領域,緩存無處不在。可以這麼說,只要有硬件性能不對等,涉及到網絡傳輸的地方都會有緩存的身影。

  Guava Cache是一個全內存的本地緩存實現,它提供了線程安全的實現機制。整體上來說Guava cache 是本地緩存的不二之選,簡單易用,性能好。

  Guava Cache有兩種創建方式:

  1. cacheLoader
  2. callable callback

  通過這兩種方法創建的cache,和通常用map來緩存的做法比,不同在於,這兩種方法都實現了一種邏輯——從緩存中取key X的值,如果該值已經緩存過了,則返回緩存中的值,如果沒有緩存過,可以通過某個方法來獲取這個值。但不同的在於cacheloader的定義比較寬泛,是針對整個cache定義的,可以認爲是統一的根據key值load value的方法。而callable的方式較爲靈活,允許你在get的時候指定。

  cacheLoader方式實現實例:

複製代碼
    @Test
    public void TestLoadingCache() throws Exception{
        LoadingCache<String,String> cahceBuilder=CacheBuilder
        .newBuilder()
        .build(new CacheLoader<String, String>(){
            @Override
            public String load(String key) throws Exception {        
                String strProValue="hello "+key+"!";                
                return strProValue;
            }
            
        });        
        
        System.out.println("jerry value:"+cahceBuilder.apply("jerry"));
        System.out.println("jerry value:"+cahceBuilder.get("jerry"));
        System.out.println("peida value:"+cahceBuilder.get("peida"));
        System.out.println("peida value:"+cahceBuilder.apply("peida"));
        System.out.println("lisa value:"+cahceBuilder.apply("lisa"));
        cahceBuilder.put("harry", "ssdded");
        System.out.println("harry value:"+cahceBuilder.get("harry"));
    }
複製代碼

  輸出:

複製代碼
jerry value:hello jerry!
jerry value:hello jerry!
peida value:hello peida!
peida value:hello peida!
lisa value:hello lisa!
harry value:ssdded
複製代碼

  callable callback的實現:

複製代碼
    @Test
    public void testcallableCache()throws Exception{
        Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(1000).build();  
        String resultVal = cache.get("jerry", new Callable<String>() {  
            public String call() {  
                String strProValue="hello "+"jerry"+"!";                
                return strProValue;
            }  
        });  
        System.out.println("jerry value : " + resultVal);
        
        resultVal = cache.get("peida", new Callable<String>() {  
            public String call() {  
                String strProValue="hello "+"peida"+"!";                
                return strProValue;
            }  
        });  
        System.out.println("peida value : " + resultVal);  
    }

  輸出:
  jerry value : hello jerry!
  peida value : hello peida!
複製代碼

   cache的參數說明:

  回收的參數:
  1. 大小的設置:CacheBuilder.maximumSize(long)  CacheBuilder.weigher(Weigher)  CacheBuilder.maxumumWeigher(long)
  2. 時間:expireAfterAccess(long, TimeUnit) expireAfterWrite(long, TimeUnit)
  3. 引用:CacheBuilder.weakKeys() CacheBuilder.weakValues()  CacheBuilder.softValues()
  4. 明確的刪除:invalidate(key)  invalidateAll(keys)  invalidateAll()
  5. 刪除監聽器:CacheBuilder.removalListener(RemovalListener)
  

  refresh機制:
  1. LoadingCache.refresh(K)  在生成新的value的時候,舊的value依然會被使用。
  2. CacheLoader.reload(K, V) 生成新的value過程中允許使用舊的value
  3. CacheBuilder.refreshAfterWrite(long, TimeUnit) 自動刷新cache

   基於泛型的實現:

複製代碼
    /**
     * 不需要延遲處理(泛型的方式封裝)
     * @return
     */
    public  <K , V> LoadingCache<K , V> cached(CacheLoader<K , V> cacheLoader) {
          LoadingCache<K , V> cache = CacheBuilder
          .newBuilder()
          .maximumSize(2)
          .weakKeys()
          .softValues()
          .refreshAfterWrite(120, TimeUnit.SECONDS)
          .expireAfterWrite(10, TimeUnit.MINUTES)        
          .removalListener(new RemovalListener<K, V>(){
            @Override
            public void onRemoval(RemovalNotification<K, V> rn) {
                System.out.println(rn.getKey()+"被移除");  
                
            }})
          .build(cacheLoader);
          return cache;
    }
    
    /**
     * 通過key獲取value
     * 調用方式 commonCache.get(key) ; return String
     * @param key
     * @return
     * @throws Exception
     */
  
    public  LoadingCache<String , String> commonCache(final String key) throws Exception{
        LoadingCache<String , String> commonCache= cached(new CacheLoader<String , String>(){
                @Override
                public String load(String key) throws Exception {
                    return "hello "+key+"!";    
                }
          });
        return commonCache;
    }
    
    @Test
    public void testCache() throws Exception{
        LoadingCache<String , String> commonCache=commonCache("peida");
        System.out.println("peida:"+commonCache.get("peida"));
        commonCache.apply("harry");
        System.out.println("harry:"+commonCache.get("harry"));
        commonCache.apply("lisa");
        System.out.println("lisa:"+commonCache.get("lisa"));
    }
複製代碼

  輸出:

peida:hello peida!
harry:hello harry!
peida被移除
lisa:hello lisa!

  基於泛型的Callable Cache實現:

複製代碼
    private static Cache<String, String> cacheFormCallable = null; 

    
    /**
     * 對需要延遲處理的可以採用這個機制;(泛型的方式封裝)
     * @param <K>
     * @param <V>
     * @param key
     * @param callable
     * @return V
     * @throws Exception
     */
    public static <K,V> Cache<K , V> callableCached() throws Exception {
          Cache<K, V> cache = CacheBuilder
          .newBuilder()
          .maximumSize(10000)
          .expireAfterWrite(10, TimeUnit.MINUTES)
          .build();
          return cache;
    }

    
    private String getCallableCache(final String userName) {
           try {
             //Callable只有在緩存值不存在時,纔會調用
             return cacheFormCallable.get(userName, new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println(userName+" from db");
                    return "hello "+userName+"!";
               }
              });
           } catch (ExecutionException e) {
              e.printStackTrace();
              return null;
            } 
    }
    
    @Test
    public void testCallableCache() throws Exception{
         final String u1name = "peida";
         final String u2name = "jerry"; 
         final String u3name = "lisa"; 
         cacheFormCallable=callableCached();
         System.out.println("peida:"+getCallableCache(u1name));
         System.out.println("jerry:"+getCallableCache(u2name));
         System.out.println("lisa:"+getCallableCache(u3name));
         System.out.println("peida:"+getCallableCache(u1name));
         
    }
複製代碼

  輸出:

複製代碼
peida from db
peida:hello peida!
jerry from db
jerry:hello jerry!
lisa from db
lisa:hello lisa!
peida:hello peida!
複製代碼

  說明:Callable只有在緩存值不存在時,纔會調用,比如第二次調用getCallableCache(u1name)直接返回緩存中的值

  guava Cache數據移除:

  guava做cache時候數據的移除方式,在guava中數據的移除分爲被動移除和主動移除兩種。
  被動移除數據的方式,guava默認提供了三種方式:
  1.基於大小的移除:看字面意思就知道就是按照緩存的大小來移除,如果即將到達指定的大小,那就會把不常用的鍵值對從cache中移除。
  定義的方式一般爲 CacheBuilder.maximumSize(long),還有一種一種可以算權重的方法,個人認爲實際使用中不太用到。就這個常用的來看有幾個注意點,
    其一,這個size指的是cache中的條目數,不是內存大小或是其他;
    其二,並不是完全到了指定的size系統纔開始移除不常用的數據的,而是接近這個size的時候系統就會開始做移除的動作;
    其三,如果一個鍵值對已經從緩存中被移除了,你再次請求訪問的時候,如果cachebuild是使用cacheloader方式的,那依然還是會從cacheloader中再取一次值,如果這樣還沒有,就會拋出異常
  2.基於時間的移除:guava提供了兩個基於時間移除的方法
    expireAfterAccess(long, TimeUnit)  這個方法是根據某個鍵值對最後一次訪問之後多少時間後移除
    expireAfterWrite(long, TimeUnit)  這個方法是根據某個鍵值對被創建或值被替換後多少時間移除
  3.基於引用的移除:
  這種移除方式主要是基於java的垃圾回收機制,根據鍵或者值的引用關係決定移除
  主動移除數據方式,主動移除有三種方法:
  1.單獨移除用 Cache.invalidate(key)
  2.批量移除用 Cache.invalidateAll(keys)
  3.移除所有用 Cache.invalidateAll()
  如果需要在移除數據的時候有所動作還可以定義Removal Listener,但是有點需要注意的是默認Removal Listener中的行爲是和移除動作同步執行的,如果需要改成異步形式,可以考慮使用RemovalListeners.asynchronous(RemovalListener, Executor)


參考資料料:

http://www.kankanews.com/ICkengine/archives/75853.shtml

http://www.cnblogs.com/peida/p/Guava_Cache.html

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