dubbo 緩存的使用和實現解析

dubbo緩存主要實現,對方法調用結果的緩存。
在服務消費方和提供方均可以配置使用緩存。
以消費方爲例,能夠配置全局緩存策略,這樣全部服務引用都啓動緩存
<dubbo:consumer cache="lru"/>
能夠對某個服務引用配置緩存策略
<dubbo:reference id="demoService"   interface="demo.dubbo.api.DemoService" cache="lru"  >
也支持對單個方法啓用緩存策略
<dubbo:reference id="demoService"    interface="demo.dubbo.api.DemoService" >
   <dubbo:method name="sayHello" cache="lru"> </dubbo:method>
</dubbo:reference>
服務方配置方法同樣。java

下面分析具體的實現過程設計模式

dubbo的緩存是經過過濾器實現的
經過 這篇博文 對註解Activate的認識,還有緩存的使用配置cache
這裏找到了對應的Filter實現CacheFilterapi

//Activate指明服務方和消費方均可以啓用緩存
@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY)
public class CacheFilter implements Filter {

    private CacheFactory cacheFactory;

    public void setCacheFactory(CacheFactory cacheFactory) {
        this.cacheFactory = cacheFactory;
    }

    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
          
        if (cacheFactory != null && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.CACHE_KEY))) {
           //invoker.getUrl().addParameter(Constants.METHOD_KEY, invocation.getMethodName()) 
	   //做爲緩存對象的key 可知不一樣的服務提供者,每一個方法都會單獨分配一個緩存對象
	    Cache cache = cacheFactory.getCache(invoker.getUrl().addParameter(Constants.METHOD_KEY, invocation.getMethodName()));
            if (cache != null) {
	        //方法的參數做爲key
                String key = StringUtils.toArgumentString(invocation.getArguments());
                if (cache != null && key != null) {
                    Object value = cache.get(key);
                    if (value != null) {
		        //緩存命中,直接返回,也就是說,
			//這裏要注意,若是有多個過濾器,cache後面的過濾器不會執行
                        return new RpcResult(value);
                    }
                    Result result = invoker.invoke(invocation);
                    if (!result.hasException()) {
                        cache.put(key, result.getValue());
                    }
                    return result;
                }
            }
        }
        return invoker.invoke(invocation);
    }

}

關於cacheFactory的實現,這裏看下由dubbo動態生成的CacheFactory$Adaptive源碼數組

public class CacheFactory$Adaptive implements com.alibaba.dubbo.cache.CacheFactory {
    public com.alibaba.dubbo.cache.Cache getCache(com.alibaba.dubbo.common.URL arg0) {
        if (arg0 == null) 
            throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        //默認是 lru 緩存策略
        String extName = url.getParameter("cache", "lru");
        if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.cache.CacheFactory) name from url(" + url.toString() + ") use keys([cache])");
        com.alibaba.dubbo.cache.CacheFactory extension = (com.alibaba.dubbo.cache.CacheFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.cache.CacheFactory.class).getExtension(extName);
        return extension.getCache(arg0);
    }
}

duobb提供了CacheFactory的具體有三種實現,類圖以下緩存

AbstractCacheFactory抽象父類中定義了緩存對象的獲取方法getCache數據結構

public abstract class AbstractCacheFactory implements CacheFactory {

    private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();

    public Cache getCache(URL url) {
        String key = url.toFullString();
        Cache cache = caches.get(key);
        if (cache == null) {
            caches.put(key, createCache(url));
            cache = caches.get(key);
        }
        return cache;
    }
    //具體緩存實如今子類中,這也是dubbo一向的設計模式,公共方法提到抽象類中
    protected abstract Cache createCache(URL url);

}
//LruCacheFactory子類,返回LruCache對象,實現LRU策略緩存
public class LruCacheFactory extends AbstractCacheFactory {

    protected Cache createCache(URL url) {
        return new LruCache(url);
    }

}
//JCacheFactory子類,返回LruCache對象,可與符合JSR107規範的緩存橋接
public class JCacheFactory extends AbstractCacheFactory {

    protected Cache createCache(URL url) {
        return new JCache(url);
    }

}
//ThreadLocalCacheFactory子類,返回LruCache對象,ThreadLocal利用當前線程緩存
public class ThreadLocalCacheFactory extends AbstractCacheFactory {

