为什么说dubbo的声明式缓存不好用!!!
title: 為什么說dubbo的聲明式緩存不好用!!! tags:
- dubbo
- mock
- zookeeper
- cache
- cluster categories: dubbo date: 2017-06-25 18:18:53
前幾篇我們分析了dubbo的緩存以及緩存依賴的機制(filter)
- dubbo源碼系列之filter的今世?(f6car)
- dubbo源碼系列之filter的前生?(f6car
- dubbo緩存代碼分析?(f6car)
那么通常提供緩存的目的是什么呢?
關于兩級緩存的說明
通常為了更快的速度(以及一定的穩定性)
那么dubbo中的實現是通過filter機制基本上來緩存了我們需要結果。
但是默認提供的緩存并沒有清空機制(不能提供供用戶直接刷新緩存的機制)
我們系統中較為簡單的是提供jmx來共用戶刷新mac環境下ehcache 廣播rmi異常解決&JMX相關
其次緩存提供了更好的穩定性,通常第三方服務掛了時候對于已經緩存好的數據可以直接獲取緩存數據。
那么dubbo中的實現如何呢?下面來分析一下
對于穩定性來說我們一本是站在客戶端的角度的。那么我們先從ReferneceBean開始分析
拋開spring對于RefenenceBean的初始化過程,直接進入RefenenceBean的init方法。
private void init() {if (initialized) {return;}initialized = true;if (interfaceName == null || interfaceName.length() == 0) {throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");}// 獲取消費者全局配置checkDefault();appendProperties(this);if (getGeneric() == null && getConsumer() != null) {setGeneric(getConsumer().getGeneric());}if (ProtocolUtils.isGeneric(getGeneric())) {interfaceClass = GenericService.class;} else {try {interfaceClass = Class.forName(interfaceName, true, Thread.currentThread().getContextClassLoader());} catch (ClassNotFoundException e) {throw new IllegalStateException(e.getMessage(), e);}checkInterfaceAndMethods(interfaceClass, methods);}String resolve = System.getProperty(interfaceName);String resolveFile = null;if (resolve == null || resolve.length() == 0) {resolveFile = System.getProperty("dubbo.resolve.file");if (resolveFile == null || resolveFile.length() == 0) {File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");if (userResolveFile.exists()) {resolveFile = userResolveFile.getAbsolutePath();}}if (resolveFile != null && resolveFile.length() > 0) {Properties properties = new Properties();FileInputStream fis = null;try {fis = new FileInputStream(new File(resolveFile));properties.load(fis);} catch (IOException e) {throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e);} finally {try {if(null != fis) fis.close();} catch (IOException e) {logger.warn(e.getMessage(), e);}}resolve = properties.getProperty(interfaceName);}}if (resolve != null && resolve.length() > 0) {url = resolve;if (logger.isWarnEnabled()) {if (resolveFile != null && resolveFile.length() > 0) {logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service.");} else {logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service.");}}}if (consumer != null) {if (application == null) {application = consumer.getApplication();}if (module == null) {module = consumer.getModule();}if (registries == null) {registries = consumer.getRegistries();}if (monitor == null) {monitor = consumer.getMonitor();}}if (module != null) {if (registries == null) {registries = module.getRegistries();}if (monitor == null) {monitor = module.getMonitor();}}if (application != null) {if (registries == null) {registries = application.getRegistries();}if (monitor == null) {monitor = application.getMonitor();}}checkApplication();checkStubAndMock(interfaceClass);Map<String, String> map = new HashMap<String, String>();Map<Object, Object> attributes = new HashMap<Object, Object>();map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));if (ConfigUtils.getPid() > 0) {map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));}if (! isGeneric()) {String revision = Version.getVersion(interfaceClass, version);if (revision != null && revision.length() > 0) {map.put("revision", revision);}String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();if(methods.length == 0) {logger.warn("NO method found in service interface " + interfaceClass.getName());map.put("methods", Constants.ANY_VALUE);}else {map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));}}map.put(Constants.INTERFACE_KEY, interfaceName);appendParameters(map, application);appendParameters(map, module);appendParameters(map, consumer, Constants.DEFAULT_KEY);appendParameters(map, this);String prifix = StringUtils.getServiceKey(map);if (methods != null && methods.size() > 0) {for (MethodConfig method : methods) {appendParameters(map, method, method.getName());String retryKey = method.getName() + ".retry";if (map.containsKey(retryKey)) {String retryValue = map.remove(retryKey);if ("false".equals(retryValue)) {map.put(method.getName() + ".retries", "0");}}appendAttributes(attributes, method, prifix + "." + method.getName());checkAndConvertImplicitConfig(method, map, attributes);}}//attributes通過系統context進行存儲.StaticContext.getSystemContext().putAll(attributes);ref = createProxy(map);} 復制代碼除開一些必要的設置之外,主要進入createProxy方法
({ "unchecked", "rawtypes", "deprecation" })private T createProxy(Map<String, String> map) {URL tmpUrl = new URL("temp", "localhost", 0, map);final boolean isJvmRefer;if (isInjvm() == null) {if (url != null && url.length() > 0) { //指定URL的情況下,不做本地引用isJvmRefer = false;} else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {//默認情況下如果本地有服務暴露,則引用本地服務.isJvmRefer = true;} else {isJvmRefer = false;}} else {isJvmRefer = isInjvm().booleanValue();}if (isJvmRefer) {URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);invoker = refprotocol.refer(interfaceClass, url);if (logger.isInfoEnabled()) {logger.info("Using injvm service " + interfaceClass.getName());}} else {if (url != null && url.length() > 0) { // 用戶指定URL,指定的URL可能是對點對直連地址,也可能是注冊中心URLString[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);if (us != null && us.length > 0) {for (String u : us) {URL url = URL.valueOf(u);if (url.getPath() == null || url.getPath().length() == 0) {url = url.setPath(interfaceName);}if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));} else {urls.add(ClusterUtils.mergeUrl(url, map));}}}} else { // 通過注冊中心配置拼裝URLList<URL> us = loadRegistries(false);if (us != null && us.size() > 0) {for (URL u : us) {URL monitorUrl = loadMonitor(u);if (monitorUrl != null) {map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));}urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));}}if (urls == null || urls.size() == 0) {throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");}}if (urls.size() == 1) {invoker = refprotocol.refer(interfaceClass, urls.get(0));} else {List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();URL registryURL = null;for (URL url : urls) {invokers.add(refprotocol.refer(interfaceClass, url));if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {registryURL = url; // 用了最后一個registry url}}if (registryURL != null) { // 有 注冊中心協議的URL// 對有注冊中心的Cluster 只用 AvailableClusterURL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);invoker = cluster.join(new StaticDirectory(u, invokers));} else { // 不是 注冊中心的URLinvoker = cluster.join(new StaticDirectory(invokers));}}}Boolean c = check;if (c == null && consumer != null) {c = consumer.isCheck();}if (c == null) {c = true; // default true}if (c && ! invoker.isAvailable()) {throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());}if (logger.isInfoEnabled()) {logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());}// 創建服務代理return (T) proxyFactory.getProxy(invoker);} 復制代碼便于說明我們此處使用zookeeper作為協議 可以看到此處我們走到的業務邏輯為
List<URL> us = loadRegistries(false);protected List<URL> loadRegistries(boolean provider) {checkRegistry();List<URL> registryList = new ArrayList<URL>();if (registries != null && registries.size() > 0) {for (RegistryConfig config : registries) {String address = config.getAddress();if (address == null || address.length() == 0) {address = Constants.ANYHOST_VALUE;}String sysaddress = System.getProperty("dubbo.registry.address");if (sysaddress != null && sysaddress.length() > 0) {address = sysaddress;}if (address != null && address.length() > 0&& ! RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {Map<String, String> map = new HashMap<String, String>();appendParameters(map, application);appendParameters(map, config);map.put("path", RegistryService.class.getName());map.put("dubbo", Version.getVersion());map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));if (ConfigUtils.getPid() > 0) {map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));}if (! map.containsKey("protocol")) {if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {map.put("protocol", "remote");} else {map.put("protocol", "dubbo");}}List<URL> urls = UrlUtils.parseURLs(address, map);for (URL url : urls) {url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());url = url.setProtocol(Constants.REGISTRY_PROTOCOL);if ((provider && url.getParameter(Constants.REGISTER_KEY, true))|| (! provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {registryList.add(url);}}}}}return registryList;}通篇最重要的就是設置了Protocol為Constants.REGISTRY\_PROTOCOL 因此我們可以認為此處使用的是RegistryProtocol?。private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);directory.setRegistry(registry);directory.setProtocol(protocol);URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());if (! Constants.ANY_VALUE.equals(url.getServiceInterface())&& url.getParameter(Constants.REGISTER_KEY, true)) {registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,Constants.CHECK_KEY, String.valueOf(false)));}directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,Constants.PROVIDERS_CATEGORY+ "," + Constants.CONFIGURATORS_CATEGORY+ "," + Constants.ROUTERS_CATEGORY));return cluster.join(directory);} 復制代碼RegistryProtocol在生成Invoker時使用了Cluster,那么這個Cluster是啥呢?
通常來說指的是(使用*或者復雜分組)
private Cluster getMergeableCluster() {return ExtensionLoader.getExtensionLoader(Cluster.class).getExtension("mergeable");} 復制代碼而cluster對于來說spi如下
????mock=com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper????failover=com.alibaba.dubbo.rpc.cluster.support.FailoverCluster
????failfast=com.alibaba.dubbo.rpc.cluster.support.FailfastCluster
????failsafe=com.alibaba.dubbo.rpc.cluster.support.FailsafeCluster
????failback=com.alibaba.dubbo.rpc.cluster.support.FailbackCluster
????forking=com.alibaba.dubbo.rpc.cluster.support.ForkingCluster
????available=com.alibaba.dubbo.rpc.cluster.support.AvailableCluster
????mergeable=com.alibaba.dubbo.rpc.cluster.support.MergeableCluster
????broadcast=com.alibaba.dubbo.rpc.cluster.support.BroadcastCluster
復制代碼
其中MockClusterWrapper是對應的wrapper 參考?dubbo源碼系列之filter的前生
*/public class MockClusterWrapper implements Cluster {private Cluster cluster;public MockClusterWrapper(Cluster cluster) {this.cluster = cluster;}public <T> Invoker<T> join(Directory<T> directory) throws RpcException {return new MockClusterInvoker<T>(directory,this.cluster.join(directory));}} 復制代碼那么可以確定生成的Invoker就是MockClusterInvoker
這個Mock中注入的為cluster接口spi默認為failover
(FailoverCluster.NAME)public interface Cluster {/*** Merge the directory invokers to a virtual invoker.** @param <T>* @param directory* @return cluster invoker* @throws RpcException*/<T> Invoker<T> join(Directory<T> directory) throws RpcException;} 復制代碼參考說明
| Failover Cluster | Stable | 失敗自動切換,當出現失敗,重試其它服務器,通常用于讀操作(推薦使用) | 重試會帶來更長延遲 | 可用于生產環境 | Alibaba |
| Failfast Cluster | Stable | 快速失敗,只發起一次調用,失敗立即報錯,通常用于非冪等性的寫操作 | 如果有機器正在重啟,可能會出現調用失敗 | 可用于生產環境 | Alibaba |
| Failsafe Cluster | Stable | 失敗安全,出現異常時,直接忽略,通常用于寫入審計日志等操作 | 調用信息丟失 | 可用于生產環境 | Monitor |
| Failback Cluster | Tested | 失敗自動恢復,后臺記錄失敗請求,定時重發,通常用于消息通知操作 | 不可靠,重啟丟失 | 可用于生產環境 | Registry |
| Forking Cluster | Tested | 并行調用多個服務器,只要一個成功即返回,通常用于實時性要求較高的讀操作 | 需要浪費更多服務資源 | 可用于生產環境 | ? |
| Broadcast Cluster | Tested | 廣播調用所有提供者,逐個調用,任意一臺報錯則報錯,通常用于更新提供方本地狀態 | 速度慢,任意一臺報錯則報錯 | 可用于生產環境 | ? |
我們獲取到了一個FailoverClusterInvoker
注意此處是由FailoverCluster直接new的,換言之沒有任何包裝和代理
public class FailoverCluster implements Cluster {public final static String NAME = "failover";public <T> Invoker<T> join(Directory<T> directory) throws RpcException {return new FailoverClusterInvoker<T>(directory);}} 復制代碼那么我們在實際調用的時候(上述情景)使用的是由MockClusterInvoker委托的FailoverClusterInvoker
出現MockClusterInvoker的主要原因應該是可以直接使用dubbo的 mock功能可以在服務調用結束時收尾(做其他處理)===》此處先不提stub
那么每次調用均會執行到
?({ "unchecked", "rawtypes" })public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {List<Invoker<T>> copyinvokers = invokers;checkInvokers(copyinvokers, invocation);int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;if (len <= 0) {len = 1;}// retry loop.RpcException le = null; // last exception.List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // invoked invokers.Set<String> providers = new HashSet<String>(len);for (int i = 0; i < len; i++) {//重試時,進行重新選擇,避免重試時invoker列表已發生變化.//注意:如果列表發生了變化,那么invoked判斷會失效,因為invoker示例已經改變if (i > 0) {checkWheatherDestoried();copyinvokers = list(invocation);//重新檢查一下checkInvokers(copyinvokers, invocation);}Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);invoked.add(invoker);RpcContext.getContext().setInvokers((List)invoked);try {Result result = invoker.invoke(invocation);if (le != null && logger.isWarnEnabled()) {logger.warn("Although retry the method " + invocation.getMethodName()+ " in the service " + getInterface().getName()+ " was successful by the provider " + invoker.getUrl().getAddress()+ ", but there have been failed providers " + providers+ " (" + providers.size() + "/" + copyinvokers.size()+ ") from the registry " + directory.getUrl().getAddress()+ " on the consumer " + NetUtils.getLocalHost()+ " using the dubbo version " + Version.getVersion() + ". Last error is: "+ le.getMessage(), le);}return result;} catch (RpcException e) {if (e.isBiz()) { // biz exception.throw e;}le = e;} catch (Throwable e) {le = new RpcException(e.getMessage(), e);} finally {providers.add(invoker.getUrl().getAddress());}}throw new RpcException(le != null ? le.getCode() : 0, "Failed to invoke the method "+ invocation.getMethodName() + " in the service " + getInterface().getName()+ ". Tried " + len + " times of the providers " + providers+ " (" + providers.size() + "/" + copyinvokers.size()+ ") from the registry " + directory.getUrl().getAddress()+ " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "+ Version.getVersion() + ". Last error is: "+ (le != null ? le.getMessage() : ""), le != null && le.getCause() != null ? le.getCause() : le);}protected List<Invoker<T>> list(Invocation invocation) throws RpcException {List<Invoker<T>> invokers = directory.list(invocation);return invokers;}public List<Invoker<T>> doList(Invocation invocation) {if (forbidden) {throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, "Forbid consumer " + NetUtils.getLocalHost() + " access service " + getInterface().getName() + " from registry " + getUrl().getAddress() + " use dubbo version " + Version.getVersion() + ", Please check registry access list (whitelist/blacklist).");}List<Invoker<T>> invokers = null;Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; // local referenceif (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {String methodName = RpcUtils.getMethodName(invocation);Object[] args = RpcUtils.getArguments(invocation);if(args != null && args.length > 0 && args[0] != null&& (args[0] instanceof String || args[0].getClass().isEnum())) {invokers = localMethodInvokerMap.get(methodName + "." + args[0]); // 可根據第一個參數枚舉路由}if(invokers == null) {invokers = localMethodInvokerMap.get(methodName);}if(invokers == null) {invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);}if(invokers == null) {Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator();if (iterator.hasNext()) {invokers = iterator.next();}}}return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers;} 復制代碼那么即使此刻有緩存的時候我們也無法使用該緩存,因為緩存的CacheFilter根本沒有機會執行,反而直接報錯。那么使用聲明式緩存完成dubbo穩定性的美夢破滅了。
接著分析,上述代碼中的字段forbidden是何時被指為true的呢?
/*** 根據invokerURL列表轉換為invoker列表。轉換規則如下:* 1.如果url已經被轉換為invoker,則不在重新引用,直接從緩存中獲取,注意如果url中任何一個參數變更也會重新引用* 2.如果傳入的invoker列表不為空,則表示最新的invoker列表* 3.如果傳入的invokerUrl列表是空,則表示只是下發的override規則或route規則,需要重新交叉對比,決定是否需要重新引用。* @param invokerUrls 傳入的參數不能為null*/private void refreshInvoker(List<URL> invokerUrls){if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null&& Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {this.forbidden = true; // 禁止訪問this.methodInvokerMap = null; // 置空列表destroyAllInvokers(); // 關閉所有Invoker} else {this.forbidden = false; // 允許訪問Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local referenceif (invokerUrls.size() == 0 && this.cachedInvokerUrls != null){invokerUrls.addAll(this.cachedInvokerUrls);} else {this.cachedInvokerUrls = new HashSet<URL>();this.cachedInvokerUrls.addAll(invokerUrls);//緩存invokerUrls列表,便于交叉對比}if (invokerUrls.size() ==0 ){return;}Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls) ;// 將URL列表轉成Invoker列表Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // 換方法名映射Invoker列表// state change//如果計算錯誤,則不進行處理.if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0 ){logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :"+invokerUrls.size() + ", invoker.size :0. urls :"+invokerUrls.toString()));return ;}this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;this.urlInvokerMap = newUrlInvokerMap;try{destroyUnusedInvokers(oldUrlInvokerMap,newUrlInvokerMap); // 關閉未使用的Invoker}catch (Exception e) {logger.warn("destroyUnusedInvokers error. ", e);}}}```此段代碼當invokerUrls中有且僅有一個empty的protocol的URL時將會強行置為true。 ```javapublic synchronized void notify(List<URL> urls) {List<URL> invokerUrls = new ArrayList<URL>();List<URL> routerUrls = new ArrayList<URL>();List<URL> configuratorUrls = new ArrayList<URL>();for (URL url : urls) {String protocol = url.getProtocol();String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);if (Constants.ROUTERS_CATEGORY.equals(category)|| Constants.ROUTE_PROTOCOL.equals(protocol)) {routerUrls.add(url);} else if (Constants.CONFIGURATORS_CATEGORY.equals(category)|| Constants.OVERRIDE_PROTOCOL.equals(protocol)) {configuratorUrls.add(url);} else if (Constants.PROVIDERS_CATEGORY.equals(category)) {invokerUrls.add(url);} else {logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost());}}// configuratorsif (configuratorUrls != null && configuratorUrls.size() >0 ){this.configurators = toConfigurators(configuratorUrls);}// routersif (routerUrls != null && routerUrls.size() >0 ){List<Router> routers = toRouters(routerUrls);if(routers != null){ // null - do nothingsetRouters(routers);}}List<Configurator> localConfigurators = this.configurators; // local reference// 合并override參數this.overrideDirectoryUrl = directoryUrl;if (localConfigurators != null && localConfigurators.size() > 0) {for (Configurator configurator : localConfigurators) {this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);}}// providersrefreshInvoker(invokerUrls);}```通常來說我們使用zookeeper作為注冊。通常provider在注冊一個臨時節點(當服務被摘掉時將會自動消失)那么客戶端會對對應path進行監聽,如果出現childremove ```javaif (zkListener == null) {listeners.putIfAbsent(listener, new ChildListener() {public void childChanged(String parentPath, List<String> currentChilds) {ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));}});zkListener = listeners.get(listener);} 復制代碼此時會對currentChilds進行處理
private List<URL> toUrlsWithEmpty(URL consumer, String path, List<String> providers) {List<URL> urls = toUrlsWithoutEmpty(consumer, providers);if (urls == null || urls.isEmpty()) {int i = path.lastIndexOf('/');String category = i < 0 ? path : path.substring(i + 1);URL empty = consumer.setProtocol(Constants.EMPTY_PROTOCOL).addParameter(Constants.CATEGORY_KEY, category);urls.add(empty);}return urls;} 復制代碼一旦發現當前孩子節點為空那么新建一條protocol為empty的URL放入,此時由于上述代碼將會將forbidden置為true,因此出現消費者不能正常獲取緩存。
當然對于結果的緩存還是有用處的,但是一般如果單機服務節點出現故障即使存有緩存也無法消費略有些遺憾。
下一篇可以介紹使用Stub或者mock來進行dubbo緩存前置
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的为什么说dubbo的声明式缓存不好用!!!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaScript实现排序算法
- 下一篇: 好文收藏系列(二)