C/C++教程

从浅入深来看RPC框架中同步和异步调用

本文主要是介绍从浅入深来看RPC框架中同步和异步调用,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
同步调用就不多说了,先来看看异步调用的好处:
使用异步调用可以不用一直等待一个方法执行完成,可以同时调用多个方法,大多数情况下对于无关联的方法
完全可以分别去执行。

 Future

先从java中的Future来看吧:

我们在使用线程池的时候经常会遇到如下几个类,有着如下的关系:

Runnable  实现此接口的任务线程无返回结果
Callable  实现此接口的任务线程有返回结果
Future    代表着未来的接口,提供了如下几个方法,具体的实现都在FutureTask中
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
RunnableFuture   extends Runnable, Future<V>
FutureTask       implements RunnableFuture<V>
调用其get方法,如果任务没有执行完成则会阻塞。这里简单理解如何实现异步调用,如果你的方法想立刻方法,
则可以立刻返回一个FutureTask,想获取方法执行的结果则调用其get方法,但是如果任务还没执行完成,调用get
方法也会阻塞。       

简单使用:
public static void main(String[] args) throws ExecutionException, InterruptedException {

    Future<Integer> f1=exec.submit(()->{
        LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
        return 1;
    });

    Future<Integer> f2=exec.submit(()->{
        LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
        return 2;
    });

    int result=f1.get()+f2.get();
}
具体结果和执行时间大家自己体会一下。


FutureTask


我们来看看FutureTask是怎么实现的:
首先你提交的线程任务都会被包装为FutureTask
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

线程池最终会调用其run()方法
我们先来看看其get方法
public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}
当调用get方法的时候,首先会判断任务状态是否已经完成,如果没有完成则就会进行阻塞等待。

public void run() {
        ...
    result = c.call();
        ran = true;
        
        if (ran)
          set(result);
        ...
}

在执行run方法的时候会调用call方法,执行完成后会唤醒执行阻塞的get().
具体阻塞和唤醒可以看源码深入了解一下。

RPC框架中的同步和异步调用


这里来看看在RPC框架Motan中是怎么实现的:
Motan中自己实现了如下几个类:
Future
Response
DefaultResponse
DefaultResponseFuture
FutureListener

首先自己定义了Future和Response接口,然后定义了ResponseFuture接口。
通过名称可以知道Future相关的都代表着未来也就是异步调用会使用。
可以看到对于同步返回和异步的返回分别有其对应的实现类:
DefaultResponse implements Response
DefaultResponseFuture implements ResponseFuture
这里的DefaultResponseFuture 类似我们上面讲到的FutureTask.


DefaultResponseFuture :
我们来看看其具体的实现
DefaultResponseFuture 中实现的 getValue() 方法类型FutureTask中的get方法,会进行阻塞直到结果返回。
它是利用protected Object lock = new Object();来实现的阻塞。
synchronized (lock){
  if (!isDoing()) {
     return getValueOrThrowable();//如果不是在运行就报错
  }
  
  if (timeout <= 0) {//超时时间
    try {
        lock.wait();
    } catch (Exception e) {
        ...
    }
    //System.out.println("<= 0  getValueOrThrowable");
    return getValueOrThrowable();
  }
  ...部分代码
}

可以看到这里也是先判断任务是否还在运行,如果还在运行就会调用lock.wait();进行阻塞。
那是什么时候来lock.notifyAll()的呢?我们先看完同步调用的DefaultResponse 是如何实现的在来看
这个问题。


DefaultResponse :
我们先找到NettyClient中具体发送请求的request方法:
private Response request(Request request, boolean async) throws TransportException {
    Channel channel;
    Response response;
    try {
        // async request 异步
        response = channel.request(request);
    } catch (Exception e) {

    }
    // aysnc or sync result
    response = asyncResponse(response, async);
    return response;
}

在NettyChannel类中又实现了request方法:

public Response request(Request request) throws TransportException {
   
    ResponseFuture response = new DefaultResponseFuture(request, timeout, 
    this.nettyClient.getUrl());
    this.nettyClient.registerCallback(request.getRequestId(), response);
    byte[] msg = CodecUtil.encodeObjectToBytes(this, codec, request);
    ChannelFuture writeFuture = this.channel.writeAndFlush(msg);
    boolean result = writeFuture.awaitUninterruptibly(timeout, TimeUnit.MILLISECONDS);

    ...
    return response;
}

可以看到返回出去的response其实是DefaultResponseFuture.

再回到NettyClient中的方法:
private Response asyncResponse(Response response, boolean async) {
    if (async || !(response instanceof ResponseFuture)) {
        //心跳消息也会异步发送
        return response;
    }
    return new DefaultResponse(response);//同步获取
}

可以看到如果是同步则会返回DefaultResponse.并且将DefaultResponseFuture包装到DefaultResponse中.
所以同步调用其实也是借助了DefaultResponseFuture的功能。

我们来看看DefaultResponse的构造函数:
public DefaultResponse(Response response) {
    this.value = response.getValue();
    this.exception = response.getException();
    this.requestId = response.getRequestId();
    this.processTime = response.getProcessTime();
    this.timeout = response.getTimeout();
}
可以看到会直接调用response.getValue();也就是DefaultResponseFuture的getValue()方法。
我们上面讲了什么呢?DefaultResponseFuture的getValue()方法会怎么样呢?当然是阻塞到结果返回。

到这里我们可以看到如果是同步调用则会等到结果返回后才会返回response.而异步调用则是先返回
DefaultResponseFuture,等到想获取结果的时候则可以调用其get方法。

那我们解决最后一个问题吧,什么时候调用了lock.notifyAll(),让结果返回的。


答案是onSuccess方法。
在NettyClient中客户端获取到服务端的返回后会调用如下的handler方法:
@Override
public Object handle(Channel channel, Object message) {
    Response response = (Response) message;
    System.out.println("获取到message");
    ResponseFuture responseFuture = NettyClient.this.removeCallback(response.getRequestId());
    ...
    if (response.getException() != null) {
        responseFuture.onFailure(response);
    } else {
        responseFuture.onSuccess(response);
    }
    return null;
}

我们发现这里会调用DefaultResponseFuture的onsuccess方法:
public void onSuccess(Response response) {
    this.result = response.getValue();
    this.processTime = response.getProcessTime();
    done();
}
到这里就知道了,一切都在这个done方法里面了:
protected boolean done() {
    synchronized (lock) {
        if (!isDoing()) {//默认doing   如果不是这个状态就返回
            return false;
        }
         //System.out.println("done");
        state = FutureState.DONE;//运行完毕
        //System.out.println("notifyAll");
        lock.notifyAll();
    }

    notifyListeners();
    return true;
}

终于看到我们想看到的lock.notifyAll();方法了。


 总结

 通过分析Motan中的实现,可以看到整体思路和futureTask很类型,因此真的有必要学好基础的思想。加油。













这篇关于从浅入深来看RPC框架中同步和异步调用的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!