Dubbo學習筆記4:服務消費端泛化調用與異步調用


本文借用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的非阻塞實現並行調用,客戶端不需要啟動多線程即可完成並行調用多個遠程服務,相對調用不同的服務使用不同線程來說開銷較小。  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM