(十四)異步調用

異步調用可以通過文件配置:

<dubbo:reference id="fooService" interface="com.alibaba.foo.FooService">
      <dubbo:method name="findFoo" async="true" />
</dubbo:reference>

在我們的業務層調用dubbo封裝的service時,dubbo底層會調用com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker<T>的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); //21處
        boolean isOneway = RpcUtils.isOneway(getUrl(), invocation); //22處
        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) ;
            RpcContext.getContext().setFuture(new FutureAdapter<Object>(future)); //23處
            return new RpcResult();
        } else {
            RpcContext.getContext().setFuture(null);
            return (Result) currentClient.request(inv, timeout).get(); //13處,調用服務的結果,同步返回結果
        }
    } 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);
    }
}

在源碼“21處”,會判斷RPC調用是不是異步調用,RpcUtils的isAsync方法:

public static boolean isAsync(URL url, Invocation inv) {
    boolean isAsync ;
    //如果Java代碼中設置優先.
    if (Boolean.TRUE.toString().equals(inv.getAttachment(Constants.ASYNC_KEY))) {
        isAsync = true;
    } else {
        isAsync = url.getMethodParameter(getMethodName(inv), Constants.ASYNC_KEY, false);
    }
    return isAsync;
}

從isAsync方法,可以看出來,除了可以在配置文件配置異步調用,還可以在代碼設置,而且代碼設置的還是優先獲取的,
可通過com.alibaba.dubbo.rpc.RpcInvocation.setAttachment(String, String)來設置異步調用。
在上面doInvoke方法的源碼中的“23處”,異步調用,會往當前線程的RpcContext對象裏設置一個Future對象,則異步調用了方法之後,再通過RpcContext.getContext().getFuture()來獲取到Future對象,而在底層,這個Future對象在設置在ThreadLocal對象裏的,則同一個線程會獲取設置好的Future對象。

fooService.findFoo(fooId);
Future<Foo> fooFuture = RpcContext.getContext().getFuture(); // 拿到調用的Future引用,當結果返回後,會被通知和設置到此Future。

barService.findBar(barId);
Future<Bar> barFuture = RpcContext.getContext().getFuture(); // 拿到調用的Future引用,當結果返回後,會被通知和設置到此Future。

// 此時findFoo和findBar的請求同時在執行,客戶端不需要啓動多線程來支持並行,而是藉助NIO的非阻塞完成。

Foo foo = fooFuture.get(); // 如果foo已返回,直接拿到返回值,否則線程wait住,等待foo返回後,線程會被notify喚醒。
Bar bar = barFuture.get(); // 同理等待bar返回。

// 如果foo需要5秒返回,bar需要6秒返回,實際只需等6秒,即可獲取到foo和bar,進行接下來的處理。

此外,dubbo還可以通過配置異步,不用等待調用的返回值,可以配置return="false"
<dubbo:method name="findFoo" async="true" return="false" />
看上面源碼的“22處”的RpcUtils.isOneway方法:

public static boolean isOneway(URL url, Invocation inv) {
    boolean isOneway ;
    //如果Java代碼中設置優先.
    if (Boolean.FALSE.toString().equals(inv.getAttachment(Constants.RETURN_KEY))) {
        isOneway = true;
    } else {
        isOneway = ! url.getMethodParameter(getMethodName(inv), Constants.RETURN_KEY, true);
    }
    return isOneway;
}

<dubbo:method/>標籤中的return參數,跟async參數類似,除了可以在配置文件配置,也可以在代碼中設置,並且代碼優先級別高。
可通過com.alibaba.dubbo.rpc.RpcInvocation.setAttachment(String, String)來設置異步調用。

另外
還可以設置是否等待消息發出:(異步總是不等待返回)

    sent="true" 等待消息發出,消息發送失敗將拋出異常。
    sent="false" 不等待消息發出,將消息放入IO隊列,即刻返回。

<dubbo:method name="findFoo" async="true" sent="true" />
以netty作爲傳輸來看看NettyChannel類源碼:

public void send(Object message, boolean sent) throws RemotingException {
    super.send(message, sent);
    
    boolean success = true;
    int timeout = 0;
    try {
        ChannelFuture future = channel.write(message); //31處
        if (sent) {
            timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
            success = future.await(timeout);
        }
        Throwable cause = future.getCause();
        if (cause != null) {
            throw cause;
        }
    } catch (Throwable e) {
        throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress() + ", cause: " + e.getMessage(), e);
    }
    
    if(! success) {
        throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress()
                + "in timeout(" + timeout + "ms) limit");
    }
}

這是源碼“31處”方法org.jboss.netty.channel.Channel.write(Object message)的註釋:

/**
 * Sends a message to this channel asynchronously.    If this channel was
 * created by a connectionless transport (e.g. {@link DatagramChannel})
 * and is not connected yet, you have to call {@link #write(Object, SocketAddress)}
 * instead.  Otherwise, the write request will fail with
 * {@link NotYetConnectedException} and an {@code 'exceptionCaught'} event
 * will be triggered.
 *
 * @param message the message to write
 *
 * @return the {@link ChannelFuture} which will be notified when the
 *         write request succeeds or fails
 *
 * @throws NullPointerException if the specified message is {@code null}
 */
ChannelFuture write(Object message);

可以知道是異步發送消息的,返回一個ChannelFuture對象給發送端,如果消息異步發送成功或者失敗都會通知ChannelFuture對象的。
所以如果我們設置send="false",不用等待發送的結果,只有send值爲true,纔會執行future.await(timeout)方法阻塞等待返回結果。

自己寫了個RPC:

https://github.com/nytta

可以給個star,^0^.

發佈了184 篇原創文章 · 獲贊 60 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章