motan之非同步調用

  • 2019 年 12 月 7 日
  • 筆記

一、什麼是非同步調用?  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 原則,這屬於並發控制的範疇了,謹慎使用。查詢操作則大多沒有這樣的限制。 在能使用並發的地方使用並發,不能使用的地方才選擇同步,這需要我們思考更多細節,但可以最大限度的提升系統的性能。