dubbo的超时分为服务端超时 SERVER_TIMEOUT 和客户端超时 CLIENT_TIMEOUT。本文讨论服务端超时的情形:

超时:consumer发送调用请求后,等待服务端的响应,若超过timeout时间仍未收到响应,则抛异常。

dubbo consumer 超时重试的逻辑在 FailoverClusterInvoker.doInvoke 中:

public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers,
LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> copyinvokers = invokers;
checkInvokers(copyinvokers, invocation);
//取retries参数值,默认值为2,所以len默认为3
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 {
       //继续invoker链的调用
Result result = invoker.invoke(invocation);
if (le != null && logger.isWarnEnabled()) {
//打印日志:上次调用产生的异常
logger.warn("Although retry the method XXX");
}
//调用成功,即返回。如果产生RpcException异常,进入catch块,设置le。
return result;
} catch (RpcException e) {
//在DubboInvoker.doInvoke中会把TimeoutException封装成RpcException
//所以超时异常会进入这个catch分支,开始for循环的下一次调用
if (e.isBiz()) { // biz exception.
throw e;
}
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
providers.add(invoker.getUrl().getAddress());
}
} // retry loop.
//调用len次后,仍然没有结果,则抛异常。
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);
}

当invoker的调用链进行到DubboInvoker.doInvoke时:

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 isOneway = RpcUtils.isOneway(getUrl(), invocation);
int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY,
                                 Constants.DEFAULT_TIMEOUT);
if (isOneway) {
//oneway的意思是:consumer不需要调用结果。需要配置return="false"
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
RpcContext.getContext().setFuture(null);
return new RpcResult();
} else if (isAsync) {
//如果consumer需要调用结果,但又不想阻塞程序,则设置return="true", async="true"
ResponseFuture future = currentClient.request(inv, timeout);
//在RpcContext中设置Future,返回空的RpcResult
RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
return new RpcResult();
} else {
//如果consumer想阻塞获取provider的调用结果,不需要做配置,默认即可。
RpcContext.getContext().setFuture(null);
//currentClient.request会发送请求,返回Future。调用Future.get导致阻塞
return (Result) currentClient.request(inv, timeout).get();
}
} catch (TimeoutException e) {
//调用超时,将TimeoutException封装成RpcException。
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);
}
}

currentClient.request(inv, timeout).get(); 会阻塞等待响应,超时则会抛出异常。

// HeaderExchangeChannel.request
public ResponseFuture request(Object request, int timeout) throws RemotingException {
if (closed) {
throw new RemotingException(this.getLocalAddress(), null,
          "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
}
// create request.
Request req = new Request();
req.setVersion("2.0.0");
req.setTwoWay(true);
req.setData(request);
//设置超时
DefaultFuture future = new DefaultFuture(channel, req, timeout);
try{
channel.send(req);
}catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
} // DefaultFuture.get(int timeout)
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()) {
         //(1)await超时醒来,但是未收到响应:则isDone为false,但是System.currentTimeMillis() - start > timeout 为true
//(2)provider及时响应。更具体的说法是等待DubboClientHandler线程接收响应后,唤醒该线程。isDone会设置为true
//(3)RemotingInvocationTimeoutScan线程扫描到超时,然后创建一个超时响应,并唤醒这个等待。isDone被设置为true
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();
}

在DefaultFuture类中有一个内部类RemotingInvocationTimeoutScan,负责扫描超时的调用,在客户端构造超时响应。

private static class RemotingInvocationTimeoutScan implements Runnable {

    public void run() {
while (true) {
try {
for (DefaultFuture future : FUTURES.values()) {
if (future == null || future.isDone()) {
continue;
}
if (System.currentTimeMillis() - future.getStartTimestamp() > future.getTimeout()) {
// create exception response.
Response timeoutResponse = new Response(future.getId());
// set timeout status.
timeoutResponse.setStatus(future.isSent() ? Response.SERVER_TIMEOUT : Response.CLIENT_TIMEOUT);
timeoutResponse.setErrorMessage(future.getTimeoutMessage(true));
// handle response.
DefaultFuture.received(future.getChannel(), timeoutResponse);
}
}
Thread.sleep(30);
} catch (Throwable e) {
logger.error("Exception when scan the timeout invocation of remoting.", e);
}
}
}
} static {
Thread th = new Thread(new RemotingInvocationTimeoutScan(), "DubboResponseTimeoutScanTimer");
th.setDaemon(true);
th.start();
}

