dubbo同名方法的问题及思考
2018-09-05 18:47
465 查看
背景
今天小伙伴问我一个问题分析
我们系统中一直存在该种实践 似乎从来都稳稳的运行,没有任何问题呢……比如
* * 查询客户List * @param customerCarVO * @param curPage * @return * @throws Exception */ @Deprecated PageResult<CustomerCarVO> getPageCustomerList(CustomerCarVO customerCarVO, int curPage) throws Exception; /** * 查询客户List * @param customerCarSo * @return * @throws Exception */ PageResult<CustomerCarVO> getPageCustomerList(CustomerCarSo customerCarSo) throws Exception;
用了这么久 似乎也没有出现问题~
那么是否可以很武断的认为同名方法没有问题???
我们想一下以前webservice中似乎有个限制 方法同名将不能使用 也就是说不支持方法的重载 那么为何dubbo没有这个问题?
或者说其实存在问题 只不过我们没注意???
我们首先来看一下对应url如何映射到invoker的!
/** * 根据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 reference if (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来刷新获取到对应的远程执行者 那么这段核心代码将会映射直接的调用者!根据method的映射newMethodInvokerMap 也就是根据method名称获取到最新的invoker列表
如下方法通过url转换成为特定的根据method的invoker列表
/** * 将invokers列表转成与方法的映射关系 * * @param invokersMap Invoker列表 * @return Invoker与方法的映射关系 */ private Map<String, List<Invoker<T>>> toMethodInvokers(Map<String, Invoker<T>> invokersMap) { Map<String, List<Invoker<T>>> newMethodInvokerMap = new HashMap<String, List<Invoker<T>>>(); // 按提供者URL所声明的methods分类,兼容注册中心执行路由过滤掉的methods List<Invoker<T>> invokersList = new ArrayList<Invoker<T>>(); if (invokersMap != null && invokersMap.size() > 0) { for (Invoker<T> invoker : invokersMap.values()) { String parameter = invoker.getUrl().getParameter(Constants.METHODS_KEY); if (parameter != null && parameter.length() > 0) { String[] methods = Constants.COMMA_SPLIT_PATTERN.split(parameter); if (methods != null && methods.length > 0) { for (String method : methods) { if (method != null && method.length() > 0 && ! Constants.ANY_VALUE.equals(method)) { List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method); if (methodInvokers == null) { methodInvokers = new ArrayList<Invoker<T>>(); newMethodInvokerMap.put(method, methodInvokers); } methodInvokers.add(invoker); } } } } invokersList.add(invoker); } } newMethodInvokerMap.put(Constants.ANY_VALUE, invokersList); if (serviceMethods != null && serviceMethods.length > 0) { for (String method : serviceMethods) { List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method); if (methodInvokers == null || methodInvokers.size() == 0) { methodInvokers = invokersList; } newMethodInvokerMap.put(method, route(methodInvokers, method)); } } // sort and unmodifiable for (String method : new HashSet<String>(newMethodInvokerMap.keySet())) { List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method); Collections.sort(methodInvokers, InvokerComparator.getComparator()); newMethodInvokerMap.put(method, Collections.unmodifiableList(methodInvokers)); } return Collections.unmodifiableMap(newMethodInvokerMap); }
由于通过url拼接的时候如下操作
if (generic) { map.put("generic", String.valueOf(true)); map.put("methods", Constants.ANY_VALUE); } else { 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)), ",")); } }
很明显的set将会抹除掉重复的方法名称 那么实质上在url上不会出现相同的methodname
那么为何在使用的时候没有错呢???
我们来继续回顾上一段代码
由于根据方法名称放入了特定的invoker 而后根据invoker传递给特殊的调用参数【这个就是实际的网络请求 也就是rpc】
此时可以看到真正传递过去的是
/** * Invocation. (API, Prototype, NonThreadSafe) * * @serial Don't change the class name and package name. * @see com.alibaba.dubbo.rpc.Invoker#invoke(Invocation) * @see com.alibaba.dubbo.rpc.RpcInvocation * @author qian.lei * @author william.liangf */ public interface Invocation { /** * get method name. * * @serial * @return method name. */ String getMethodName(); /** * get parameter types. * * @serial * @return parameter types. */ Class<?>[] getParameterTypes(); /** * get arguments. * * @serial * @return arguments. */ Object[] getArguments(); /** * get attachments. * * @serial * @return attachments. */ Map<String, String> getAttachments(); /** * get attachment by key. * * @serial * @return attachment value. */ String getAttachment(String key); /** * get attachment by key with default value. * * @serial * @return attachment value. */ String getAttachment(String key, String defaultValue); /** * get the invoker in current context. * * @transient * @return invoker. */ Invoker<?> getInvoker(); }
很明显这里面可以获取到真实的参数 包括参数类型 自然调用不会出错了啊!!!
深思
可以认为没问题了么???对的 大部分场景是没有问题的!!!彩蛋
设想如此场景存在同一个服务的两个副本
假设分别称为服务1-1和服务1-2
服务1-1中存在方法为getA()
结果我们升级了服务1-1 增加了方法getA(A a) ====>只是多了一个方法
当我们使用蓝绿发布的时候 首先将服务1-2成功升级多了一个getA(A a)方法
此时refer到该service的 接收到服务推送 此时重新解析methodMap 此时getA方法中放置了两个invoker 分别对用服务1-1和服务1-2
那么当调用refer调用getA()从服务1-1和服务1-2调用均没有问题 但是refer也升级了之后调用大搜了方法getA(A a)
此时将调用到了getA 但是方法列表仍然返回了两个invoker 此时如果取到了服务1-1 那么在调用之后服务1-1 将会发生失败
我们可以查看实质上通过netty等方式传输之后 在provider可以获得一个对应的DecodeableRpcInvocation 这样就回到了包装前的invoker
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { // TODO Wrapper类不能正确处理带$的类名 final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type); return new AbstractProxyInvoker<T>(proxy, type, url) { @Override protected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable { return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); } }; }
最终完成业务调用
相关文章推荐
- 说说单词智能纠错算法--探讨思考问题的方法
- Webservice中如何实现方法重载--(方法名同名时出现的问题)
- “同名连接已存在”问题的解决方法
- 动态规划;漂亮打印问题;时间复杂度O(n方); 思考方法记录在内;
- 用js操作html dom对象是,对象和js方法同名的问题
- 有趣的同名不同约束泛型扩展方法的编译问题
- 最近开发中遇到的DLL问题思考及解决方法
- java 向上转型之后调用子类的同名变量/方法的问题(多态)
- delphi 开发中遇到的DLL问题思考及解决方法
- 64位进程调用32位dll的解决方法 / 程序64位化带来的问题和思考
- 思考问题的方法
- 技巧:多共享动态库中同名对象重复析构问题的解决方法
- delphi 开发中遇到的DLL问题思考及解决方法
- 技巧:多共享动态库中同名对象重复析构问题的解决方法
- Powerdesgner数据模型设计中,属性名同名问题的解决方法
- 勤于思考:jquery.getJSON的缓存问题的解决方法
- 开发中遇到的DLL问题思考及解决方法
- 遇到问题应该多思考一下——由一个泛型方法想到的
- 搭建SSH时的思考和遇到的几个问题的解决方法
- 使用动态规划解花店问题 两种思考方法分析