电光石火电光石火

陌晴博客
ilkhome.cn

motan之异步调用

一、什么是异步调用?

 1.同步调用

方法间的调用,假设A方法调用B方法,A方法等待B方法执行完毕后才执行本身,这个同步调用,是具有阻塞式的调用,如果B方法非常耗时,那么整个方法的执行效率将会非常低;

2.异步调用

同样是方法间的调用,假设A方法调用B方法,不同的是A方法调用B方法后,B方法很快的返回给A方法个答复(这个答复不是执行完整个B方法的答复),A方法收到答复后就执行本身,这个是异步调用,不管B方法是否耗时,整体的效率都提升。

 

二、motan的异步调用入门

1.首先,以入门案例为基础案例改造:http://www.cnblogs.com/Json1208/p/8784906.html

2.motan-api工程HelloWorldService添加注解@MotanAsync

复制代码
package com.motan.service;

import com.weibo.api.motan.transport.async.MotanAsync;

@MotanAsync
public interface HelloWorldService {

    String hello(String name);
}
复制代码
3.motan-api添加maven插件build-helper-maven-plugin,用来把自动生成类的目录设置为source path

复制代码
<build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <version>1.10</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>add-source</goal>
                        </goals>
                        <configuration>
                            <sources>
                                <source>${project.build.directory}/generated-sources/annotations</source>
                            </sources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
复制代码
编译时,Motan自动生成异步service类,生成路径为target/generated-sources/annotations/,生成的类名为service名加上Async,例如service类名为HelloWorldService.java,则自动生成的类名为HelloWorldServiceAsync.java。

另外,需要将motan自动生产类文件的路径配置为项目source path,可以使用maven plugin或手动配置,以上使用maven plugin方式。

这样,我们就能在eclipse中的source folder 中生成HelloWorldServiceAsync.java。

4.motan-client.xml配置的motan:referer标签中配置interface为自动生成的以Async为后缀的对应service类

<motan:referer id="helloWorldReferer" interface="com.motan.service.HelloWorldServiceAsync" directUrl="localhost:8002"/>
5.测试,先启动server,再启动client

复制代码
public class Server {

    @SuppressWarnings({ "unused", "resource" })
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:motan-server.xml");
        System.out.println("server start...");
    }
}

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
server start...
复制代码
复制代码
public class Client {

    @SuppressWarnings("resource")
    public static void main(String[] args) throws InterruptedException {
        ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"classpath:motan-client.xml"});
        HelloWorldServiceAsync async = (HelloWorldServiceAsync) ctx.getBean("helloWorldReferer");
        System.out.println(async.hello("motan"));
    }
}

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
Hello motan!
复制代码
最后再来看server的控制台,如果成功调用,会输出方法结果:

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
server start...
motan
 

三、motan异步调用详解

1.使用ResponseFuture接口来接收远程调用结果,ResponseFuture具备future和callback能力

①.将接口实现修改为:

复制代码
package com.motan.service;

public class HelloWorldServiceImpl implements HelloWorldService{

    @Override
    public String hello(String name) {
        try {
            Thread.sleep(5000);
 System.out.println(name);
            System.out.println("等待5s后返回");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello " + name + "!";
    }

}
复制代码
②.修改客户端调用为:

复制代码
package com.motan.client;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.motan.service.HelloWorldServiceAsync;
import com.weibo.api.motan.rpc.ResponseFuture;

public class Client {

    @SuppressWarnings("resource")
    public static void main(String[] args) throws InterruptedException {
        ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"classpath:motan-client.xml"});
        HelloWorldServiceAsync async = (HelloWorldServiceAsync) ctx.getBean("helloWorldReferer");
        ResponseFuture future = async.helloAsync("ResponseFuture");
        System.out.println(future.getValue());
    }
}
复制代码
注意:为了防止接口调用超时,消费端需要配置调用超时时间,在motan-client.xml中配置:

<motan:referer id="helloWorldReferer" interface="com.motan.service.HelloWorldServiceAsync" directUrl="localhost:8002" connectTimeout="8000" requestTimeout="8000"/>
③.启动服务端

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
server start...
④.启动客户端

等待5s后服务端控制台打印:

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
server start...
ResponseFuture
等待5s后返回
客户端控制台打印:

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
Hello ResponseFuture!
2.使用FutureListener监听,该监听器可以监听到接口是否成功调用,可以很灵活的判断如果成功调用在输出相关调用返回信息

