本文借用dubbo.learn的Dubbo API方式來解釋原理。
服務消費端泛化調用
前面我們講解到,基於Spring和基於Dubbo API方式搭建簡單的分布式系統時,服務消費端引入了一個SDK二方包,里面存放着服務提供端提供的所有接口類,之所以需要引入接口類是因為服務消費端一般是基於接口使用JDK代理實現遠程調用的。
泛化接口調用方式主要在服務消費端沒有API接口類及模型類元(比如入參和出參的POJO類)的情況下使用。其參數及返回值中沒有對應的POJO類,所以所有POJO均轉換為Map表示。使用泛化調用時候服務消費模塊不再需要引入SDK二方包。
下面基於Dubbo API實現異步調用,在Consumer模塊里面TestConsumerApiGeneric是泛化調用的方式,代碼如下:
public class TestConsumerApiGeneric(){ public static void main(String[] args) throws IOException{ // 當前應用配置 ApplicationConfig application = new ApplicationConfig(); application.setName("dubboConsumer"); // 連接注冊中心配置 RegistryConfig registry = new RegistryConfig(); registry.setAddress("127.0.0.1:2181"); registry.setProtocol("zookeeper"); // 泛型參數設置為GenericService ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>(); reference.setApplication(application); reference.setRegistry(registry); reference.setVersion("1.0.0"); reference.setGroup("dubbo"); reference.setTimeout(3000); // 設置為泛化 reference.setInterface("com.test.UserServiceBo"); reference.setGeneric(true); // 用com.alibaba.dubbo.rpc.service.GenericService替代所有接口引用 GenericService userService = reference.get(); // 基於類型以及Date/List/Map等不需要轉換,直接調用,如果返回值為POJO也將自動轉為Map Object result = userService.$invoke("sayHello",new String[]{"java.lang.String"},new Object[]{"哈哈哈"}); System.out.println(JSON.json(result)); // POJO參數轉換為map Map<String,Object> map = new HashMap<String,Object>(); map.put("class","com.test.PersonImpl"); map.put("name","jiaduo"); map.put("password","password"); result = userService.$invoke("testPojo",new String[]{"com.test.Person"},new Object[]{map}); System.out.println((result)); } }
上面代碼中,由於sayHello的參數是String,沒有很好的體現參數轉換為Map,下面我們具體來說下POJO參數轉換Map的含義。
比如服務提供者提供的一個接口的 testPojo(Person person) 方法的參數為如下所示:
package com.test; public class PersonImpl implements Person{ private String name; private String password; public String getName(){ return name; } public void setName(String name){ this.name = name; } public String getPassword(){ return password; } public void setPassword(String password){ this.password = password; } }
則POJO數據:
Person person = new PersonImpl(); person.setName("jiaduo"); person.setPassword("password");
正常情況下調用接口是使用:
servicePerson.testPojo(person);
泛化調用下需要首先轉換person為Map,如下表示:
Map<String,Object> map = new HashMap<String,Object>(); // 注意:如果參數類型是接口,或者List等丟失泛型,可通過class屬性指定類型。 map.put("class","com.test.PersonImpl"); map.put("name","jiaduo"); map.put("password","password");
然后使用下面方法進行泛化調用:
servicePerson.$invoke("testPojo",new String[]{"com.test.Person"},new Object[]{map});
泛化調用通常用於框架集成,比如:實現一個通用的服務測試框架,可通過GenericService調用所有服務實現,而不需要依賴服務實現方提供的接口類以及接口的入參和出參的POJO類。
服務消費端異步調用
無論前面我們講解的正常調用還是泛化調用,都是同步調用,也就是服務消費方發起一個遠程調用后,調用線程要被阻塞掛起,直到服務提供方返回。
本節講解下服務消費端異步調用,異步調用是指服務消費方發起一個遠程調用后,不等服務提供方返回結果,調用方法就返回了,也就是當前線程不會被阻塞,這就允許調用方同時調用多個遠程方法。
在Consumer模塊里面TestConsumerAsync是泛化調用,代碼如下:
public class TestConsumerAsync{ public static void main(String[] args) throws InterruptedException,ExecutionException{ // 當前應用配置 ApplicationConfig application = new ApplicationConfig(); application.setName("dubboConsumer"); // 連接注冊中心配置 RegistryConfig registry = new RegistryConfig(); registry.setAddress("127.0.0.1:2181"); registry.setProtocol("zookeeper"); // 引用遠程服務 ReferenceConfig<UserServiceBo> reference = new ReferenceConfig<UserServiceBo>(); reference.setApplication(application); reference.setRegistry(registry); reference.setInterface(UserServiceBo.class); reference.setVersion("1.0.0"); reference.setGroup("dubbo"); reference.setTimeout(3000); // (1)設置為異步調用 reference.setAsync(true); // 和本地bean一樣使用xxxService UserServiceBo userService = reference.get(); long startTime = System.currentTimeMillis() / 1000; // (2)因為異步調用,此處返回null System.out.println(userService.sayHello("哈哈哈")); // 拿到調用的Future引用,當結果返回后,會被通知和設置到此Futrue Future<String> userServiceFutureOne = RpcContext.getContext().getFuture(); // (3)因為異步調用,此處返回null System.out.println(userService.sayHello2("哈哈哈2")); // 拿到調用的Future引用,當結果返回后,會被通知和設置到此Future Future<String> userServiceFutureTwo = RpcContext.getContext().getFuture(); // (4)阻塞到get方法,等待結果返回 System.out.println(userServiceFutureOne.get()); System.out.println(userServiceFutureTwo.get()); long endTime = System.currentTimeMillis() / 1000; System.out.println("costs:" + (endTime - startTime)); } }
運行上面代碼,輸出如下圖所示:

其中代碼(2)(3)處輸出null,說明開啟異步調用后調用方直接返回null。
輸出costs:2說明異步調用生效了,因為sayHello和sayHello2方法內都sleep了2s,如果是順序調用則會耗時至少4s,這里耗時2s說明兩次調用是並發進行的。
異步調用是基於nio的非阻塞實現並行調用,客戶端不需要啟動多線程即可完成並行調用多個遠程服務,相對調用不同的服務使用不同線程來說開銷較小。
