dubbox注解的一个坑
我和我同事Daniel排查的一个问题,原文是我同事Daniel写的,我做了些修改了补充。
我们dubbox的provider端有很多service开发时没有考虑到幂等问题,于是只能暂时关掉dubbo的重试功能。
dubbo自带的配置中有一个reties是可以配置重试次数的,官方文档上说默认两次,设置成0可以关闭重试。这种配置在使用xml 方式配置时是没有问题的,然而用注解就比较坑。
上图是同事写的使用注解的测试类截图,有没有发现0是灰色的?看看编译后的.class文件:
为什么呢,我们看这个注解的源码:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) public @interface Reference { ... int retries() default 0; ... }
这个注解有个默认值0,编译的时候被编译器发现,于是优化掉了。而调用的时候:
从注解中反射找reties,如果没找到就取默认值2,于是就造成了一个现象,只要不设置0就没问题,0就会等同于2。上图的完整方法:
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); }
这个方法就是远程调用中会执行的Invoke链中的一个,通过:
int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1; if (len <= 0) { len = 1; }
上面这个判断可以猜到,文档中说的值为0关闭重试就是说的这句判断了。上一句中对反射得到的值进行了+1,这个+1就是加上了本身调用的一次,这里就是最大可能执行的总次数。文档中给出了一个方便人理解的值0来控制是否重试。由于一些原因只能用注解的方式,又无法使用0,这里的判断是<=0,那我们就在注解上设置-1或者更小都可以,于是结果就可以满足需要了。经过测试发现确实可以,于是问题解决的。注意,虽然问题解决了,但这个方式非常不好,只是限于一些原因只能暂时采用这种别扭的方式去做了,并不提倡。
==========================================================
咱最近用的github:https://github.com/saaavsaaa
微信公众号: