Dubbo泛化調用入門到精通

Dubbo 泛化調用、阿里的好舒服的泛化調用都是類似的功能。最近給同事排查一個dubbo-admin 控制檯調用報錯的問題的小研究,爲此還給社區提了一個issue,具體可以查看鏈接 dubbo-admin 泛型參數測試 ClassNotFoundException 看了最新版本好像自動去掉了出現泛型<這樣的信息通通幹掉,不過沒有發佈release版本。 參考文檔:使用泛化調用


1、泛化調用

1.1 概念

泛化接口調用方式主要用於客戶端沒有 API 接口(泛化調用是指不需要依賴服務的二方包)及模型類元的情況,參數及返回值中的所有 POJO 均用 Map 表示,通常用於框架集成,API網關比如:實現一個通用的服務測試框架,可通過 GenericService 調用所有服務實現。泛化調用的方式可以有效減少平臺型產品的二方包依賴,實現系統的輕量級運行。對於服務提供方新增的接口不需要修改二方包的版本,即可調用,不方便的就是對於數據入參、返回值由於沒有二方包很難理解,全部都是org.apache.dubbo.common.utils.PojoUtils#generalize(java.lang.Object) 轉換爲基本類型或者Map等等。

特點

  • 沒有二方、三方的 接口的類信息、class 字節碼
  • 直接通過基本的類型參數表示複雜的對象

1.2 PojoUtils 說明

PojoUtils. Travel object deeply, and convert complex type to simple type. (深度遍歷對象,並將複雜類型轉換爲簡單類型),同時也提供了反向轉換。
Simple type below will be remained:

  • Primitive Type, also include String, Number(Integer, Long), Date
  • Array of Primitive Type
  • Collection, eg: List, Map, Set etc.
  • Other type will be covert to a map which contains the attributes and value pair of object.


PojoUtils 提供了講複雜對象轉換爲簡單的對象,通過簡單對象轉換爲複雜對象。有點像序列化和反序列話,你要說它是不是,感覺也沒錯哈!官方的測試例子不錯,可以去了解一下

 List<User> users = new ArrayList<>();
 User user = new User();
 user.setName("other");
 user.setAge(31);
 users.add(user);

 Object generalize = PojoUtils.generalize(users);
 System.out.println(generalize);
 
 //[{name=other, class=org.apache.dubbo.samples.Dto.User, age=31}]

2、泛化例子

基於官方demo 中 dubbo-samples-zookeper,本地安裝一個zk 即可啓動
https://github.com/apache/dubbo-samples/tree/master/java/dubbo-samples-zookeeper

2.1 實踐指南

泛化調用最好的一個參考例子就是dubbo-admin 中 ,通過泛化進行控制檯的調用實踐,泛化沒有二方包,服務提供方的方法參數類型無法直接使用強類型表示,傳遞的參數也無法直接使用強類型只能通過 參考 :PojoUtils 序列化 Object generalize = PojoUtils.generalize(data) 例子傳遞 Map、List 、Array 等基本類型的參數
eg List -> [{name=other, age=31, class=org.apache.dubbo.samples.Dto.User}] 就是List<HashMap<String,String> 這樣的結構**,中增加 class 字段信息,反序列化時候使用表徵對應的泛型的信息,或者類class的信息**。

泛化必須參數

  • 服務的名稱、版本、分組 唯一限定符
  • 方法名稱、方法泛型參數類型
  • 傳遞的 PojoUtils.generalize(data) 序列化後的基本參數

爲啥需要這些,就是爲了找到具體的接口的提供者、接口的具體的方法是哪個?調用傳遞的參數是啥,即可享受正常的調用邏輯。如下是dubbo-admin 控制檯實踐指南,Dubbo API 網關也是類似的概念哦!只要通過配置或者傳遞參數中有如下的概念即可搞定問題。


泛化泛化的結果:也是基本類型的數據,根據 PojoUtils.generalize(result) 根據result 進行Pojo作爲泛化的結果。如下是 dubbo-admin 控制檯測試的代碼
https://github.com/apache/dubbo-admin/blob/develop/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/service/impl/GenericServiceImpl.java

