(十四)异步调用

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

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在使用 Feign 进行异步调用时,你可以通过以下步骤来实现: 1. 配置 Feign 客户端:在你的 Spring Boot 项目中,添加 Feign 的依赖,并在配置类上加上 `@EnableFeignClients` 注解,以启用 Feign 客户端。 2. 创建 Feign 接口:定义一个接口,用于声明需要调用的远程服务的方法。你可以使用 `@FeignClient` 注解来指定所要调用的服务的名称,并使用 `@RequestMapping` 或其他相关注解来定义接口的请求路径和参数。 3. 实现异步调用:在你的 Feign 接口中,可以使用 `@Async` 注解将方法标记为异步调用。然后,你可以在方法体内使用 Spring 的异步支持(如 `CompletableFuture`)来处理异步任务。 以下是一个简单的示例: ```java @FeignClient(name = "remote-service") public interface RemoteServiceClient { @Async @GetMapping("/api/async") CompletableFuture<String> getAsyncData(); } ``` 在上面的示例中,`RemoteServiceClient` 接口定义了一个异步调用的方法 `getAsyncData()`,它将返回一个 `CompletableFuture` 对象,表示异步任务的结果。通过 `@FeignClient` 注解指定了需要调用的远程服务的名称。 然后,在你的业务代码中,你可以通过调用 `getAsyncData()` 方法来发起异步调用,并使用 `CompletableFuture` 来处理异步任务的结果。 需要注意的是,为了使 Feign 客户端支持异步调用,你还需要在项目的配置文件中增加以下配置: ```properties feign.hystrix.enabled=false ``` 这样,Feign 客户端就能够支持异步调用了。希望这能帮到你!如果还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值