dubbo超时机制

343 阅读3分钟

说道dubbo的超时机制的话,得先从dubbo支持的3种调用方式说起: 第一种:同步调用 第二种:异步调用 第三种:oneway

我们重点说下同步调用 同步其实就是一直等待结果的返回,注意如果provide和consumer端都没有设置超时时间的话,会默认设置为1000ms,

dubbo是如何实现等待超时的这个机制的呢?(注意:使用的协议是dubbo协议) 我们可以通过追踪源码发现,在进行了远程调用的时候, 会执行到DubboInvoker.doInvoke(Invocation invocation)方法:

    同步调用的部分代码片段如下:
    protected Result doInvoke(Invocation invocation) throws Throwable {
        ..... 此处省略大部分代码

        RpcContext.getContext().setFuture((Future)null);
        return (Result)currentClient.request(inv, timeout).get();
    }

会返回一个DefaulteFuture的实例对象,然后同步调用get方法。

dubbo的超时的实现其实就是通过get方法来实现的,我们可以进入到源码看到,最终会调用到get(int timeout)的方法。

在这个逻辑里面做的处理其实包括
1:如果没有设置超时时间,就默认设置为1000ms
2:记录服务调用开始时间
3:通过reentrantlock加锁
    Lock lock = new ReentrantLock();
    lock.lock();
4:如果response未返回(也就是为null的时候)执行第5步,否则执行第85:通过reentrantlock的Condition竞态对象进行阻塞等待
    Condition done = this.lock.newCondition();
    done.await((long)timeout, TimeUnit.MILLISECONDS);
6:如果返回了结果或者超时(等待结束时间 - 开始时间 > timeout),退出循环判断,然后释放锁
    if (this.isDone() || System.currentTimeMillis() - start > (long)timeout) {
        break;
    }
7:未返回结果然后超时的情况,直接抛出异常
8:通过this.returnFromResponse()返回具体的结果。
    
    private Object returnFromResponse() throws RemotingException {
        Response res = this.response;
        if (res == null) {
            throw new IllegalStateException("response cannot be null");
        } else if (res.getStatus() == 20) {
            return res.getResult();
        } else if (res.getStatus() != Response.SERVER_TIMEOUT && res.getStatus() != Response.CLIENT_TIMEOUT)) {
            throw new RemotingException(this.channel, res.getErrorMessage());
        } else {
            throw new TimeoutException(res.getStatus() == 31, this.channel, res.getErrorMessage());
        }
    }
    

对于returnFromResponse()这个方法中能通过不同status来判断超时的解读,我们需要看另外的一个逻辑,

我们再细看DefaultFuture类,其实在加载类的时候,有一个静态代码块:
static {
    Thread th = new Thread(new DefaultFuture.RemotingInvocationTimeoutScan(), "DubboResponseTimeoutScanTimer");
    th.setDaemon(true);
    th.start();
}
这个静态代码块做的事情就是启动了一个守护线程,这个守护线程主要做的事情是什么呢?
其实就是不断的遍历DefaultFuture.FUTURES这个静态常量,
DefaultFuture.FUTURES的作用是用来保存dubbo的RPC调用中,request.id和Channel的关系。
1:通过channel判断是否已经返回,并且是否已经超时,以此来对超时的请求设置一个特定的超时Response实例。

2:并且status设置为Response.SERVER_TIMEOUT 或者 Response.CLIENT_TIMEOUT,
       // 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));
3:设置response结果并且唤醒同步调用过程中调用get接口导致阻塞的流程
    this.done.signal();
    以及释放锁
    
通过分析这个守护线程,可以知道returnFromResponse方法中,response有值了,还要通过判断status的来处理是否超时。就是因为超时的时候会设置一个超时的特殊response

以上的过期策略是旧版本dubbo的实现,新版本的dubbo的实现改为时间论的方式,具体得看各自工程中使用的版本为准。