public Object invoke(String service, String method, String[] parameterTypes, Object[] params) {

        ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
        String group = Tool.getGroup(service);
        String version = Tool.getVersion(service);
        String intf = Tool.getInterface(service);
        reference.setGeneric(true);
        reference.setApplication(applicationConfig);
        reference.setInterface(intf);
        reference.setVersion(version);
        reference.setGroup(group);

        try {
            removeGenericSymbol(parameterTypes);
            GenericService genericService = reference.get();
            return genericService.$invoke(method, parameterTypes, params);
        } finally {
            reference.destroy();
        }
    }

    /**
     * remove generic from parameterTypes 排除掉泛型信息
     *
     * @param parameterTypes
     */
    private void removeGenericSymbol(String[] parameterTypes){
        for (int i = 0; i < parameterTypes.length; i++) {
            int index = parameterTypes[i].indexOf("<");
            if (index > -1) {
                parameterTypes[i] = parameterTypes[i].substring(0, index);
            }
        }
    }

2.2 簡單的泛化調用

Provider 接口

針對官方的版本 添加了一個泛化的對象的接口

import java.util.List;

import org.apache.dubbo.config.annotation.Service;
import org.apache.dubbo.samples.Dto.User;
import org.apache.dubbo.samples.api.GreetingService;

@Service(version = "1.0.0.daily", group = "dubbo")
public class AnnotatedGreetingService implements GreetingService {

    /**
     * 簡單測試
     * 
     * @param name
     * @return
     */
    @Override
    public String sayHello(String name) {
        System.out.println("greeting service received: " + name);
        return "hello, " + name;
    }

    /**
     * 測試泛型接口
     * 
     * @param users
     * @return
     */
    @Override
    public List<User> getListUserIncludeMe(List<User> users) {
        if (users == null) {
            return null;
        }
        User user = new User();
        user.setName("wangji");
        user.setAge(28);
        users.add(user);
        return users;
    }
}


/**
 * @author wangji
 * @date 2020/6/2 7:12 PM
 */
public class User {
    private String name;

    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}


泛化調用

最最關鍵的地方在於 genericService.$invoke,調用這個泛化接口的幾個最主要的參數信息。最好自己動手實踐一下子。

public static void main(String[] args) {

    ApplicationConfig application = new ApplicationConfig();
    application.setName("Test-" + UUID.randomUUID().toString());
    RegistryConfig registryConfig = new RegistryConfig();
    registryConfig.setAddress("zookeeper://127.0.0.1:2181");
    application.setRegistry(registryConfig);
    application.setQosEnable(false);

    ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
    String group = "dubbo";
    String version = "1.0.0.daily";
    String interfaceName = "org.apache.dubbo.samples.api.GreetingService";
    reference.setGeneric(true);
    reference.setApplication(application);
    reference.setInterface(interfaceName);
    reference.setVersion(version);
    reference.setGroup(group);
    GenericService genericService = reference.get();

    Object data = genericService.$invoke("sayHello", new String[] { "java.lang.String" },
                                         new Object[] { "wangji" });

    System.out.println("result:" + data);

 }
// result:hello, wangji

2.3 含有泛型對象的泛化調用

如果你不知道怎麼編寫這個參數,可以 PojoUtils.generalize(data) 通過這個接口來生成對象,來看一下生成的數據的結構是咋樣的。

注意泛型參數傳遞的參數類型不包含 java.util.List<org.apache.dubbo.samples.Dto.User> 後面的泛型的信息,只需要傳遞 java.util.List 即可。

 public static void main(String[] args) {

     ApplicationConfig application = new ApplicationConfig();
     application.setName("Test-" + UUID.randomUUID().toString());
     RegistryConfig registryConfig = new RegistryConfig();
     registryConfig.setAddress("zookeeper://127.0.0.1:2181");
     application.setRegistry(registryConfig);
     application.setQosEnable(false);

     ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
     String group = "dubbo";
     String version = "1.0.0.daily";
     String interfaceName = "org.apache.dubbo.samples.api.GreetingService";
     reference.setGeneric(true);
     reference.setApplication(application);
     reference.setInterface(interfaceName);
     reference.setVersion(version);
     reference.setGroup(group);
     GenericService genericService = reference.get();

     List<User> users = new ArrayList<>();
     User user = new User();
     user.setName("other");
     user.setAge(31);
     users.add(user);

     Object generalize = PojoUtils.generalize(users);
     System.out.println(generalize);

     Object getListUserIncludeMe = genericService.$invoke("getListUserIncludeMe", new String[] { "java.util.List" },
                                                          new Object[] { generalize });

     System.out.println(getListUserIncludeMe);

    }

