您的位置:首页 > 其它

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);
}
};
}

最终完成业务调用
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Dubbo