dubbo的超时重试的更多相关文章

  1. Dubbo超时重试机制带来的数据重复问题

    Dubbo的超时重试机制为服务容错.服务稳定提供了比较好的框架支持,但是在一些比较特殊的网络环境下(网络传输慢,并发多)可能 由于服务响应慢,Dubbo自身的超时重试机制(服务端的处理时间超过了设定的 ...

  2. dobbo 服务配置详解(解决超时重试问题)

    <!-- reference method -->     <dubbo:reference interface="com.xx.XxxService">  ...

  3. dubbo超时重试和异常处理

    dubbo超时重试和异常处理 dubbo超时重试和异常处理 参考: https://www.cnblogs.com/ASPNET2008/p/7292472.html https://www.tuic ...

  4. 5.如何基于 dubbo 进行服务治理、服务降级、失败重试以及超时重试?

    作者:中华石杉 面试题 如何基于 dubbo 进行服务治理.服务降级.失败重试以及超时重试? 面试官心理分析 服务治理,这个问题如果问你,其实就是看看你有没有服务治理的思想,因为这个是做过复杂微服务的 ...

  5. 面试系列26 如何基于dubbo进行服务治理、服务降级、失败重试以及超时重试

    (1)服务治理 1)调用链路自动生成 一个大型的分布式系统,或者说是用现在流行的微服务架构来说吧,分布式系统由大量的服务组成.那么这些服务之间互相是如何调用的?调用链路是啥?说实话,几乎到后面没人搞的 ...

  6. Volley超时重试机制

    基础用法 Volley为开发者提供了可配置的超时重试机制,我们在使用时只需要为我们的Request设置自定义的RetryPolicy即可. 参考设置代码如下: int DEFAULT_TIMEOUT_ ...

  7. 超时重试(一)ajax

    我们使用jquery的ajax,超时重试可以采用两种方式,一种是配置ajax的timeout的参数,另一种就是以setTimeout定时器的方式实现: 1)timeout参数配置方式 var xhr ...

  8. SpringCloud Feign 之 超时重试次数探究

    SpringCloud Feign 之 超时重试次数探究 上篇文章,我们对Feign的fallback有一个初步的体验,在这里我们回顾一下,Fallback主要是用来解决依赖的服务不可用或者调用服务失 ...

  9. 使用 Polly 实现复杂策略(超时重试)

    一.背景 第一次接触 Polly 还是在做某个微服务系统的时候,那时只会使用单一的超时策略与重试策略,更加高级的特性就没有再进行学习了.最近开为某个客户开发 PC 端的上位机的时候,客户有个需求,在发 ...

随机推荐

  1. 关于mysql连接抛出10038错误问题

    今天用Navicat Premium连接windows server 2003 mysql的时候, 抛出10038问题, 这种问题之前在rhel也出现过一次, 就是防火墙不允许连接kill掉了这个请求 ...

  2. 20145334赵文豪《网络攻防》 MSF基础应用

    实践目标 掌握metasploit的基本应用方式 掌握常用的三种攻击方式的思路. 实验要求 一个主动攻击,如ms08_067 一个针对浏览器的攻击,如ms11_050 一个针对客户端的攻击,如Adob ...

  3. CSS实现三角形、梯形、平行四边形、圆形、椭圆形、对话框、自适应正方形

    本文篇幅较长,希望能坚持看完,转载请注明出处,如果觉得好文请给个赞吧 CSS实现梯形 CSS实现三角形和梯形主要是依靠border是梯形的特性来做的,有点像相框的那种感觉. 首先我们先给一个正方形设置 ...

  4. MFC制作OCX

    1.新建工程 注意选择显示时注册,免得后面又去手动注册 2.工程解释,一般ocx是看类视图而不是解决方案 ①.xxxApp:类似整个工程的入口,有xxxApp.h和xxxApp.cpp,工程的初始化, ...

  5. Python3基础 str swapcase 英文字母大小写反转

             Python : 3.7.0          OS : Ubuntu 18.04.1 LTS         IDE : PyCharm 2018.2.4       Conda ...

  6. UVA 10462 Is There A Second Way Left?(次小生成树&Prim&Kruskal)题解

    思路: Prim: 这道题目中有重边 Prim可以先加一个sec数组来保存重边的次小边,这样不会影响到最小生成树,在算次小生成树时要同时判断次小边(不需判断是否在MST中) Kruskal: Krus ...

  7. acm模板生成

    为迎接,接下来的区域赛,要做好准备(虽然不是特别有信心,但是还是要鼓励自己,可以取得收获的,加油) acm_latex模板: https://www.cnblogs.com/palayutm/p/64 ...

  8. BZOJ2724 [Violet]蒲公英 分块

    题目描述 经典区间众数题目 然而是权限题,所以题目链接放Luogu的 题解 因为太菜所以只会$O(n*\sqrt{n}+n*\sqrt{n}*log(n))$的做法 就是那种要用二分的,并不会clj那 ...

  9. [BZOJ1103][POI2007]大都市meg dfs序+树状数组

    Description 在经济全球化浪潮的影响下,习惯于漫步在清晨的乡间小路的邮递员Blue Mary也开始骑着摩托车传递邮件了.不过,她经常回忆起以前在乡间漫步的情景.昔日,乡下有依次编号为1..n ...

  10. 如何上传本地文件到github又如何删除自己的github仓库

    首先自己在https://github.com/网站要注册一个账户 自己上传工程到jithub,没有付费的用户只能选用public,意味着你的项目在全网是可以被看到和下载的: 所以涉及私密信息的,需要 ...