// [{name=other, class=org.apache.dubbo.samples.Dto.User, age=31}]
// [{name=other, class=org.apache.dubbo.samples.Dto.User, age=31}, {name=wangji, class=org.apache.dubbo.samples.Dto.User, age=28}]

2.4 指定IP調用Provider

測試環境中相同的服務會存在多臺,如果你想調用具體的某臺機子咋辦呢?指定具體的IP進行調用,沒有看到官方的文檔、問了一下小杰子的實踐,參考試了一下泛化調用直接ip 也是ok的,只需要找到具體的服務提供者即可。
不過不同的版本好像不一樣,沒有仔細嘗試


設置服務的url 即可滿足,能夠定位一個服務即可

dubbo 版本 2.7.2

reference.setUrl("dubbo://192.168.1.3:20880/org.apache.dubbo.samples.api.GreetingService");

dubbo 版本 2.7.0

需要添加分組的信息纔行

reference.setUrl("dubbo://192.168.1.3:20880/分組/org.apache.dubbo.samples.api.GreetingService");

指定ip調用完整的例子

基於 2.7.2 版本

public static void main(String[] args) {
    ApplicationConfig application = new ApplicationConfig();
    application.setName("Test-" + UUID.randomUUID().toString());
    RegistryConfig registryConfig = new RegistryConfig();
    registryConfig.setAddress("zookeeper://127.0.0.1:2181");
    application.setRegistry(registryConfig);
    application.setQosEnable(false);

    ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
    String group = "dubbo";
    String version = "1.0.0.daily";
    String interfaceName = "org.apache.dubbo.samples.api.GreetingService";
    reference.setGeneric(true);
    reference.setApplication(application);
    reference.setInterface(interfaceName);
    reference.setVersion(version);
    reference.setGroup(group);

    reference.setUrl("dubbo://192.168.1.3:20880/org.apache.dubbo.samples.api.GreetingService");
    GenericService genericService = reference.get();

    Object data = genericService.$invoke("sayHello", new String[] { "java.lang.String" },
                                         new Object[] { "wangji" });

    System.out.println("result:" + data);

 }

issue 中錯誤的調用

java.util.List<org.apache.dubbo.samples.Dto.User> 這個是由於PojoUtils的實現問題,導致這樣的寫法是有問題的。

Object getListUserIncludeMe = genericService.$invoke("getListUserIncludeMe", new String[] { "java.util.List<org.apache.dubbo.samples.Dto.User>" },
                                                             new Object[] { generalize });
    
// at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
// at org.apache.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:41)
// at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
// at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$CallbackRegistrationInvoker.invoke(ProtocolFilterWrapper.java:150)
// at org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:152)
// at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:102)
// at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:193)
// at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51)
// at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57)
// at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
// at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
// at java.lang.Thread.run(Thread.java:748)
// Caused by: java.lang.ClassNotFoundException: java.util.List<org.apache.dubbo.samples.Dto.User>
// at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
// at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
// at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
// at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
// at java.lang.Class.forName0(Native Method)
// at java.lang.Class.forName(Class.java:348)
// at org.apache.dubbo.common.utils.ReflectUtils.name2class(ReflectUtils.java:743)
// at org.apache.dubbo.common.utils.ReflectUtils.name2class(ReflectUtils.java:670)
// at org.apache.dubbo.common.utils.ReflectUtils.findMethodByMethodSignature(ReflectUtils.java:885)

3、實現原理

dubbo泛化調用原理 畫的這個圖不錯十分的形象生動,可以總結一下 泛化的原理

原理簡介

