背景

在使用dubbo时,通常会遇到timeout这个属性,timeout属性的作用是:给某个服务调用设置超时时间,如果服务在设置的时间内未返回结果,则会抛出调用超时异常:TimeoutException,在使用的过程中,我们有时会对provider和consumer两个配置都会设置timeout值,那么服务调用过程中会以哪个为准?本文主要针对这个问题进行分析和扩展

三种设置方式

以provider配置为例:

方法级别

设置方式如下所示:

接口级别

全局级别

优先级选择

在dubbo中如果provider和consumer都配置了相同的一个属性,比如本文分析的timeout,其实是有一个优先级的,优先级:

consumer方法配置 > provider方法配置 > consumer接口配置 > provider接口配置 > consumer全局配置 > provider全局配置。所以对于本文开始的提出的问题就有了结果,会以消费者配置的为准,接下结合源码来进行解析,其实源码很简单,在RegistryDirectory类中将服务列表转换为DubboInvlker方法中进行了处理:

private Map> toInvokers(List urls) {

Map> newUrlInvokerMap = new HashMap>();

if (urls == null || urls.isEmpty()) {

return newUrlInvokerMap;

}

Set keys = new HashSet();

String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY);

for (URL providerUrl : urls) {

// If protocol is configured at the reference side, only the matching protocol is selected

if (queryProtocols != null && queryProtocols.length() > 0) {

boolean accept = false;

String[] acceptProtocols = queryProtocols.split(",");

for (String acceptProtocol : acceptProtocols) {

if (providerUrl.getProtocol().equals(acceptProtocol)) {

accept = true;

break;

}

}

if (!accept) {

continue;

}

}

if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {

continue;

}

if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {

logger.error(new IllegalStateException("Unsupported protocol " + providerUrl.getProtocol() +

" in notified url: " + providerUrl + " from registry " + getUrl().getAddress() +

" to consumer " + NetUtils.getLocalHost() + ", supported protocol: " +

ExtensionLoader.getExtensionLoader(Protocol.class).getSupportedExtensions()));

continue;

}

// 重点就是下面这个方法

URL url = mergeUrl(providerUrl);

String key = url.toFullString(); // The parameter urls are sorted

if (keys.contains(key)) { // Repeated url

continue;

}

keys.add(key);

// Cache key is url that does not merge with consumer side parameters, regardless of how the consumer combines parameters, if the server url changes, then refer again

Map> localUrlInvokerMap = this.urlInvokerMap; // local reference

Invoker invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);

if (invoker == null) { // Not in the cache, refer again

try {

boolean enabled = true;

if (url.hasParameter(Constants.DISABLED_KEY)) {

enabled = !url.getParameter(Constants.DISABLED_KEY, false);

} else {

enabled = url.getParameter(Constants.ENABLED_KEY, true);

}

if (enabled) {

invoker = new InvokerDelegate(protocol.refer(serviceType, url), url, providerUrl);

}

} catch (Throwable t) {

logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);

}

if (invoker != null) { // Put new invoker in cache

newUrlInvokerMap.put(key, invoker);

}

} else {

newUrlInvokerMap.put(key, invoker);

}

}

keys.clear();

return newUrlInvokerMap;

}

重点就是上面mergeUrl()方法,将provider和comsumer的url参数进行了整合,在

mergeUrl()方法有会调用ClusterUtils.mergeUrl方法进行整合,因为这个方法比较简单,就是对一些参数进行了整合了,会用consumer参数进行覆盖,咱们这里就不分析了,如果感兴趣的同学可以去研究一下。

超时处理

在配置设置了超时timeout,那么代码中是如何处理的,这里咱们在进行一下扩展,分析一下dubbo中是如何处理超时的,在调用服务方法,最后都会调用DubboInvoker.doInvoke方法,咱们就从这个方法开始分析:

@Override

protected Result doInvoke(final Invocation invocation) throws Throwable {

RpcInvocation inv = (RpcInvocation) invocation;

final String methodName = RpcUtils.getMethodName(invocation);

inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());

inv.setAttachment(Constants.VERSION_KEY, version);

ExchangeClient currentClient;

