異步調用可以通過文件配置:
<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^.