+-------------------------------------------+               +-------------------------------------------+
|  consumer 端                               |               | provider 端                                |
|                                           |               |                                           |
|                                           |               |                                           |
|                                           |               |                                           |
|                                           |               |                                           |
|                    +------------------+   |               |       +--------------+                    |
|                    |GenericImplFilter |   |  Invocation   |       |GenericFilter |                    |
|             +----> |                  +-------------------------> |              |                    |
|             |      +------------------+   |               |       +--------------+                    |
| +-----------+                             |               |                      |    +-----------+   |
| |           |                             |               |                      |    |           |   |
| |Client     |                             |               |                      +--> | Service   |   |
| |           |                             |               |                           |           |   |
| +-----------+                             |               |                           +-------+---+   |
|                                           |               |                                   |       |
|      ^             +------------------+   |               |       +--------------+            |       |
|      |             |GenericImplFilter |   |               |       |GenericFilter | <----------+       |
|      +-------------+                  | <-------------------------+              |                    |
|                    +------------------+   |               |       +--------------+                    |
|                                           |               |                                           |
|                                           |               |                                           |
|                                           |               |                                           |
|                                           |               |                                           |
+-------------------------------------------+               +-------------------------------------------+
  • 基於PojoUtils 簡化複雜對象,不用引入二方包。
  • 針對 com.alibaba.dubbo.rpc.service.GenericService.$invoke(String method, String[] parameterTypes, Object[] args) 這個接口進行特殊判斷,基於攔截器處理特殊攔截。
  • 基於攔截器,消費者端 GenericImplFilter 處理(不考慮dubbo 其他的複雜序列化的需求很簡單,基本上啥都不做)
  • 基於攔截器,服務提供者端 GenericFilter處理,>1 基於接口、方法名稱、方法參數類型查詢具體的服務、服務的方法名稱; 2> 基於PojoUtils 、方法參數類型、方法參數 反序列化爲複雜的參數對象PojoUtils.realize(args, params, method.getGenericParameterTypes()) ;3、基於PojoUtils.generalize(appResponse.getValue()) 序列化返回值。

簡單實現泛化的過濾器

消費者端

/**
 * GenericImplInvokerFilter 基於官方的class 2.7.2 刪掉不要的東西,簡化理解
 */
@Activate(group = CommonConstants.CONSUMER, value = GENERIC_KEY, order = 20000)
public class GenericImplFilter extends ListenableFilter {

    private static final Logger logger = LoggerFactory.getLogger(GenericImplFilter.class);

    private static final Class<?>[] GENERIC_PARAMETER_TYPES = new Class<?>[]{String.class, String[].class, Object[].class};

    public GenericImplFilter() {
        super.listener = new GenericImplListener();
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String generic = invoker.getUrl().getParameter(GENERIC_KEY);
        if ((invocation.getMethodName().equals($INVOKE) || invocation.getMethodName().equals($INVOKE_ASYNC))
                && invocation.getArguments() != null
                && invocation.getArguments().length == 3
                && ProtocolUtils.isGeneric(generic)) {

            invocation.setAttachment(
                    GENERIC_KEY, invoker.getUrl().getParameter(GENERIC_KEY));
        }
        return invoker.invoke(invocation);
    }

    static class GenericImplListener implements Listener {
        @Override
        public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
            
        }

        @Override
        public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {

        }
    }

}

生產者端

/**
 * GenericInvokerFilter. 基於官方的class 2.7.2 刪掉不要的東西,簡化理解
 */
@Activate(group = CommonConstants.PROVIDER, order = -20000)
public class GenericFilter extends ListenableFilter {

    public GenericFilter() {
        super.listener = new GenericListener();
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
        if ((inv.getMethodName().equals($INVOKE) || inv.getMethodName().equals($INVOKE_ASYNC))
                && inv.getArguments() != null
                && inv.getArguments().length == 3
                && !GenericService.class.isAssignableFrom(invoker.getInterface())) {
            String name = ((String) inv.getArguments()[0]).trim();
            String[] types = (String[]) inv.getArguments()[1];
            Object[] args = (Object[]) inv.getArguments()[2];
            try {
                // 找到調用的接口
                Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
                Class<?>[] params = method.getParameterTypes();
                if (args == null) {
                    args = new Object[params.length];
                }
                String generic = inv.getAttachment(GENERIC_KEY);

                if (StringUtils.isBlank(generic)) {
                    generic = RpcContext.getContext().getAttachment(GENERIC_KEY);
                }

                if (StringUtils.isEmpty(generic)
                        || ProtocolUtils.isDefaultGenericSerialization(generic)) {
                    // 將PojoUtils 轉換的簡單對象 轉換爲複雜的對象 
                    args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
                }
                return invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
            } catch (NoSuchMethodException e) {
                throw new RpcException(e.getMessage(), e);
            } catch (ClassNotFoundException e) {
                throw new RpcException(e.getMessage(), e);
            }
        }
        return invoker.invoke(inv);
    }

