zoukankan      html  css  js  c++  java
  • 一行代码搞定Dubbo接口调用

    本文来自网易云社区


    作者:吕彦峰

    在工作中我们经常遇到关于接口测试的问题,无论是对于QA同学还是开发同学都会有远程接口调用的需求。针对这种问题我研发了一个工具包,专门用于远程Dubbo调用,下面就让我们一起来学习一下。

    主要解决的问题

    • 针对QA同学来讲,如果对应的开发只是在某个任务中提供了接口,自己要怎么测试?如何保证该接口在测试环境和预发布环境都能测试通过?如果测试边界值?

    • 针对开发同学来讲,其他的业务方反馈说自己的接口在stabel_master上没有返回数据或者少了字段?stable_pre环境下数据返回不正确?线上数据不正确?开发要如何验证自己的数据是否有问题呢?

    remote-debug-util介绍

    1.将工具jar包引入到本地pom文件中,目前稳定版本问1.1.0

        <dependency>
            <groupId>com.netease.kaola</groupId>
            <artifactId>remote-debug-util</artifactId>
            <version>1.1.0</version>
        </dependency>

    2.通过工具类边写具体的远程调用逻辑 

    AppGoodsServiceFacade appGoodsServiceFacade = InvokerFactory.getInstance("hst_test7", AppGoodsServiceFacade.class, EnvEnum.TEST_ENV);

    通过以上调用,我们就拿到了hst_test7环境下的AppGoodsServiceFacade接口,具体要对接口进行怎么样的测试,就需要具体的开发自己确定了。
    需要说明的一点是这样子的:以上接口虽然指定了group和interface,但是没有质指定version,version默认取的1.0版本。如果又其他版本的接口可以这么调用:

    AppGoodsServiceFacade appGoodsServiceFacade = InvokerFactory.getInstance("hst_test7", AppGoodsServiceFacade.class,"2.0", EnvEnum.TEST_ENV);

    通过以上的调用我们就指定了version为2.0。 因为考拉的环境基本分为3个,TEST环境,PRE环境和ONLINE环境,所以通过最后的参数表示该环境为测试环境。如果接口调用的为PRE环境的话需要指定最后的环境参数为EnvEnum.PRE_ENV,Online环境的话指定为EnvEnum.ONLNIE_ENV。
    但是上面的方式在工作环境中会遇到如下问题:

    • 预发布环境和线上环境因为服务器注册服务时候是将自己的机房IP注册到zk上的,我们及时拿到了具体服务的URL信息,也无法掉的通。

    • 如果同一个服务器上同时暴露了两个服务,但是两个服务的接口内容不一定,我们怎么样掉到我们指定的那一个接口。


    针对第一个问题的解决方式:
    因为机房IP我们无法ping的通所以接口掉不通,因为我们可以才作用Dubbo的url直连模式进行调用,这样调用会相对于上面的情况稍微复杂一点:

        RemoteInvoker<GoodsFrontInventoryService> remoteInvoker = new RemoteInvoker("10.171.165.2:20880", "online","1.0", GoodsFrontInventoryService.class);
        GoodsFrontInventoryService goodsFrontInventoryService = remoteInvoker.getInvoker();

    通过这两行代码我们就拿到线上环境的GoodsFrontInventoryService接口,具体需要如何进行测试,对应的开发自己解决。
    参数说明:

        /**
         * @param address 远程服务的地址(host:port)
         * @param group 远程服务的分组信息 例如:stable_pre,online,hst_test1
         * @param version 接口的版本,默认为1.0
         * @param invokerClass 需要测试的接口
         */
        public RemoteInvoker(String address, String group, String version, Class invokerClass);

    针对address的说明:adderss的host部分需要是本地可以ping通的ip地址,端口号可以不指定,如果不指定的话会默认填充为20880,如果具体的服务端口不是20880的话,会默认从20880重试至20890。这样的话我们可以基本不同考虑服务端口的问题,除非端口号比较特殊的话需要自己在address中指定一下。

    我在自己的使用过程中发现一个问题:IP地址和端口号比较多,不同环境都有不用的地址记忆起来非常繁琐,因此希望可以只写一次,以后都可以方便的调用,因此开发了alias配置模式,方式如下:

    NewGoodsFacade newGoodsFacade = InvokerFactory.getInstance("goods.front.base-service2", NewGoodsFacade.class);

    通过以上的调用方式就可以获得goods-front工程的base-service2环境下的NewGoodsFacade接口。 

    需要说明解析规则:这种方式使用的前提工作是自己配置好前缀与对应地址的匹配规则,配置方式如下:
    在自己的resource或者resource/config或者reource/config/locol目录下创建remote-debug.properties配置文件,文件的内容大致如下:

        goods.front=com.netease.kaola.goods.constant.GoodsFrontConstant
        goods.compose=com.netease.kaola.goods.constant.GoodsComposeConstant

    通过以上的规则可以确定:由goods.front前缀可以找到GoodsFrontConstant这个配置文件,该文件的内容如下:

        public static final String GOODS_FRONT_ONLINE = "10.171.168.28:20880";    public static final String GOODS_FRONT_BETA = "10.164.104.66:20880";    public static final String GOODS_FRONT_BASE_SERVICE2 = "10.165.124.192:20880";    public static final String GOODS_FRONT_STABLE_DEV = "10.165.125.200:20880";    public static final String GOODS_FRONT_STABLE_PRE = "10.171.164.238:20881";    public static final String GOODS_FRONT_PRE5 = "10.171.160.28:20882";

    解析规则:
    例如:goods.front.base-service2 将所有的小写变为大写,.(英文点号)和-(中划线)都替换为_(下划线),因此goods.front.base-service2可以替换为GOODS_FRONT_BASE_SERVICE2。然后就可以找到对应的服务地址。
    解析规则比较固定,目前不支持自定义解析规则,基本只要在首次使用的时候引入配置文件,然后每次需要新增环境的时候把对应环境的地址信息加上就好,端口如果配置错了也没有问题,工具会进行一定的容错(在首次出错之后再次从20880重试到20890,直到有可用的接口返回为止)。

    基于自己的经验对于工具类的使用作出以下建议:

    1.测试环境下还是使用最简便的方式,直接配置group和interface调用
    2.常用工程下的接口测试建议配置alias模式,以方便自己以后对于其他接口的测试

    3.不常用的接口直接配置地址和版本信息直接调用

    有时候为了不想在自己的工程内添加多余的垃圾代码(因为远程接口调用的代码实际上既不属于业务代码,也不属于单元测试,在工程中存在意义有点奇怪),因此也可以将工具类clone到本地,然后直接在工具类本地工程中边写测试代码。
    工具类对于所有的模式都是支持,而且对于alias模式有更方便的支持,那就是可以直接在constant目录下配置指定的地址文件,不需要再次创建remote-debug.properties工具类。

    实现方式解读

    • 对于remote-debug工具类的定位就是一个纯粹的工具类,不需要启动Spring来加载dubbo的配置信息,就跟调用自己本地写的简单方法一样,基本上点击run之后就立马会有结果响应。

    • 除了dubbo和zk之外没有任何依赖,做到足够小,足够精。


    工具实现的核心来时Dubbo中ReferenceConfig提供的直连模式,基本的运行原理如下:
    如果是注册中心模式的情况下:

    • 通过提供的group和version,interface信息构造consumer端的直连URL。

    • 根据提供的环境信息连接到对应的zk集群

    • 根据URL信息从Registry中找到与其匹配的提供者URL列表

    • 遍历所有的URL信息直到拿到可用的provider为止。


    如果是alias或者基本配置模式:

    • 解析alias信息,找到对应的地址(如果需要)

    • 根据配置信息构造基本的URL

    • 通过Echo来测试接口的可用性,并负责重试。

    • 如果有异常的话将异常转换为可读的异常并返回/返回代理结果。


    下面对于核心的处理逻辑进行介绍:

        /**
         * 获得可用的RemoteInvoker对象
         * @param referenceConfig 这里已经将对应的配置信息转换为ReferenceConfig对象
         * @return
         */
        private T getAvaiableRemoteInvoker(ReferenceConfig<T> referenceConfig) {
            T result = null;
            EchoService echoService = null;        try {
                result = referenceConfig.get();            //回响测试
                echoService = (EchoService)result;
                echoService.$echo("OK");
            } catch (Exception e) {            //this.invoker主要为自定义的配置类
                String host = this.invokerConfig.getAddress().split(":")[0];            for (int i = defaultPort; i < endPort; i++) {
                    invokerConfig.setAddress(host + ":" + i);
                    referenceConfig = initRefConfig(invokerConfig, false);                try {
                        result = referenceConfig.get();
                        echoService = (EchoService)result;
                        echoService.$echo("OK");                    break;
                    } catch (Exception e1){                    continue;
                    }
                }
            }        if (result == null) {            throw new RuntimeException("Get remote invoker error, please check your network(host,port,VPN) by ping "
                        + invokerConfig.getHost() + "or telnet host ip");
            }        return result;
        }
        /**
         * 根据key读取配置的host:port 规则:goods.front.stable_master -> GOODS_FRONT_STABLE_MASTER
         * @param key
         * @return
         */
        public static String getAddress(String key) {
            checkKeyNotNull(key);
            String[] keyGroup = key.split("\.");
            StringBuilder sb = new StringBuilder();
            sb.append(".");        //读取数据直到最后一个点号为止
            for (int i = 0; i < keyGroup.length - 1; i++) {
                sb.append(keyGroup[i].substring(0, 1).toUpperCase());
                sb.append(keyGroup[i].substring(1));
            }
            sb.append("Constant");
            String className = constantDir + sb.toString();        //先从工程constant目录下读取数据,如果读不到就从remote-debug.properties指定的配置文件中读数据
            try {
                String result = loadAddress(key, className);            return result;
            } catch (Exception e) {            return getAddressByDefinedProp(key);
            }
        }
        /**
         * 根据配置信息拿到最红的接口代理
         * @param group 
         * @param version
         * @param interfaceClass
         * @param env 环境
         * @return
         */
        public static <T> T getInstance(String group, String version, Class interfaceClass, EnvEnum env) {
            URL url = URLUtil.valueOf(group, version, interfaceClass);
            String registryAddress = null;        if (env == null || env.getType() == EnvEnum.TEST_ENV.getType()) {
                registryAddress = ZkAddressEnum.TEST_ZK_ADDRESS.getAddress();
            } else if (env.getType() == EnvEnum.PRE_ENV.getType()) {
                registryAddress = ZkAddressEnum.PRE_ZK_ADDRESS.getAddress();
            } else {
                registryAddress = ZkAddressEnum.ONLINE_ZK_ADDRESS.getAddress();
            }        if (registryAddress == null) {            throw new RuntimeException("can not find registry address");
            }
            URL registryUrl = URL.valueOf(registryAddress);
            Registry registry = registryFactory.getRegistry(registryUrl);        if (registry == null) {            throw new RuntimeException("can not find registry, registryUrl is " + url.toFullString());
            }
            List<URL> providerUrls = registry.lookup(url);        //可用的provider可能有多个,因此会逐渐尝试直到有可用的为止
            if (providerUrls != null && providerUrls.size() > 0) {
                T result = getInvoker(providerUrls);            if (result == null) {                throw new RuntimeException("can not find class " + interfaceClass.getName() + "in registry");
                }            return result;
            }        throw new RuntimeException("can not find matched url in registry");
        }

    对于源码也只是设计了一些重要的流程,因为篇幅有限所有不能把所有的内容都讲解清楚。

    基本上通过remote-debug的调用,文章开头提出的两个问题都可以得到完美的解决,作为商品前台的开发,我经常需要向其他业务方证明我或者他人的接口是没有问题的,基本上我都是通过工具类调用接口,然后返回数据给他们看。尤其是当我遇到线上问题的时候,你会发现这种方式查看接口返回数据是有多么优雅~~~~

    如果,你曾被telnet拼参数时候的蛋疼气到过;
    如果,你曾经在SOA上调用某个参数复杂的接口并且心情错乱过;
    如果,你是一个以简为美的技术哥哥或者QA姐姐;
    请速速转移到remote-debug工具包,开启远程调用的新生活吧。


    本文来自网易云社区,经作者吕彦峰授权发布                         



    相关文章:
    【推荐】 MySQL Group Replication数据安全性保障

  • 相关阅读:
    1.27
    1.25
    Representation Learning with Contrastive Predictive Coding
    Learning a Similarity Metric Discriminatively, with Application to Face Verification
    噪声对比估计(负样本采样)
    Certified Adversarial Robustness via Randomized Smoothing
    Certified Robustness to Adversarial Examples with Differential Privacy
    Dynamic Routing Between Capsules
    Defending Adversarial Attacks by Correcting logits
    Visualizing Data using t-SNE
  • 原文地址:https://www.cnblogs.com/zyfd/p/9626752.html
Copyright © 2011-2022 走看看