异步调用可以通过文件配置:
<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:
可以给个star,^0^.