异步调用
基于NIO的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。
并行发起多个请求,但只使用一个线程
服务端同步客户端异步无返回
服务接口
定义写日志接口:
public interface AsyncService {
public void asyncWriteLog(String log);
}
服务提供者
在服务方模拟实现接口(仅为demo无任何实际意义):
public class AsyncServiceImpl implements AsyncService {
private static final Logger LOGGER = LoggerFactory.getLogger(AsyncServiceImpl.class);
@Override
public void asyncWriteLog(String log) {
LOGGER.info(log);
}
}
用Spring配置声明暴露异步写日志服务:
<dubbo:service interface="qunar.tc.dubbo.demo.api.AsyncService" ref="asyncService" version="1.0" timeout="3000" />
<bean id="asyncService" class="qunar.tc.demo.provider.AsyncServiceImpl"></bean>
服务消费者
通过Spring配置引用远程服务,设置写日志方法为异步方法:
<bean id="asyncAction" class="qunar.tc.demo.consumer.AsyncAction"></bean>
<dubbo:reference id="asyncService" interface="qunar.tc.dubbo.demo.api.AsyncService" version="1.0" check="false">
<dubbo:method name="asyncWriteLog" async="true" return="false"/>
</dubbo:reference>
异步调用写日志服务:
public class AsyncAction {
private AsyncService asyncService;
public void setAsyncService(AsyncService asyncService) {
this.asyncService = asyncService;
}
public void log(String log) {
//异步写日志,非阻塞
asyncService.asyncWriteLog("this is a test log");
}
}
服务端同步客户端异步使用Future返回
服务接口
定义从hbase中读取消息信息接口:
public interface AsyncService {
public List<Message> asyncSearchFromHbase(MessageQuery query);
}
服务提供者
在服务方模拟实现接口(仅为demo无任何实际意义):
public class AsyncServiceImpl implements AsyncService {
@Override
public List<Message> asyncSearchFromHbase(MessageQuery query) {
Preconditions.checkNotNull(query,"query should not be null");
List<Message> messages = new ArrayList<Message>();
for (int i = 0; i < 10; i++) {
messages.add(new Message(query.getSubject(), String.valueOf(i)));
}
return messages;
}
}
服务消费者
通过Spring配置引用远程服务,设置从hbase查询消息为异步方法:
<bean id="asyncAction" class="qunar.tc.demo.consumer.AsyncAction"></bean>
<dubbo:reference id="asyncService" interface="qunar.tc.dubbo.demo.api.AsyncService" version="1.0" check="false">
<dubbo:method name="asyncSearchFromHbase" async="true" />
</dubbo:reference>
异步调用从hbase查询消息服务:
如果查询消息记录是由mysql和hbase的结果集合并组成,同步执行两个查询方法,耗费时间为ts_mysql+ts_hbase。如果同步查询mysql,异步查询hbase,耗费时间为max(ts_mysql,ts_hbase),这样能节约时间。
public class AsyncAction {
private static final Logger LOGGER = LoggerFactory.getLogger(AsyncAction.class);
private AsyncService asyncService;
public void setAsyncService(AsyncService asyncService) {
this.asyncService = asyncService;
}
public List<Message> findMessages(MessageQuery query) {
// 此调用会立即返回null
asyncService.asyncSearchFromHbase(query);
// 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future
Future<List<Message>> future = RpcContext.getContext().getFuture();
List<Message> messagesFormMysql = findMessagesFromMysql(query);
try {
//如果asyncSearchFromHbase已返回,直接拿到返回值,否则线程wait住,等待asyncSearchFromHbase返回后,线程会被notify唤醒。
List<Message> messagesFormHbase=future.get();
messagesFormMysql.addAll(messagesFormHbase);
} catch (Exception e) {
LOGGER.error("search messages from hbase fail:", e);
}
return messagesFormMysql;
}
}
服务端异步客户端异步回调
在原版的dubbo中,如果服务端执行非常耗时的操作有可能会造成服务端线程池爆满的情况,解决这种问题的一个办法就是服务端回调。服务端接到请求后开始异步处理,这个时候请求立即返回,在异步处理完成的时候再调用客户端传递过来的回调将结果写回到客户端,在执行回调的时候客户端和服务端的角色发生互换,这种方式虽然解决了线程池爆满问题,但也引入了其他一些问题,比如很难编写自动化测试,需要自己维护调用上下文等。我们在新版本的dubbo中提供了服务端异步的方式。使用这种方式后对于客户端来说和原来的同步服务无异。
服务接口
服务接口与普通的服务没有什么差别
public interface AsyncService {
String sayHello();
}
服务提供者
public class AsyncServiceImpl implements AsyncService {
private static final Logger LOGGER = LoggerFactory.getLogger(AsyncServiceImpl.class);
@Override
public String asyncBatchSendMessage() {
//拿到上下文
RpcContext context = RpcContext.getContext();
final SettableFuture<String> result = SettableFuture.create();
//开启一个线程模拟异步任务,举例
new Thread(new Runnable(){
@Override
public void run(){
try{
//模拟耗时操作
Thread.sleep(3000L);
}catch(Exception e){}
//任务处理完成,将结果写回客户端
result.set("done");
}
}).start();
//设置到这个future的对象必须从com.alibaba.dubbo.remoting.exchange.support.ListenableFuture派生
context.setFuture(result);
return null;
}
}
public class SettableFuture<V> extends com.alibaba.dubbo.remoting.exchange.support.ListenableFuture<V> {
private V t;
private volatile boolean done;
public SettableFuture() {
}
public SetableFuture(V t) {
this.t = t;
this.done = true;
}
public void set(V t) {
this.t = t;
this.done = true;
trigger();
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return done;
}
@Override
public V get() throws InterruptedException, ExecutionException {
return t;
}
@Override
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return get();
}
public static <T> SettableFuture<T> create() {
return new SettableFuture<T>();
}
public static <T> SettableFuture<T> create(T value) {
return new SettableFuture<T>(value);
}
}
服务消费者
通过Spring配置引用远程服务,设置批量发消息为异步方法,且设置消息发送完毕和异常时的事件处理方法:
<bean id="asyncAction"></bean>
<dubbo:reference id="asyncService" interface="qunar.tc.dubbo.demo.api.AsyncService" version="1.0" check="false">
<dubbo:method name="asyncBatchSendMessage" async="true" onreturn="asyncAction.onSuccess" onthrow="asyncAction.onError" />
</dubbo:reference>
异步调用批量发消息服务,并在消息发送完毕和抛出异常时做相应处理(仅为demo无任何实际意义):
public class AsyncAction {
private static final Logger LOGGER = LoggerFactory.getLogger(AsyncAction.class);
private AsyncService asyncService;
public void setAsyncService(AsyncService asyncService) {
this.asyncService = asyncService;
}
public void asyncBatchSendMessage(List<Message> messages) {
asyncService.asyncBatchSendMessage(messages);
}
public void onSuccess(Map<String, Exception> result, List<Message> messages) {
LOGGER.info("send messages finish : {}", result);
}
public void onError(Throwable e, List<Message> messages) {
LOGGER.error("send messages fail : ", e);
}
}