    protected Cache createCache(URL url) {
        return new ThreadLocalCache(url);
    }

}

上面提到的三種Cache對象類都實現了com.alibaba.dubbo.cache接口,類圖以下,併發

Cache接口很簡單ide

public interface Cache {
    //put value到緩存
    void put(Object key, Object value);
    //經過key 獲取value
    Object get(Object key);

}

三種Cache類都實現也值得學習
LruCache類函數

public class LruCache implements Cache {

    private final Map<Object, Object> store;

    public LruCache(URL url) {
        //從配置中,獲取cache大小
        final int max = url.getParameter("cache.size", 1000);
        //內部是LRUCache對象
        this.store = new LRUCache<Object, Object>(max);
    }

    public void put(Object key, Object value) {
        store.put(key, value);
    }

    public Object get(Object key) {
        return store.get(key);
    }

}

分析LRUCache源碼,能夠看到LRUCache繼承了LinkedHashMap,而LinkedHashMap是個雙向鏈表,它是個自然的lru數據結構
只要定義LinkedHashMap是有序的,好比LRUCache的構造函數的定義學習

public LRUCache(int maxCapacity) {
        //默認有序的鏈表,初始數組申請空間大小16,負載因子0.75(觸發擴展的填充度臨界值)
        super(16, DEFAULT_LOAD_FACTOR, true);
        this.maxCapacity = maxCapacity;
    }

並重寫LinkedHashMap的removeEldestEntry方法

@Override
    //定義換出緩存對象的條,這裏是大小超過最大容量
    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
        return size() > maxCapacity;
    }

這樣就能夠完成一個LRU緩存容器的建立,具體實現,可讀寫LinkedHashMap源碼

ThreadLocalCache類實現

public class ThreadLocalCache implements Cache {
    //經過ThreadLocal把store 綁定到當前線程
    private final ThreadLocal<Map<Object, Object>> store;

    public ThreadLocalCache(URL url) {
        this.store = new ThreadLocal<Map<Object, Object>>() {
            @Override
            protected Map<Object, Object> initialValue() {
                return new HashMap<Object, Object>();
            }
        };
    }

    public void put(Object key, Object value) {
        store.get().put(key, value);
    }

    public Object get(Object key) {
        return store.get().get(key);
    }

}

JCache類實現

public class JCache implements com.alibaba.dubbo.cache.Cache {

    private final Cache<Object, Object> store;

    public JCache(URL url) {
        String method = url.getParameter(Constants.METHOD_KEY, "");
        //每一個服務提供者每一個方法,分配一個cache對象
        String key = url.getAddress() + "." + url.getServiceKey() + "." + method;
        // jcache 爲SPI實現的全限定類名
        String type = url.getParameter("jcache");
        
        //經過CachingProvider 等jsr107規範相關接口 操做,這樣,就能經過購spi 機制橋接各類緩存實現了
        CachingProvider provider = type == null || type.length() == 0 ? Caching.getCachingProvider() : Caching.getCachingProvider(type);
        CacheManager cacheManager = provider.getCacheManager();
        Cache<Object, Object> cache = cacheManager.getCache(key);
        if (cache == null) {
            try {
                //configure the cache
                MutableConfiguration config =
                        new MutableConfiguration<Object, Object>()
                                .setTypes(Object.class, Object.class)
                                 //經過cache.write.expire 設置失效時間,默認爲1分鐘,但不知道cache.write.expire在哪設置的??
                                .setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(new Duration(TimeUnit.MILLISECONDS, url.getMethodParameter(method, "cache.write.expire", 60 * 1000))))
                                .setStoreByValue(false)
                                .setManagementEnabled(true)
                                .setStatisticsEnabled(true);
                cache = cacheManager.createCache(key, config);
            } catch (CacheException e) {
                // 初始化cache 的併發狀況
                cache = cacheManager.getCache(key);
            }
        }

        this.store = cache;
    }

    public void put(Object key, Object value) {
        store.put(key, value);
    }

    public Object get(Object key) {
        return store.get(key);
    }

}

個人博客即將搬運同步至騰訊雲+社區,邀請你們一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=15h2lq45eu17e

相關文章
相關標籤/搜索