if (clients.length == 1) {

currentClient = clients[0];

} else {

currentClient = clients[index.getAndIncrement() % clients.length];

}

try {

boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);

boolean isAsyncFuture = RpcUtils.isReturnTypeFuture(inv);

boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);

int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);

if (isOneway) {

boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);

currentClient.send(inv, isSent);

RpcContext.getContext().setFuture(null);

return new RpcResult();

} else if (isAsync) {

ResponseFuture future = currentClient.request(inv, timeout);

// For compatibility

FutureAdapter futureAdapter = new FutureAdapter<>(future);

RpcContext.getContext().setFuture(futureAdapter);

Result result;

// 异步处理

if (isAsyncFuture) {

// register resultCallback, sometimes we need the async result being processed by the filter chain.

result = new AsyncRpcResult(futureAdapter, futureAdapter.getResultFuture(), false);

} else {

result = new SimpleAsyncRpcResult(futureAdapter, futureAdapter.getResultFuture(), false);

}

return result;

} else {

// 同步处理

RpcContext.getContext().setFuture(null);

return (Result) currentClient.request(inv, timeout).get();

}

} catch (TimeoutException e) {

throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);

} catch (RemotingException e) {

throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);

}

}

在这个方法中,咱们就以同步模式进行分析,看request方法,request()方法会返回一个DefaultFuture类,在去调用DefaultFuture.get()方法,这里其实涉及到一个在异步中实现同步的技巧,咱们这里不做分析,所以重点就在get()方法里:

@Override

public Object get() throws RemotingException {

return get(timeout);

}

@Override

public Object get(int timeout) throws RemotingException {

if (timeout <= 0) {

timeout = Constants.DEFAULT_TIMEOUT;

}

if (!isDone()) {

long start = System.currentTimeMillis();

lock.lock();

try {

while (!isDone()) {

done.await(timeout, TimeUnit.MILLISECONDS);

if (isDone() || System.currentTimeMillis() - start > timeout) {

break;

}

}

} catch (InterruptedException e) {

throw new RuntimeException(e);

} finally {

lock.unlock();

}

if (!isDone()) {

throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));

}

}

return returnFromResponse();

}

在调用get()方法时,会去调用get(timeout)这个方法,在这个方法中会传一个timeout字段,在和timeout就是咱们配置的那个参数,在这个方法中咱们要关注下面一个代码块:

if (!isDone()) {

long start = System.currentTimeMillis();

lock.lock();

try {

while (!isDone()) {

// 线程阻塞

done.await(timeout, TimeUnit.MILLISECONDS);

if (isDone() || System.currentTimeMillis() - start > timeout) {

break;

}

}

} catch (InterruptedException e) {

throw new RuntimeException(e);

} finally {

lock.unlock();

}

// 在超时时间里,还没有结果,则抛出超时异常

if (!isDone()) {

throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));

}

}

重点看await()方法,会进行阻塞timeout时间,如果阻塞时间到了,则会唤醒往下执行,超时跳出while循环中,判断是否有结果返回,如果没有(这个地方要注意:只有有结果返回,或超时才跳出循环中),则抛出超时异常。讲到这里,超时原理基本上其实差不多了,DefaultFuture这个类还有个地方需要注意,在初始化DefaultFuture对象时,会去创建一个超时的延迟任务,延迟时间就是timeout值,在这个延迟任务中也会调用signal()方法唤醒阻塞

