一行代码搞定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工具包,开启远程调用的新生活吧。
本文来自网易云社区,经作者吕彦峰授权发布
相关文章:
【推荐】 3分钟掌握一个有数小技能:回头客分析
【推荐】 两分钟了解Docker的优势
【推荐】 网易易盾验证码的安全策略
一行代码搞定Dubbo接口调用的更多相关文章
- Asp.Net Core 轻松学-一行代码搞定文件上传 JSONHelper
Asp.Net Core 轻松学-一行代码搞定文件上传 前言 在 Web 应用程序开发过程中,总是无法避免涉及到文件上传,这次我们来聊一聊怎么去实现一个简单方便可复用文件上传功能:通过创建 ...
- 开源作品ThinkJDBC—一行代码搞定数据库操作
1 简介 ThinkJD,又名ThinkJDBC,一个简洁而强大的开源JDBC操作库.你可以使用Java像ThinkPHP框架的M方法一样,一行代码搞定数据库操作.ThinkJD会自动管理数据库连接, ...
- 一行代码搞定Adapter
15年Google I/O大会发不了三个重要支持库 >Material design (Android Support Design) >百分比布局:Percent support lib ...
- 一行代码搞定 FTP 服务
环境搭建: python windows/linux pip install pyftpdlib (安装失败请到这里下载:https://pypi.python.org/pypi/pyftpdlib/ ...
- 一行代码搞定所有屏幕适配AbViewUtil
适配原理:抛弃google提供的dip理论与多套图片与布局方案,采用与UI设计师通用的px作为标准单位,原理是将UI设计师的设计图与当前查看的手机或其他设备的屏幕像素尺寸进行换算,得到缩放比例,在Ac ...
- 【springboot+easypoi】一行代码搞定excel导入导出
原文:https://www.jianshu.com/p/5d67fb720ece 开发中经常会遇到excel的处理,导入导出解析等等,java中比较流行的用poi,但是每次都要写大段工具类来搞定这事 ...
- easypoi 一行代码搞定excel导入导出
开发中经常会遇到excel的处理,导入导出解析等等,java中比较流行的用poi,但是每次都要写大段工具类来搞定这事儿,此处推荐一个别人造好的轮子[easypoi],下面介绍下“轮子”的使用. pom ...
- 一行代码搞定WordPress面包屑导航breadcrumb
有好几位网友在问WordPress面包屑导航breadcrumb怎么操作,网上有些教程是去function文件中定义,其实不用那么复杂,很简单一行代码就能搞定.下面随ytkah一起来看看.如果是单页, ...
- 初识sa-token,一行代码搞定登录授权!
前言 在java的世界里,有很多优秀的权限认证框架,如Apache Shiro.Spring Security 等等.这些框架背景强大,历史悠久,其生态也比较齐全. 但同时这些框架也并非十分完美,在前 ...
随机推荐
- umbraco
在任意页面获取根节点 var locale = CurrentPage.Site(); 遍历根节点 @foreach (var module in CurrentPage.Site().Childre ...
- TortoiseSVN文件夹及文件图标不显示解决方法(兼容Window xp、window7)
最近遇到TortoiseSVN图标(如上图:增加文件图标.文件同步完成图标等)不显示问题,网上找到的解决方法试了很多都无法真正解决,最后总结了一下,找到了终极解决方案,当然此方案也有弊端,接下来我们就 ...
- linux 下的mysql 连接报错
报错: Fri Jul 28 16:28:52 CST 2017 WARN: Establishing SSL connection without server’s identity verific ...
- C++11之nullptr
[C++11空指针] 1.NULL的问题 class Test { public: void TestWork(int index) { std::cout << "TestWo ...
- Android Studio 无法正确引入包内存在的类
Android Studio 无法识别同一个 package 里的类,显示为红色,但是 compile 没有问题. 重启,rebuild,clean都没有用. 多半是因为 Android Studio ...
- 第一个MFC实例:计算圆周长和圆面积
一.基于Microsoft MFC的编程方法 MFC是微软基础类库(Microsoft Foundation Class)的缩写.与API不同,MFC不是Windows操作系统的组成部分,而是微软公司 ...
- 值得一做》关于一道暴搜BZOJ1024(EASY+)
为什么要写这道题的DP捏? 原因很简单,因为为原来在openjudge上有一道题叫分蛋糕,有一个思路和这道题很像:“分锅”. 分锅:即为考虑计算当前情况的最优解时,把当前状态结果,分散为考虑当前状态的 ...
- Luogu 4725 【模板】多项式对数函数
继续补全模板. 要求 $$g(x) = ln f(x)$$ 两边求导, $$g'(x) = \frac{f'(x)}{f(x)}$$ 然后左转去把多项式求导和多项式求逆的模板复制过来,就可以计算出$g ...
- code1044 导弹拦截
分析: 这套系统最多能拦截的导弹数 就是 导弹高度的最长不上升子序列(下降或相等) 如果要拦截所有导弹最少要配备多少套这种导弹拦截系统 就是 导弹高度的最长上升子序列 因此直接用dp求就可以了 a[i ...
- python移除系统多余大文件-乾颐堂
文件多了乱放, 突然有一天发现硬盘空间不够了, 于是写了个python脚本搜索所有大于10MB的文件,看看这些大文件有没有重复的副本,如果有,全部列出,以便手工删除 使用方式 加一个指定目录的参数 比 ...