虽然ResponseFuture带有isDone和isSuccess,但是经过测试,isDone和isSuccess并没办法在异步调用后用于判断,而是得配合FutureListener一起使用:

①.service实现不变,仍然是带有休眠的效果:

复制代码
package com.motan.service;

public class HelloWorldServiceImpl implements HelloWorldService{

    @Override
    public String hello(String name) {
        try {
            Thread.sleep(5000);
            System.out.println(name);
            System.out.println("等待5s后返回");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello " + name + "!";
    }

}
复制代码
②.使用FutureListener监听server端是否执行成功

复制代码
package com.motan.client;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.motan.service.HelloWorldServiceAsync;
import com.weibo.api.motan.rpc.Future;
import com.weibo.api.motan.rpc.FutureListener;
import com.weibo.api.motan.rpc.ResponseFuture;

public class Client {

    @SuppressWarnings("resource")
    public static void main(String[] args) throws InterruptedException {
        ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"classpath:motan-client.xml"});
        HelloWorldServiceAsync async = (HelloWorldServiceAsync) ctx.getBean("helloWorldReferer");
        FutureListener listener = new FutureListener() {
            @Override
            public void operationComplete(Future future) throws Exception {
                System.out.println("async call "
                        + (future.isSuccess() ? "sucess! value:" + future.getValue() : "fail! exception:"
                                + future.getException().getMessage()));
            }
        };
        ResponseFuture future1 = async.helloAsync("motan FutureListener...");
        future1.addListener(listener);
    }
}
复制代码
③.测试

首先,执行server端启动程序:

复制代码
package com.motan.server;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Server {

    @SuppressWarnings({ "unused", "resource" })
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:motan-server.xml");
        System.out.println("server start...");
    }
}

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
server start...
复制代码
接着,启动client端启动程序:

等待5s之后,server控制台输出:

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
server start...
motan FutureListener...
等待5s后返回
再来看client控制台输出:

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
async call sucess! value:Hello motan FutureListener...!
注意:在server端休眠的时候,client端是阻塞着的,由于我们超时时间跟上方一致配置的是8s,所以并不会超时,导致client一致阻塞,我们试着把超时实际调为3s(比server休眠时间短):

复制代码
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:motan="http://api.weibo.com/schema/motan"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://api.weibo.com/schema/motan http://api.weibo.com/schema/motan.xsd">

    <!-- 具体referer配置。使用方通过beanid使用服务接口类 -->
    <motan:referer id="helloWorldReferer" interface="com.motan.service.HelloWorldServiceAsync" directUrl="localhost:8002" connectTimeout="3000" requestTimeout="3000"/>
</beans>
复制代码
重新启动server应用程序,server控制台输出:

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
server start...
还未到休眠5s执行结束,client端就抛出一个异常:

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
async call fail! exception:error_message: com.weibo.api.motan.rpc.DefaultResponseFuture task cancel: serverPort=localhost:8002 requestId=1597643022347010049 
interface=com.motan.service.HelloWorldService method=hello(java.lang.String) cost=3042, status: 503, error_code: 10001,r=null
最后,server端才把休眠之后的消息打印:

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
server start...
motan FutureListener...
等待5s后返回
说明:client使用监听器监听server是否执行完毕,若server实际执行业务的时间在client端配置的接口请求超时时间之内,那么client请求后会一致阻塞着,直到server实际业务执行完成返回;

若server实际执行业务的时间大于client端配置的接口请求超时时间,那么一旦到达超时时间,直接抛出异常。



总结
在异步调用中,如果发起一次异步调用后,立刻使用 future.get() ,则大致和同步调用等同。其真正的优势是在submit 和 future.get() 之间可以混杂一些非依赖性的耗时操作,而不是同步等待,从而充分利用时间片。
另外需要注意,如果异步调用涉及到数据的修改,则多个异步操作直接不能保证 happens-before 原则,这属于并发控制的范畴了,谨慎使用。查询操作则大多没有这样的限制。
在能使用并发的地方使用并发,不能使用的地方才选择同步,这需要我们思考更多细节,但可以最大限度的提升系统的性能。

本博客所有文章如无特别注明均为原创。作者:似水的流年
版权所有:《电光石火》 => motan之异步调用
本文地址:http://www.ilkhome.cn/index.php/archives/351/
欢迎转载!复制或转载请以超链接形式注明,文章为 似水的流年 原创,并注明原文地址 motan之异步调用,谢谢。

评论