调用dubbo接口返回timeout_dubbo之timeout超时分析相关推荐

  1. pycharm——python调用dubbo接口返回句柄无效(Connection dropped:socket connection error:句柄无效)

    解决方案如下: kazoo版本2.9.0问题, 重新安装2.8.0版本就ok了,安装时指定版本号 kazoo==2.8.0 解决前: 解决后:

  2. java反射调用dubbo接口

    需求:项目增加幂等 场景: 1.三个项目:a .b.c 2.a项目加幂等 3.b项目dubbo调用项目a的时候超时没有获取返回结果,增加重试机制(非立即重试,3min or 5min 后重试) 4.c ...

  3. ios请求php接口失败,laravel,php_iOS调用Laravel接口返回错误信息,laravel,php,ios - phpStudy...

    iOS调用Laravel接口返回错误信息 iOS端代码如下: AFHTTPRequestOperationManager *manage = [[AFHTTPRequestOperationManag ...

  4. java:AXIS调用webService接口,返回String类型xml,并用dom4j简单解析xml

    一.使用axis调用webService接口,返回String类型xml 1.导入axis依赖 2.直接贴代码 /*** 调用webservice接口的方法,并返回String类型的xml* @par ...

  5. java调用远程 接口_java通过url调用远程接口返回json数据

    java通过url调用远程接口返回json数据,有用户名和密码验证, 转自 https://blog.csdn.net/wanglong1990421/article/details/78815856 ...

  6. 调用dubbo接口出现多次

    2019独角兽企业重金招聘Python工程师标准>>> 调用一次dubbo接口,断点在dubbo显示三次数据,网上发现dubbo的机制问题,https://blog.csdn.net ...

  7. 调用后台接口返回报错前端隐藏提示_腾讯社交联盟广告

    开发者帮助中心 优量汇服务体系升级了,除查阅本页常见问题外,还可以通过以下渠道解决您遇到的问题 1. 实时智能客服 入口:优量汇官网.开发者平台.优量汇服务号 时间:7*24即时问答 服务内容:涵盖 ...

  8. 调用后台接口返回报错前端隐藏提示_前端异常监控解决方案研究(转)

    前端监控包括行为监控.异常监控.性能监控等,本文主要讨论异常监控.对于前端而言,和后端处于同一个监控系统中,前端有自己的监控方案,后端也有自己等监控方案,但两者并不分离,因为一个用户在操作应用过程中如 ...

  9. nodejs调用后端接口返回excel文件流,nodejs再转发前端实现下载excel

    今天在项目上遇到了一个问题,就是后端接口返回excel文件流,然后前端实现下载excel,前端下载的excel文件居然无法打开! 在网上搜索了很多对应的资料,也尝试了很多办法,还是没能解决,最后在这篇 ...

最新文章

  1. js iframe是否加载完成,加载完成执行函数
  2. php中@作用,@字符在PHP中有什么作用?
  3. Map Set list 理解
  4. 关于Oracle undostat中的2012和ORA-01555问题的自我解答
  5. TensorFlow和深度学习-无需博士学位(TensorFlow and deep learning without a PhD)
  6. python调用动态链接库传送protobuf数据。
  7. Artech的MVC4框架学习——第八章View的呈现
  8. java(14) - HashMap类
  9. 将城市按照拼音首字母进行分类
  10. 【观察】华为云:决胜拉美市场“正当时”,做出海企业的“好帮手”
  11. java calendar 设置时区_详解Java时区处理之Date,Calendar,TimeZone,SimpleDateFormat
  12. 腾讯云数据库开源再突破:TDSQL PG版查询性能提升百倍
  13. html文字自动放大缩小单位,如何css控制字体按百分比放大缩小
  14. 使用matlab显示图像的一个坑:文件名或 URL 参数必须为字符向量、uigetfile出现要串联的数组的维度不一致
  15. apriori算法的简介和改进总结
  16. 分布式查询工具-Apache Drill
  17. 宋宝华:Linux设备与驱动的手动解绑与手动绑定
  18. Linux命令yun安装nginx并使用
  19. 关于关于接口测试自动化的总结与思考接口测试自动化的总结与思考
  20. 苹果系统java小游戏_Mac自带小游戏怎么玩?

热门文章

  1. 豌豆淘喜迎三大国际品牌入驻,一分钟了解三品牌齐聚豌豆淘
  2. 券商区块链的“钱途”
  3. mysql索引的面试题
  4. 咱当爹的人, 有啥不一样
  5. python中对象的特性_python中类与对象之继承,python类和对象理解,面对对象的三大特性之...
  6. 如何使用Onyx for Mac进行系统清理?
  7. python练习题之自动售货系统:
  8. 【C#基础】C# 正则表达式
  9. 今天面了个字节拿38K出来的,真是砂纸擦屁股,给我露一手
  10. mysql update主键冲突_mysql主键重复,不报错,只更新的操作