Dubbo高级特性实践-泛化调用

作者: 阿羅 | 来源:发表于2016-07-10 13:59 被阅读6314次

    引言

    当后端Java服务用Dubbo协议作为RPC方案的基础,但部分消费方是前端Restful的PHP服务,不能直接调用,于是在中间架设了Router服务提供统一的基于HTTP的后端调用入口。
    而Router调用后端Java服务就应用了Dubbo的高级特性--泛化调用

    • 直接消费方(Router服务)不需要引入接口jar包
    • 通过GenericService接口来处理所有服务请求
    • 以PHP到Router的request body中的方法名和方法参数作为Router远程调用后端Java服务的入参,最后将远程调用的result返回给PHP端

    本文将用一个小Demo来演示上面所述的泛化调用应用场景

    零.Dubbo简介

    DUBBO是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,是阿里巴巴SOA服务化治理方案的核心框架,每天为2,000+个服务提供3,000,000,000+次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。
    -- Dubbo官方描述

    Dubbo能做什么:

    • 透明化的远程方法调用
      • 就像调用本地方法一样调用远程方法
      • 只需简单配置,没有任何API侵入。
    • 软负载均衡及容错机制
      • 可在内网替代F5等硬件负载均衡器
    • 服务自动注册与发现
      • 不再需要写死服务提供方地址,注册中心基于接口名查询服务提 供者的IP地址,并且能够平滑添加或删除服务提供者

    -- 《Dubbo功能介绍》(官方资料)

    注:Dubbo的基本使用介绍不在本文范畴,如有需要请自行参考官方资料

    泛接口调用方式主要用于客户端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用Map表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过GenericService调用所有服务实现。
    -- Dubbo用户指南

    一.后端API

    public interface UserInfoService {
        public Map<String, String> getUser(String id);
        public Map<String, String>[] getUsers();
    }
    

    二.Router端dubbo配置

    dubboconf.properties:

    application.name=router
    registry.address=zookeeper://address1?buckup=address2,address3
    

    三.前端服务post到Router的Request Body示例:

    {
        "interfaceName": "foo", 
        "methodName": "bar", 
        "methodParams": [
            {
                "id": "xxx"
            }
        ]
    }
    

    四.处理前端参数用的Dto

    RequestDto.java:

    import java.util.Map;
    /**
     * Created by Luo
     */
    public class RequestDto {
        private String interfaceName;
        private String methodName
        private Map[] methodParams;
    
        public String getInterfaceName() {
            return interfaceName;
        }
        public void setInterfaceName(String interfaceName) {
            this.interfaceName =  interfaceName;
        }
        public String getMethodName() {
            return methodName;
        }
        public void setMethodName(String methodName) {
            this.methodName = methodName;
        }
        public Map[] getMethodParams() {
            return methodParams;
        }
        public void setMethodParam(Map[] methodParams) {
            this.methodParams = methodParams;
        }
    }
    

    五.Router服务入口

    RouterController.java:

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * Created by Luo
     */
    @RestController
    public class App {
            @RequestMapping(value = "/router/", method = RequestMethod.POST)
            public Object getUser(@ModelAttribute RequestDto dto) {
                Map map = new HashMap<>();
                map.put("ParamType", "java.lang.String");  //后端接口参数类型
                map.put("Object", dto.getMethodParams()[0].get("id"));  //用以调用后端接口的实参
    
                List<Map<String, Object>> paramInfos= new ArrayList<>();
                paramInfos.add(map);
    
                DubboServiceFactory dubbo = DubboServiceFactory.getInstance();
    
                return dubbo.genericInvoke(dto.getInterfaceName(), dto.getMethodName(), paramInfos);
            }
    }
    

    注:本文旨在演示泛化调用的一种应用方式,为简明起见,代码中直接从dto中获取了指定参数,而并没有完整实现其路由功能,望见谅。

    六.通过GenericService进行泛化调用

    DubboServiceFactory.java

    package local.demo.genericservice;
    
    import com.alibaba.dubbo.config.ApplicationConfig;
    import com.alibaba.dubbo.config.ReferenceConfig;
    import com.alibaba.dubbo.config.RegistryConfig;
    import com.alibaba.dubbo.config.utils.ReferenceConfigCache;
    import com.alibaba.dubbo.rpc.service.GenericService;
    
    
    import java.io.IOException;
    import java.util.List;
    import java.util.Map;
    import java.util.Properties;
    
    /**
     * Created by Luo
     */
    public class DubboServiceFactory {
    
        private ApplicationConfig application;
        private RegistryConfig registry;
    
        private static class SingletonHolder {
            private static DubboServiceFactory INSTANCE = new DubboServiceFactory();
        }
    
        private DubboServiceFactory(){
            Properties prop = new Properties();
            ClassLoader loader = DubboServiceFactory.class.getClassLoader();
    
            try {
                prop.load(loader.getResourceAsStream("dubboconf.properties"));
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            ApplicationConfig applicationConfig = new ApplicationConfig();
            applicationConfig.setName(prop.getProperty("application.name")); 
            //这里配置了dubbo的application信息*(demo只配置了name)*,因此demo没有额外的dubbo.xml配置文件
            RegistryConfig registryConfig = new RegistryConfig();
            registryConfig.setAddress(prop.getProperty("registry.address")); 
            //这里配置dubbo的注册中心信息,因此demo没有额外的dubbo.xml配置文件
    
            this.application = applicationConfig;
            this.registry = registryConfig;
    
        }
    
        public static DubboServiceFactory getInstance() {
            return SingletonHolder.INSTANCE;
        }
    
        public Object genericInvoke(String interfaceClass, String methodName, List<Map<String, Object>> parameters){
    
            ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
            reference.setApplication(application); 
            reference.setRegistry(registry); 
            reference.setInterface(interfaceClass); // 接口名 
            reference.setGeneric(true); // 声明为泛化接口 
            /*ReferenceConfig实例很重,封装了与注册中心的连接以及与提供者的连接,
            需要缓存,否则重复生成ReferenceConfig可能造成性能问题并且会有内存和连接泄漏。
            API方式编程时,容易忽略此问题。
            这里使用dubbo内置的简单缓存工具类进行缓存*/
            ReferenceConfigCache cache = ReferenceConfigCache.getCache();
            GenericService genericService = cache.get(reference); 
            // 用com.alibaba.dubbo.rpc.service.GenericService可以替代所有接口引用 
    
            int len = parameters.size();
            String[] invokeParamTyeps = new String[len];
            Object[] invokeParams = new Object[len];
            for(int i = 0; i < len; i++){
                invokeParamTyeps[i] = parameters.get(i).get("ParamType") + "";
                invokeParams[i] = parameters.get(i).get("Object");
            }
            return genericService.$invoke(methodName, invokeParamTyeps, invokeParams);
        }
    
    }
    

    七.部署

    将Router部署到Jetty/Tomcat等容器,或者直接使用SpringBoot开发,发布为内嵌Jetty/Tomcat的独立jar包,即可向前端服务提供服务。

    相关文章

      网友评论

      • 任泡泡:当服务方抛出异常的时候,genericService.$invoke会抛出GenericException封装了原始异常的信息,如果让它直接抛出原始的异常呢?(本地可以得到抛出异常的class)
        DuffyHuang:博主描述的很详细,请教一个问题:
        java.lang.reflect.UndeclaredThrowableException
        Caused by: com.caucho.hessian.io.HessianServiceException: The service has no method named: $invoke
        我按照博主的说明写了个demo,却发生上述问题,请问可能是什么原因?
        答案在风中:dubbo设计上为了避免异常反序列化时出现ClassNotFound,所以会使用GenericException包装原有的异常。但是只要consumer中定义了该异常类型,还是能够捕捉到原始异常的。
        阿羅:@任泡泡 没有实际处理过你说的场景,可能不能直接帮到你。不过dubbo是开源的,如果没有找到其他简单的办法,你可以尝试修改com.alibaba.dubbo.rpc.service.GenericException,在dubbo-rpc模块的dubbo-rpc-api子模块下。

      本文标题:Dubbo高级特性实践-泛化调用

      本文链接:https://www.haomeiwen.com/subject/lvqdjttx.html