• Dubbo学习笔记-泛化实现进行mock


    Dubbo RPC介绍

    1. 什么是Dubbo,我们正常是怎么使用的?

    Apache Dubbo™ 是一款高性能Java RPC框架.其中与Alibaba Dubbo的区别主要在于阿里开发的2.6.X且不再维护,Apache开发的2.7.X新增了元数据中心 MetaData 和配置中心 Conf-center 这两个功能。元数据信息包括服务接口,及接口的方法信息。这些信息将被用于服务mock,服务测试。

    我们平常是在实例上增加注解@Service暴露服务和使用@Reference来完成自动引用实例,样例查看http://dubbo.apache.org/zh-cn/docs/user/configuration/annotation.html

    2. 注册一个RPC服务需要什么参数

    官方给出的快速启动样例是这样的: http://dubbo.apache.org/zh-cn/docs/user/quick-start.html 这个样例是使用XML,我们并不常用,但可以比较清楚地观察到注册一个RPC服务需要什么参数。注意下面这个 provider.xml,我增加了一些改动用以说明

    <?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:dubbo="http://dubbo.apache.org/schema/dubbo"
        xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd        http://dubbo.apache.org/schema/dubbo        http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
     
        <!-- 提供方应用信息,用于计算依赖关系 application.name为应用名,一个接口下可以有多个应用来提供 -->
        <dubbo:application name="hello-world-app"  />
     
        <!-- 使用multicast广播注册中心暴露服务地址  ip:port地址可以为多个,用,分割即可。 zookeeper://101.201.232.80:2181?backup=10.20.153.11:2181,10.20.153.12:2181"-->
        <dubbo:registry address="zookeeper://101.201.232.80:2181" />
     
        <!-- 用dubbo协议在20880端口暴露服务 -->
        <dubbo:protocol name="dubbo" port="20880" />
     
        <!-- 声明需要暴露的服务接口 
    	     interface为接口全类名,
    	     ref服务对象实现引用,与下面的bean.id对应 
    	     version=服务版本,升级接口如果不兼容之前接口,就需要升级版本号 
    	     group=服务分组,当一个接口有多个实现,可以用分组区分
    	-->
        <dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" version="1.0.0" group="dubbo"/>
     
        <!-- 声明实现类,使远程RPC像本地bean一样实现服务 id=service.ref class=实现类的全类名 -->
        <bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl" />
    </beans>
    

    其实区别定位一个RPC服务需要的条件一共有四个:

    1. application.name
    2. service.interface。注意:如果这个接口不存在,就无法注册并暴露服务,而我们mock的目标就是个假的服务出来。
    3. service.version
    4. service.group

    此处注意:服务的元数据是根据service.interface反射得到的,我们为了和2.7.X兼容,就必须自己造个假的元数据上去

    3. 我们的Mock实现思路

    Dubbo与mock相关的提供了两种api:本地伪装Mock泛化实现

    本地伪装Mock,XML对应为<dubbo:reference interface="com.foo.BarService" mock="true" .../>用于服务降级,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。
    

    大白话就是参考AOP的after-throw,原服务抛出异常后,触发本地mock进行服务降级。try {//原服务} catch (Exception e) {//执行mock实例}。这个坑了我很多时间,因为mock="true"太具有迷惑性,我原本使用这个mock约定实现mock平台功能,使用force模式强制调用mock平台上的服务,但是一来会覆盖原先的本地mock;二来不符合无侵入原则。最终放弃。贴出参考用法如下

    <dubbo:reference id="demoService" check="false" interface="com.foo.BarService">
    	<!-- force表示强制走mock; return fake是mock执行的语句 -->
        <dubbo:parameter key="sayHello.mock" value="force:return fake"/>
    </dubbo:reference>
    

    泛化实现,XML对应为<dubbo:reference interface="com.foo.BarService" generic="true" .../>,用于服务器端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用Map表示,通常用于框架集成,比如:实现一个通用的远程服务Mock框架,可通过实现GenericService接口处理所有服务请求。
    

    这个就是我们最终采用的实现思路。通过指定一个通用服务接口GenericService的实现,完成服务的暴露。

    // 这个就是 提供者dubbo 和标签一一对应,该类很重,因为包括了与注册中心的链接等等,需要自行缓存处理
    ServiceConfig<GenericService> service = new ServiceConfig<GenericService>;
    // 配置service里注册中心的属性,需要指定address,例"zookeeper://101.201.232.80:2181"。
    service.setRegistry(new RegistryConfig("zookeeper://101.201.232.80:2181"));
    // 配置应用信息,这里我们将应用名统一表示为luckymock。
    ApplicationConfig application = new ApplicationConfig();
    application.setDefault(true);
    application.setName("luckymock");
    service.setApplication(application);
    // 指定通信协议和本地通讯端口
    ProtocolConfig protocol = new ProtocolConfig();
    protocol.setName("dubbo");
    protocol.setPort(20880);
    service.setProtocol(protocol);
    // 说明是否是通用服务 并绑定通用服务接口的实现:GenericServiceImpl
    service.setGeneric("true");
    GenericService genericService = new GenericServiceImpl();
    service.setRef(genericService);
    // 配置这个服务特有的的东西
    service.setGroup("dubbo");
    service.setVersion("1.0.0");
    service.setInterface("test")
    // 对外暴露这个服务
    service.export();
    // 由于上面提到的元数据问题,我们手动去提交了一遍元数据。
    // 这方面资料极少,我是通过源码ZookeeperMetadataReport的storeMetadata方法追溯到MetadataReportService的publishProviderf方法,所有用法都参考这个方法
    // 使用下面这个函数提交服务提供者的元数据,不一定是zookeeper作注册中心
    metadataReport.storeProviderMetadata(metadataIdentifier, fullServiceDefinition);
    

    关于元数据,MetadataIdentifier 该类主要是用于配置zookeeper上元数据节点路径和节点名称

    例:/dubbo/metadata/服务名(接口全类名)/1.0.0(版本)/dubbo(分组)/provide(side)/demo-provide(应用名)

    FullServiceDefinition 该对象进行gson转换后的json串,即为元数据节点内容

    • parameters:服务的属性,包括version,group等等
    • canonicalName:接口全类名
    • codeSource:源代码
    • methods:方法
    • types: 所有方法入参出参的类型

    样例

    GenericServiceImpl 这个impl就是xml中service.ref所需要的类

    import org.apache.dubbo.rpc.service.GenericException;
    import org.apache.dubbo.rpc.service.GenericService;
    
    /**
     * @Description: 服务端实现 GenericService
     * @Author: quanhuan.huang@luckincoffee.com
     * @Date: 2019/12/19 15:01
     */
    public class GenericServiceImpl implements GenericService {
        @Override
        public Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException {
           // 这里只有method方法名可以进行区别,所以该类可能需要动态编译
           if (method.equals("hi")) {
                return "hi, " + args[0];
            } else if (method.equals("hello")) {
                return "hello, " + args[0];
            } else if (method.equals("sayHello")) {
                return "say:hello, " + args[0];
            }
            return "未找到该方法,无法mock";
        }
    }
    

    数据结构:DubboRpcDTO

    public class DubboRpcDTO {
        /**
         * 服务接口的全类名
         */
        private String interfaceName;
        /**
         * 分组
         */
        private String group;
        /**
         * 版本
         */
        private String version;
        /**
         * 方法列表
         */
        private List<DubboMethodDTO> methodList;
    }
    

    数据结构:DubboMethodDTO

    public class DubboMethodDTO {
        /**
         * 方法名
         */
        private String methodName;
        /**
         * 方法参数
         */
        private String[] arguments;
        /**
         * 返回类型
         */
        private String returnType;
    }
    

    服务实现

    @Service
    public class DubboProvideService {
        private static Logger logger = LoggerFactory.getLogger(DubboProvideService.class);
        private MetadataReportFactory metadataReportFactory = ExtensionLoader.getExtensionLoader(MetadataReportFactory.class).getAdaptiveExtension();
    
        /**
         * 该类很重 自行缓存
         */
        private static ServiceConfig<GenericService> service;
    
        /**
         * 提供rpc服务
         *
         * @return 是否完成
         */
        Boolean rpcMockProvide(DubboRpcDTO rpcDTO) throws ClassNotFoundException {
    
            // 注册并对外暴露服务
            ServiceConfig<GenericService> service = getService();
            service.setGroup(rpcDTO.getGroup());
            service.setVersion(rpcDTO.getVersion());
            service.setInterface(rpcDTO.getInterfaceName());
            // 指向自己实现的通用类实例,需要动态化
            GenericService genericService = new GenericServiceImpl();
            service.setGeneric("true");
            service.setRef(genericService);
            service.export();
    
            // 注册数据源
            FullServiceDefinition fullServiceDefinition = new FullServiceDefinition();
            TypeDefinitionBuilder builder = new TypeDefinitionBuilder();
            List<TypeDefinition> typeDefinitions = new LinkedList<>();
            List<MethodDefinition> methodDefinitions = new LinkedList<>();
            for (DubboMethodDTO method : rpcDTO.getMethodList()) {
                // 记录方法
                MethodDefinition methodDefinition = new MethodDefinition();
                methodDefinition.setName(method.getMethodName());
                methodDefinition.setParameterTypes(method.getArguments());
                methodDefinition.setReturnType(method.getReturnType());
                methodDefinitions.add(methodDefinition);
                // 记录所有入参的type
                for (String argument : method.getArguments()) {
                    TypeDefinition td = builder.build(Class.forName(argument), Class.forName(argument));
                    typeDefinitions.add(td);
                }
                // 返回值的type也需要记录
                typeDefinitions.add(builder.build(Class.forName(method.getReturnType()), Class.forName(method.getReturnType())));
            }
            // 拼接服务内容
            Map<String, String> parameters = new HashMap<>(16);
            parameters.put("side", "provider");
            parameters.put("release", "2.7.3");
            parameters.put("methods", "*");
            parameters.put("deprecated", "false");
            parameters.put("dubbo", "2.0.2");
            parameters.put("interface", rpcDTO.getInterfaceName());
            parameters.put("version", rpcDTO.getVersion());
            parameters.put("generic", "true");
            parameters.put("application", "luckymock");
            parameters.put("dynamic", "true");
            parameters.put("register", "true");
            parameters.put("group", rpcDTO.getGroup());
            parameters.put("anyhost", "true");
            fullServiceDefinition.setParameters(parameters);
            fullServiceDefinition.setCodeSource(ClassUtils.getCodeSource(this.getClass()));
            fullServiceDefinition.setCanonicalName(rpcDTO.getInterfaceName());
            fullServiceDefinition.setTypes(typeDefinitions);
            fullServiceDefinition.setMethods(methodDefinitions);
            // 拼接服务描述
            MetadataIdentifier metadataIdentifier = new MetadataIdentifier();
            metadataIdentifier.setServiceInterface(rpcDTO.getInterfaceName());
            metadataIdentifier.setVersion(rpcDTO.getVersion());
            metadataIdentifier.setGroup(rpcDTO.getGroup());
            metadataIdentifier.setSide("provider");
            // 应用名统一为luckymock
            metadataIdentifier.setApplication("luckyMock");
            // 写元数据中心
            MetadataReport metadataReport = metadataReportFactory.getMetadataReport(URL.valueOf("zookeeper://101.201.232.80:2181"));
            metadataReport.storeProviderMetadata(metadataIdentifier, fullServiceDefinition);
            logger.info("注册RPC服务成功:{}", rpcDTO.getInterfaceName());
            return true;
    
        }
    
        /**
         * 该类很重,缓存
         *
         * @return @Service的信息
         */
        private static ServiceConfig<GenericService> getService() {
            if (null == service) {
                service = new ServiceConfig<>();
                // 注册中心配置
                RegistryConfig registryConfig = new RegistryConfig();
                registryConfig.setAddress("zookeeper://101.201.232.80:2181");
                service.setRegistry(registryConfig);
                // 应用配置
                ApplicationConfig application = new ApplicationConfig();
                application.setName("luckymock");
                service.setApplication(application);
                // 协议配置
                ProtocolConfig protocol = new ProtocolConfig();
                protocol.setName("dubbo");
                protocol.setPort(20880);
                service.setProtocol(protocol);
            }
            return service;
        }
    }
    

    经测试,下面这个test可以完成provider.xml一样的功能。

    @Test
        public void demoService() {
            // 现在用泛化实现,实现com.lucky.demo.api.DemoService
            // 如果想要保证消费者正常调用,消费者处 InterfaceName这个类必须存在。当然,我们作为服务提供者不需要真的有这个类
            // 什么情况消费者也没有这个接口呢?rpc测试平台,dubbo-admin已经做了。
            DubboRpcDTO dubboRpcDTO = new DubboRpcDTO();
            dubboRpcDTO.setGroup("dubbo");
            dubboRpcDTO.setVersion("1.0.0");
            dubboRpcDTO.setInterfaceName("com.lucky.demo.api.DemoService");
            List<DubboMethodDTO> methodList = new LinkedList<>();
            DubboMethodDTO dubboMethodDTO = new DubboMethodDTO();
            dubboMethodDTO.setMethodName("sayHello");
            dubboMethodDTO.setReturnType("java.lang.String");
            dubboMethodDTO.setArguments(new String[]{"java.lang.String"});
            methodList.add(dubboMethodDTO);
            dubboRpcDTO.setMethodList(methodList);
    
            try {
                dubboProvideService.rpcMockProvide(dubboRpcDTO);
                System.out.println("dubbo service started,enter any keys stop");
                Scanner scanner = new Scanner(System.in);
                scanner.next();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
  • 相关阅读:
    FJoi2017 1月21日模拟赛 comparison(平衡树+thita重构)
    juruo的刷题&博文祭
    [bzoj4247][挂饰] (动规+排序)
    FJoi2017 1月20日模拟赛 直线斯坦纳树(暴力+最小生成树+骗分+人工构造+随机乱搞)
    FJoi2017 1月20日模拟赛 交错和(等差数列+rmq)
    FJoi2017 1月20日模拟赛 恐狼后卫(口糊动规)
    【spoj 5971】lcmsum
    【bzoj 4025 改编版】graph
    【CF 718C】fibonacci
    【CF 482E】ELCA
  • 原文地址:https://www.cnblogs.com/hyry/p/12113778.html
Copyright © 2020-2023  润新知