(十四)异步调用

异步调用可以通过文件配置:

<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^.

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值