    static class GenericListener implements Listener {

        @Override
        public void onResponse(Result appResponse, Invoker<?> invoker, Invocation inv) {
            if ((inv.getMethodName().equals($INVOKE) || inv.getMethodName().equals($INVOKE_ASYNC))
                    && inv.getArguments() != null
                    && inv.getArguments().length == 3
                    && !GenericService.class.isAssignableFrom(invoker.getInterface())) {

                String generic = inv.getAttachment(GENERIC_KEY);
                if (StringUtils.isBlank(generic)) {
                    generic = RpcContext.getContext().getAttachment(GENERIC_KEY);
                }

                if (appResponse.hasException() && !(appResponse.getException() instanceof GenericException)) {
                    appResponse.setException(new GenericException(appResponse.getException()));
                }
                // 設置反序列化 講複雜對象轉換爲簡單的基礎對象
                appResponse.setValue(PojoUtils.generalize(appResponse.getValue()));
                
            }
        }

        @Override
        public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {

        }
    }
}

基於真實數據觀察 過濾器的值的傳遞

org.apache.dubbo.rpc.RpcInvocation 打印的效果,方便理解。

public String toString() {
        return "RpcInvocation [methodName=" + methodName + ", parameterTypes="
                + Arrays.toString(parameterTypes) + ", arguments=" + Arrays.toString(arguments)
                + ", attachments=" + attachments + "]";
    }

消費者端GenericImplFilter觀察

org.apache.dubbo.rpc.filter.GenericImplFilter

GenericImplFilter 消費者 invoker.getUrl()的值
dubbo://192.168.1.3:20880/org.apache.dubbo.samples.api.GreetingService?application=Test-0519fed7-71d4-494b-b225-15f153bb79db&dubbo=2.0.2&generic=true&group=dubbo&interface=org.apache.dubbo.samples.api.GreetingService&lazy=false&pid=12632&qos-enable=false&register.ip=192.168.1.3&side=consumer&sticky=false&timestamp=1591110891800&version=1.0.0.daily

GenericImplFilter 消費者 invocation.toString()的值
RpcInvocation 
[methodName=$invoke,//調用的方法
parameterTypes=[class java.lang.String, class [Ljava.lang.String;,
class [Ljava.lang.Object;],//通用泛型的參數類型
arguments=[sayHello, [Ljava.lang.String;@68b32e3e, [Ljava.lang.Object;@bcef303],
attachments={}
]

生產者端GenericFilter 觀察

org.apache.dubbo.rpc.filter.GenericFilter

GenericFilter RpcInvocation的toString的值
RpcInvocation 
[methodName=$invoke, 
parameterTypes=[class java.lang.String, class [Ljava.lang.String;, class [Ljava.lang.Object;],
arguments=[sayHello, [Ljava.lang.String;@4174acb6, [Ljava.lang.Object;@5486085f],
attachments={path=org.apache.dubbo.samples.api.GreetingService, 
input=351, dubbo=2.0.2, 
interface=org.apache.dubbo.samples.api.GreetingService, 
version=1.0.0.daily, generic=true, 
group=dubbo}]

GenericFilter invoker.getUrl()
dubbo://192.168.1.3:20880/org.apache.dubbo.samples.api.GreetingService?anyhost=true&application=zookeeper-demo-provider&bean.name=ServiceBean:org.apache.dubbo.samples.api.GreetingService:1.0.0.daily:dubbo&bind.ip=192.168.1.3&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=dubbo&interface=org.apache.dubbo.samples.api.GreetingService&methods=sayHello,getListUserIncludeMe&pid=12595&qos-accept-foreign-ip=false&register=true&release=2.7.2&revision=1.0.0.daily&side=provider&timestamp=1591110149299&version=1.0.0.daily

4、總結

對於dubbo的泛化調用的文章一直想記錄一篇,一直苦於沒有思路,不知道怎麼找到合適的切入角來描述這個問題,正好最近協助同事排查問題,正好有所收穫,因此記錄一篇博客,在瞭解過程中不斷的進化,加深了對於dubbo的認知。6-2 號,特殊的日子,好像是我陽曆的生日 happy ~~




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章