###Guava本地緩存託底緩存以及異步更新緩存

Guava本地緩存託底緩存以及異步更新緩存

1.簡介

  • 1.1 guava本地緩存是開發中比較經常使用的組件,通常使用 LoadingCache,將須要的值加載在內存中,以下所示
LoadingCache<String,T> cacheLoader= CacheBuilder
                .newBuilder()
                .expireAfterWrite(5, TimeUnit.MINUTES)
                .build(new CacheLoader<String, T>() {
                    @Override
                    public T load(String key) throws Exception {      
                        return method(key);//1.執行method,獲取key對應的值
                    }
                });

使用的方法:java

T value=cacheLoader.get(key);//獲取key對應的值

2.託底緩存設置

  • 若是mehod()執行出錯的話,沒法拿到新的緩存。有時候,咱們但願若是method執行異常的時候,本地緩存依舊用過時的緩存,那麼能夠重寫CacheLoader中的reload方法進行設置
public abstract class RefreshKeepCacheLoader<K, V> extends CacheLoader<K, V> {
    public ListenableFuture<V> reload(K key, V oldValue) throws Exception {
        Object newvalue = null;
        try {
            newvalue = this.load(key);
        } catch (Exception e) {
        
        }

        if(newvalue == null) {
            newvalue = oldValue;
        }
        return Futures.immediateFuture(newvalue);
    }
}

那麼此時咱們的cacheLoader能夠這麼寫:git

LoadingCache<String,T> cacheLoader= CacheBuilder
                .newBuilder()
                .expireAfterWrite(5, TimeUnit.MINUTES)
                .build(new RefreshKeepCacheLoader<String, T>() {
                    @Override
                    public T load(String key) throws Exception {
                       
                        return method(key);//2.執行method,獲取key對應的值
                    }
                });

與上面不一樣的是,用自定義的RefreshKeepCacheLoader替換了CacheLoader類,因爲緩存過時會執行reload方法,若是reload異常,就採用oldValue。github

3.異步緩存設置

  • 3.1在併發條件下,有N個線程,若是緩存失效了,會有一個線程A去執行load方法(參見官方說明文檔If another call to get(K) or getUnchecked(K) is currently loading the value for key, simply waits for that thread to finish and returns its loaded value.),而其餘線程就會等待線程A獲得的結果,這樣就會影響性能。
  • 3.2 咱們可使所有線程返回舊的緩存值,把去異步更新緩存。方法以下:
    首先咱們看下,默認的reload的方法是怎麼寫的:
public ListenableFuture<V> reload(K key, V oldValue) throws Exception {
    checkNotNull(key);
    checkNotNull(oldValue);
    return Futures.immediateFuture(load(key));
  }

其實經過方法名,就能夠看出執行load方法,而後用Futures.immediateFuture封裝成ListenableFuture,再來看下immediateFuture方法web

/**
   * Creates a {@code ListenableFuture} which has its value set immediately upon construction. The
   * getters just return the value. This {@code Future} can't be canceled or timed out and its
   * {@code isDone()} method always returns {@code true}.
   */
  public static <V> ListenableFuture<V> immediateFuture(@Nullable V value) {
    if (value == null) {
      // This cast is safe because null is assignable to V for all V (i.e. it is covariant)
      @SuppressWarnings({"unchecked", "rawtypes"})
      ListenableFuture<V> typedNull = (ListenableFuture) ImmediateSuccessfulFuture.NULL;
      return typedNull;
    }
    return new ImmediateSuccessfulFuture<V>(value);
  }

這就比較清楚了,這個future中直接設置的是值。
那麼咱們如今就要重寫這個reload方法。ajax

public abstract  class RefreshAsyncCacheLoader<K, V> extends CacheLoader<K, V> {

    @Override
    public ListenableFuture<V> reload(final K key, final V oldValue) throws Exception {
        ListenableFutureTask<V> task = ListenableFutureTask.create(new Callable<V>() {
            public V call() {
                try {
                    return load((K) key);
                } catch (Exception e) {
                }
                return oldValue;
            }
        });
        ThreadPoolUtil.getInstance().execute(task);//這裏將這個task放到自定義的線程池中去執行,返回一個futrue,能夠經過future獲取線程執行獲取的值
        return task;
    }

}

