Async Dubbo

异步调用

基于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>
<dubbo:method> 方法级控制
@async 是否异步执行,不可靠异步,只是忽略返回值,不阻塞执行线程
@return 方法调用是否需要返回值,async设置为true时才生效,如果设置为true,则返回future,或回调onreturn等方法,如果设置为false,则请求发送成功后直接返回Null,用于忽略返回值的场景,可以减少Future对象的创建和管理成本

 
异步调用写日志服务:

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);
}
服务参数及返回值建议使用POJO对象并实现Serializable接口,即通过set,get方法表示属性的对象。MessageQuery和Message必须实现Serializable接口。
不建议使用过于抽象的通用接口,如:Map query(Map),这样的接口没有明确语义,会给后期维护带来不便。
服务参数及返回值不能自定义实现List, Map, Number, Date, Calendar等接口,只能用JDK自带的实现,因为hessian会做特殊处理,自定义实现类中的属性值都会丢失。返回值List不能使用guava的Lists.newArrayList()来创建
Hessian序列化,只传成员属性值和值的类型,不传方法或静态变量

服务提供者

在服务方模拟实现接口(仅为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;
    }

}
编程模型:
1.调用远程服务,不需要接收返回值。xxxService.xxxMethod();
2.同步执行业务方法。
3.异步获得服务结果。RpcContext.getContext().getFuture().get()
4.结果合并处理

服务端异步客户端异步回调

在原版的dubbo中,如果服务端执行非常耗时的操作有可能会造成服务端线程池爆满的情况,解决这种问题的一个办法就是服务端回调。服务端接到请求后开始异步处理,这个时候请求立即返回,在异步处理完成的时候再调用客户端传递过来的回调将结果写回到客户端,在执行回调的时候客户端和服务端的角色发生互换,这种方式虽然解决了线程池爆满问题,但也引入了其他一些问题,比如很难编写自动化测试,需要自己维护调用上下文等。我们在新版本的dubbo中提供了服务端异步的方式。使用这种方式后对于客户端来说和原来的同步服务无异。

服务接口

服务接口与普通的服务没有什么差别

public interface AsyncService {
    String sayHello();
}

服务提供者

AsyncServiceImpl.java
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;
    }
}
SettableFuture.java
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>&nbsp;
<dubbo:reference id="asyncService" interface="qunar.tc.dubbo.demo.api.AsyncService" version="1.0" check="false">&nbsp;
<dubbo:method name="asyncBatchSendMessage" async="true" onreturn="asyncAction.onSuccess" onthrow="asyncAction.onError" />&nbsp;
</dubbo:reference>
 
*<dubbo:method>* 方法级控制 
*@async* 是否异步执行,不可靠异步,只是忽略返回值,不阻塞执行线程 
*@return* 方法调用是否需要返回值,async设置为true时才生效,如果设置为true,则返回future,或回调onreturn等方法,如果设置为false,则请求发送成功后直接返回Null,用于忽略返回值的场景,可以减少Future对象的创建和管理成本 
*@oninvoke* 方法执行前拦截 
*@onreturn* 方法执行返回后拦截 
*@onthrow* 方法执行有异常拦截 

 
异步调用批量发消息服务,并在消息发送完毕和抛出异常时做相应处理(仅为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);
	}
}
在调用之前,调用之后,出现异常时,会触发oninvoke, onreturn, onthrow三个事件,可以配置当事件发生时,通知哪个类的哪个方法。 
onreturn方法第一个参数表示服务返回值,第二个参数表示服务实参 
onthrow方法第一个参数表示服务抛出异常,第二个参数表示服务实参
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值