dubbo默認使用同步的方式調用。但在有些特殊的場景下,我們可能希望異步調用dubbo接口,從而避免不必要的等待時間,這時候我們就需要用到異步。那么dubbo的異步是如何實現的呢?下面就來看看這個問題
異步方法配置:
<dubbo:reference cache="lru" id="demoService" interface="com.ping.chen.dubbo.service.DemoService" timeout="5000">
<dubbo:method name="sayHello" async="true"/>
</dubbo:reference>
底層的異步處理實現在DubboInvoker的doInvoke方法中,源碼如下:
protected Result doInvoke(final Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
忽略Attachment設置。。。
try {
// 是否異步執行
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);//是否單向執行
int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);//接口超時時間,默認1秒
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));
return new RpcResult();
} else {
RpcContext.getContext().setFuture(null);
return (Result) currentClient.request(inv, timeout).get();
}
} catch (TimeoutException e) {
//異常處理。。。
}
}
可以看到,如果異步執行,會直接返回一個空的RpcResult,然后用戶如果需要異步執行結果,可以從RpcContext中的Future中去獲取,直接用RpcContext.getContext().getFuture().get();就可以獲取到執行結果。那么RpcContext是如何保證當前線程可以拿到執行結果呢?答案是ThreadLocal。我們來看看RpcContext源碼如下:
public class RpcContext {
private static final ThreadLocal<RpcContext> LOCAL = new ThreadLocal<RpcContext>() {
@Override
protected RpcContext initialValue() {
return new RpcContext();
}
};
public static RpcContext getContext() {
return LOCAL.get();
}
。。。。。。
}
可以看到RpcContext 內部是使用ThreadLocal來實現的。
異步可能遇到的坑
上面這種配置有一個坑,可能你會像下面這樣使用異步:
@RestController
public class DemoController {
@Reference(timeout = 5000)
DemoProvider demoProvider;
@RequestMapping("/demo")
public void demo() throws ExecutionException, InterruptedException {
demoProvider.sayHello("world");
System.out.println(">>>>>>>>>>" + RpcContext.getContext().getFuture().get());
}
}
然后請求demo接口的時候,很不幸,你將會收到一個NullPointException,這因為我們只在消費者端配置了異步執行,但服務端執行的時候是同步的,所以我們從RpcContext中獲取到的Future是空的,正確的異步配置應該是:
直接去掉消費者端的
<dubbo:method name="sayHello" async="true"/>
配置,然后在服務提供者端做如下配置:
<!-- 聲明需要暴露的服務接口 -->
<dubbo:service interface="com.ping.chen.dubbo.provider.DemoProvider" ref="demoProvider" >
<dubbo:method name="sayHello" async="true"/>
</dubbo:service>
如此,便可以實現異步了