4.總結

  • 最終咱們能夠獲得具備託底緩存設置,而且能夠異步更新緩存的guavaCache
LoadingCache<String,T> cacheLoader= CacheBuilder
                .newBuilder()
                .expireAfterWrite(5, TimeUnit.MINUTES)
                .build(new RefreshAsyncCacheLoader <String, T>() {
                    @Override
                    public T load(String key) throws Exception {
                       
                        return method(key);//1.執行method,獲取key對應的值
                    }
                });

5.實驗

下面咱們作一個簡單的實驗,涉及到WebController,CacheTest這兩個類。spring

package com.netease.mail.activity.web.controller;

import com.netease.mail.activity.constant.RetCode;
import com.netease.mail.activity.meta.RequestHolder;
import com.netease.mail.activity.meta.vo.common.AjaxResult;
import com.netease.mail.activity.service.CacheTest;
import com.netease.mail.rambo.log.StatLogger;
import com.netease.mail.rambo.log.StatLoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

/**
 * 重構web-control
 * Created by hzlaojiaqi on 2016/9/19.
 */
@Controller
public class WebController {

    private static final Logger INTERACTIVE_LOG = LoggerFactory.getLogger("INTERACTIVE_LOGGER");

    public static final StatLogger USER_TRACE_LOG = StatLoggerFactory.getLogger("emptyProject");

    @Autowired
    RequestHolder mRequestHolder;


    @Autowired
    CacheTest cacheTest;

    /**
     * 獲取當前uid的信息
     * @param httpServletRequest
     * @return
     */
    @RequestMapping(value = "/ajax/getActInfo.do",method = RequestMethod.GET)
    @ResponseBody
    public AjaxResult getActInfo2(HttpServletRequest httpServletRequest){
        String uid = mRequestHolder.getUid();
        return new AjaxResult(RetCode.SUCCESS,cacheTest.cacheGet(uid));
    }
}
  • 在看下CacheTest這個service
package com.netease.mail.activity.service;

import com.netease.mail.activity.cache.RedisConfigure;
import com.netease.mail.activity.web.controller.BaseAjaxController;
import com.netease.mail.util.common.TimeUtil;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

/**
 * Created by hzlaojiaqi on 2017/11/9.
 */
@Service
public class CacheTest extends BaseService{

    @Cacheable(cacheManager = "activityCacheManager",key = "'actCache_'+#uid",cacheNames = "actCache",unless = "#result==null")
    public String cacheGet(String uid){
        String s = uid + "_" + TimeUtil.now();
        THIRDPARTY_LOG.info("cacheGet in method uid:{} s:{}",uid,s);
        return s;
    }
}
  • 這裏設置緩存的時間比較短,只有10s鍾,具體的設置以下:
LoadingCache<String,Object> cache = CacheBuilder
            .newBuilder()
            .maximumSize(5210)
            .refreshAfterWrite(10, TimeUnit.SECONDS)
            .build(new RefreshAsyncCacheLoader<String, Object>() {
                @Override
                public Object load(String key) throws Exception {
                    Object o = mRedis.opsForValue().get(key);
                    THIRDPARTY_LOG.debug("thread:{},activityManager local cache reload key:{},result:{}",Thread.currentThread().getName(),key,o);
                    return o;
                }
            });
  • 最終能夠看到的log爲
  • 請求線程爲http-apr-8080-exec-8,該線程立刻返回了原來的值,而咱們自定義的線程custom_thread_member_11則執行了reload方法。(這裏加載後的值和以前的值是同樣的,特此說明)
  • 若是是第一次請求,因爲沒有舊的值,那麼http-apr-8080-exec-8會去執行reload方法。

Ps:文章不免有紕漏,望拍磚指正,感謝。編程

5.參考資料
相關文章
相關標籤/搜索