一、什么是异步调用?
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 原则,这属于并发控制的范畴了,谨慎使用。查询操作则大多没有这样的限制。
在能使用并发的地方使用并发,不能使用的地方才选择同步,这需要我们思考更多细节,但可以最大限度的提升系统的性能。