原文作者:liuhmmjj

同步调用

同步调用是一种阻塞式的调用方式,即 Consumer 端代码一直阻塞等待,直到 Provider 端返回为止;

dubbo默认的协议是netty, Netty 是NIO 异步通讯机制,那么服务调用是怎么转化为同步的呢?

下面看源码:

省略一部分调用链,最终会来到这里 DubboInvoker




SpringBoot 远程调用 dubbo超时时间配置_dubbo源码


protected Result doInvoke(final Invocation invocation) throws Throwable {     RpcInvocation inv = (RpcInvocation)invocation;        ...        try {        boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);        boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);        int timeout = getUrl().getMethodParameter(methodName, "timeout", 1000);                //忽略返回值        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);            RpcContext.getContext().setFuture(new FutureAdapter(future));            return new RpcResult();        //同步调用        } else {            RpcContext.getContext().setFuture(null);            return (Result) currentClient.request(inv, timeout).get();        }    }}

接着我们看同步调用部分,(Result) currentClient.request(inv, timeout).get();

关于上面这句代码,它包含两个动作:先调用currentClient.request方法,通过Netty发送请求数据;然后调用其返回值的get方法,来获取返回值。

1、发送请求

这一步主要是将请求方法封装成Request对象,通过Netty将数据发送到服务端,然后返回一个DefaultFuture对象。 com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeChannel的Request方法:

public ResponseFuture request(Object request, int timeout) throws RemotingException {     //如果客户端已断开连接    if (closed) {        throw new RemotingException(".......");    }    //封装请求信息    Request req = new Request();    req.setVersion("2.0.0");    req.setTwoWay(true);    req.setData(request);        //构建DefaultFuture对象    DefaultFuture future = new DefaultFuture(channel, req, timeout);    try {        //通过Netty发送网络数据        channel.send(req);    } catch (RemotingException e) {        future.cancel();        throw e;    }    return future;}

如上代码,逻辑很清晰。关于看它的返回值是一个DefaultFuture对象,我们再看它的构造方法。

public DefaultFuture(Channel channel, Request request, int timeout) {    this.channel = channel;    this.request = request;    this.id = request.getId();    this.timeout = timeout > 0 ? timeout :                 channel.getUrl().getPositiveParameter("timeout", 1000);    //当前Future和请求信息的映射    FUTURES.put(id, this);    //当前Channel和请求信息的映射    CHANNELS.put(id, channel);}

同时类加载的时候会启动一个超时扫描线程:

static {    Thread th = new Thread(new RemotingInvocationTimeoutScan(), "DubboResponseTimeoutScanTimer");    th.setDaemon(true);    th.start();}

看看线程扫描啥:

private static class RemotingInvocationTimeoutScan implements Runnable {         @Override        public void run() {            while (true) {                try {                    // 就是去扫描DefaultFuture列表                    for (DefaultFuture future : FUTURES.values()) {                        if (future == null || future.isDone()) {                            continue;                        }                        // 如果future未完成,且超时                        if (System.currentTimeMillis() - future.getStartTimestamp() > future.getTimeout()) {                            // 创建一个异常的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));                            // 处理异常                            DefaultFuture.received(future.getChannel(), timeoutResponse);                        }                    }                    Thread.sleep(30);                } catch (Throwable e) {                    logger.error("Exception when scan the timeout invocation of remoting.", e);                }            }        }    }

2、获取返回值

我们接着看get方法。

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();} //获取返回值responseprivate Object returnFromResponse() throws RemotingException {    Response res = response;    if (res == null) {        throw new IllegalStateException("response cannot be null");    }    // 正常返回,返回 Result 对象    if (res.getStatus() == Response.OK) {        return res.getResult();    }    // 超时处理    if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {        // 重新抛出异常        throw new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage());    }    throw new RemotingException(channel, res.getErrorMessage());}

其中的isDone()方法为:

public boolean isDone() {        return this.response != null;    }

我们总结下它的运行流程:

  • 判断超时时间,小于0则设置默认值
  • 判断操作是否已完成,即response是否为空;如果已完成,获取返回值,并返回
  • 如果操作未完成,加锁、等待;获得通知后,再次判断操作是否完成。若完成,获取返回值,并返回。

其中等待用的是 done.await(timeout, TimeUnit.MILLISECONDS);

done为private final Condition done;

因此调用的是Condition的await方法,会释放锁和资源,singl方法会将其唤醒。

get方法后面的returnFromResponse()方法可以看到超时处理,也就是之前那个超时扫描线程对status进行赋值后,returnFromResponse()方法里面对超时扫描线程赋值的status值进行判断是否超时,如果超时就抛出TimeoutException异常

response在哪里被赋值、await在哪里被通知?

在Netty读取到网络数据后,其中会调用到HeaderExchangeHandler中的方法,我们来看一眼就明白了。

public class HeaderExchangeHandler implements ChannelHandlerDelegate {        //处理返回信息    static void handleResponse(Channel channel, Response response) throws RemotingException {        if (response != null && !response.isHeartbeat()) {            DefaultFuture.received(channel, response);        }    }}

如果response 不为空,并且不是心跳数据,就调用DefaultFuture.received,在这个方法里面,主要就是根据返回信息的ID找到对应的Future,然后通知。

public static void received(Channel channel, Response response)         try {        //根据返回信息中的ID找到对应的Future        DefaultFuture future = FUTURES.remove(response.getId());        if (future != null) {            //通知方法            future.doReceived(response);        } else {            logger.warn("......");        }    } finally {        //处理完成,删除Future        CHANNELS.remove(response.getId());    }}

future.doReceived(response);就很简单了,它就回答了我们上面的那两个小问题。赋值response和await通知。

private void doReceived(Response res) {    lock.lock();    try {        //赋值response        response = res;        if (done != null) {            //通知方法            done.signal();        }    } finally {        lock.unlock();    }    if (callback != null) {        invokeCallback(callback);    }}

异步调用的话用到了RpcContext.

RpcContext是Dubbo中的一个上下文信息,它是一个 ThreadLocal 的临时状态记录器