1、什么是 Dubbo?
Dubbo 最开始是利用于淘宝网,由阿里巴巴开源的一款优良的高性能服务框架,由 Java 开发,起初奉献给了 Apache 开源基金会组织。
上面以官网的一个阐明来理解一下架构的演变过程,从而理解 Dubbo 的诞生起因:
繁多利用架构
当网站流量很小时,只需一个利用,将所有性能都部署在一起,以缩小部署节点和老本。此时,用于简化增删改查工作量的数据拜访框架 (ORM) 是要害。
垂直利用架构
当访问量逐步增大,繁多利用减少机器带来的加速度越来越小,晋升效率的办法之一是将利用拆成互不相干的几个利用,以晋升效率。此时,用于减速前端页面开发的 Web 框架 (MVC) 是要害。
分布式服务架构
当垂直利用越来越多,利用之间交互不可避免,将外围业务抽取进去,作为独立的服务,逐步造成稳固的服务中心,使前端利用能更疾速的响应多变的市场需求。此时,用于进步业务复用及整合的分布式服务框架 (RPC) 是要害。
流动计算架构
当服务越来越多,容量的评估,小服务资源的节约等问题逐步浮现,此时需减少一个调度核心基于拜访压力实时治理集群容量,进步集群利用率。此时,用于进步机器利用率的资源调度和治理核心 (SOA) 是要害。
2、Dubbo 架构简介
Dubbo 比拟有特点的中央就是这个注册核心,平时咱们测试较多的 HTTP 接口,间接申请接口,调用后端服务即可;而 Dubbo 是要先走注册核心获取服务的地位,上面来举个现实生活中的例子来阐明。
事实举例
好比大家平时约敌人一起进来吃饭,据说川菜馆“赠李白”不错,而后须要找这家饭店在哪(用小蓝或小黄 App),晓得了具体的地址才登程,至于是走路,打车还是骑车,就随便了。
这里 App 就相当于注册核心(Registry),咱们这群吃货就是消费者(Consumer),商家属于生产者(Provider)。商家把本人的信息注册在 App 上,消费者依据 App 查问到商家的信息,再依据信息找到商家进行生产。
2.1、Zookeeper 简介
之前常常有小伙伴问我 zk 干啥的?怎么用?上面就来简略理解一哈:
ZK,全称就是 zookeeper,是 Apache 软件基金会的一个软件我的项目,它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。
上面的图示也能够清晰的阐明 zk 的部署和工作的一些形式(具体的技术细节需要的话能够针对 zk 专门搜寻学习):
Leader:集群工作的外围,事务申请的惟一调度和解决者,保障事务处理的程序性。对于有写操作的申请,需对立转发给 Leader 解决。Leader 需决定编号执行操作。
Follower:解决客户端非事务申请,转发事务申请转发给 Leader,参加 Leader 选举。
Observer 观察者:进行非事务申请的独立解决, 对于事务申请, 则转发给 Leader 服务器进行解决. 不参加投票。
3、什么是 Dubbo 接口?
所谓的 Dubbo 接口,其实就是一个个 Dubbo 服务中的办法,而测试 Dubbo 接口就相当于咱们测试人员充当消费者或者发明消费者去 ” 生产 ” 这个办法。
具体的形式有很多,代码、工具、命令皆可,在接下来的内容中来一一演示。
4、Dubbo 接口测试(发明消费者)
以下我将以本地的一个简略的 Dubbo 服务 demo 为例,演示 Dubbo 测试的各种办法。
interface 只有两个,别离是 OrderService 和 UserService
OrderService
package com.qinzhen.testmall.service;
import com.qinzhen.testmall.bean.UserAddress;
import java.util.List;
public interface OrderService {
/**
- 初始化订单
- @param userID
*/
public List<UserAddress> initOrder(String userID);
}
UserService
package com.qinzhen.testmall.service;
import com.qinzhen.testmall.bean.UserAddress;
import java.util.List;
/**
- 用户服务
*/
public interface UserService {
/**
- 依照 userId 返回所有的播种地址
- @param userId
- @return
*/
public List<UserAddress> getUserAddressList(String userId);
/**
- 返回所有的播种地址
- @param
- @return
*/
public List<UserAddress> getUserAddressList();
}
JavaBean 对象 UserAddress 如下:
package com.qinzhen.testmall.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
@AllArgsConstructor
@Data
public class UserAddress implements Serializable {
private Integer id;
private String userAddress; // 用户地址
private String userId; // 用户 ID
private String consignee; // 收货人
private String phoneNum; // 电话号码
private String isDefault; // 是否为默认地址 Y- 是 N- 否
public UserAddress(){
}
}
创立一个 provider 来实现 UserService 的 Interface:
实现办法中,依据 id 返回对应的用户地址信息即可:···package com.qinzhen.testmall.bootuserserviceprovider.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.qinzhen.testmall.bean.UserAddress;
import com.qinzhen.testmall.service.UserService;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@Component
@Service // 裸露服务
public class UserServiceImpl implements UserService {
private UserAddress userAddress1 = new UserAddress(1, “ 杭州市西湖区 XX 公司 ”, “1”, “qz”, “12345678”, “Y”);
private UserAddress userAddress2 = new UserAddress(2, “ 杭州市西湖区花园 ”, “2”, “qz”, “12345678”, “N”);
@Override
public List<UserAddress> getUserAddressList(String userId) {
if (userId.equals(“1”)){
return Collections.singletonList(userAddress1);
}
else if (userId.equals(“2”)){
return Collections.singletonList(userAddress2);
}
else {
return Arrays.asList(userAddress1, userAddress2);
}
}
@Override
public List<UserAddress> getUserAddressList(){
return Arrays.asList(userAddress1, userAddress2);
}
}
···
4.1 Java consumer 代码
上面咱们编写 consumer 代码,让服务消费者去注册核心订阅服务提供者的服务地址,以 RPC 形式,获取近程服务代理,从而执行近程办法,代码也很简略,如下:
代码构造:
实现场景就是实现 OrderService 中的 initOrder() 办法,初始化订单,初始化中间接调用 userService 的 getUserAddressLis(java.lang.String) 办法, 具体代码如下:
package com.qinzhen.testmall.service.impl;
import com.qinzhen.testmall.bean.UserAddress;
import com.qinzhen.testmall.service.OrderService;
import com.qinzhen.testmall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
- 1、讲服务提供者注册到注册核心(裸露服务)
- 1)导入 dubbo 依赖:操作 zookeeper 的客户端(curator)
- 2、让服务消费者去注册核心订阅服务提供者的服务地址
*/
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
UserService userService;
public List<UserAddress> initOrder(String userId) {
//1. 查问用户的收货地址
System.out.println(“ 用户 ID 为:” + userId);
List<UserAddress> userAddressList = userService.getUserAddressList(userId);
return userAddressList;
}
}
consumer MainApplication
package com.qinzhen.testmall;
import com.qinzhen.testmall.bean.UserAddress;
import com.qinzhen.testmall.service.OrderService;
import com.qinzhen.testmall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.List;
/**
- 1、将服务提供者注册到注册核心(裸露服务)
- 1)导入 dubbo 依赖:操作 zookeeper 的客户端(curator)
- 2、让服务消费者去注册核心订阅服务提供者的服务地址
*/
@Service
public class MainApplication {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {“consumer.xml”});
context.start();
OrderService orderService = context.getBean(OrderService.class); // 获取近程服务代理
List<UserAddress> userAddresses = orderService.initOrder(“3”);// 执行近程办法
System.out.println(userAddresses);
System.out.println(“ 调用实现。。。”);
System.in.read();
}
}
consumer.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://code.alibabatech.com/schema/dubbo”
xmlns:context=”http://www.springframework.org/schema/context”
xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.or…
http://code.alibabatech.com/s… http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.or… http://www.springframework.org/schema/context/spring-context.xsd”>
<context:component-scan base-package=”com.qinzhen.testmall.service.impl”></context:component-scan>
<dubbo:application name=”order-service-comsumer”></dubbo:application>
<dubbo:registry address=”zookeeper://127.0.0.1:2181″></dubbo:registry>
<!– 申明须要近程调用近程服务的接口,生成近程服务代理 –>
<dubbo:reference interface=”com.qinzhen.testmall.service.UserService” id=”userService”></dubbo:reference>
</beans>
实例演示:
首先确保 provider 已启动:
运行 consumer , 能够看到胜利调用到 dubbo 办法,获取地址列表信息:
4.2 telnet+invoke
咱们应用 telnet 命令能够间接拜访对应的服务,然而前提是你须要晓得服务对应的 ip+ 端口。
如下配置文件,咱们能够晓得服务裸露在本地的 20880 端口
dubbo.application.name=boot-user-service-provider
dubbo.registry.address=127.0.0.1:2181
dubbo.registry.protocol=zookeeper
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
应用 telnet 命令进行拜访,如下呈现 Dubbo 字样时阐明连贯胜利:
% telnet localhost 20880
Trying ::1…
Connected to localhost.
Escape character is ‘^]’.
dubbo>
Dubbo 内建的 telnet 命令的阐明和用法如下
ls
ls: 显示服务列表
ls -l: 显示服务详细信息列表
ls XxxService: 显示服务的办法列表
ls -l XxxService: 显示服务的办法详细信息列表
dubbo>ls
com.qinzhen.testmall.service.UserService
dubbo>ls -l
com.qinzhen.testmall.service.UserService -> dubbo://192.168.2.xxx:20880/com.qinzhen.testmall.service.UserService?anyhost=true&application=boot-user-service-provider&bind.ip=192.168.2.xxx&bind.port=20880&dubbo=2.6.2&generic=false&interface=com.qinzhen.testmall.service.UserService&methods=getUserAddressList&pid=55472&qos.enable=false&side=provider×tamp=1615088321885
dubbo>dubbo>ls com.qinzhen.testmall.service.UserService
getUserAddressList
getUserAddressList
dubbo>dubbo>ls -l com.qinzhen.testmall.service.UserService
java.util.List getUserAddressList(java.lang.String)
java.util.List getUserAddressList()
invoke
invoke XxxService.xxxMethod(1234, “abcd”, {“prop” : “value”}): 调用服务的办法
invoke com.xxx.XxxService.XxxService.xxxMethod(1234, “abcd”, {“prop” : “value”}): 调用全门路服务的办法
invoke xxxMethod(1234, “abcd”, {“prop” : “value”}): 调用服务的办法(主动查找蕴含此办法的服务)
invoke xxxMethod({“name”:”zhangsan”,”age”:12,”class”:”org.apache.dubbo.qos.legacy.service.Person”}) : 当有参数重载,或者类型转换失败的时候,能够通过减少 class 属性指定须要转换类
当参数为 Map<Integer,T>,key 的类型为 Integer 时,倡议指定类型。例如 invoke com.xxx.xxxApiService({“3”:0.123, “class”:”java.util.HashMap”})
而后咱们应用 invoke 命令对 dubbo 办法 getUserAddressList()进行调用,如下:
dubbo>invoke getUserAddressList()
[{“consignee”:”qz”,”id”:1,”isDefault”:”Y”,”phoneNum”:”12345678″,”userAddress”:” 杭州市西湖区 xx 公司 ”,”userId”:”1″},{“consignee”:”qz”,”id”:2,”isDefault”:”N”,”phoneNum”:”12345678″,”userAddress”:” 杭州市西湖区 xx 花园 ”,”userId”:”2″}]
dubbo>invoke getUserAddressList(“1”)
[{“consignee”:”qz”,”id”:1,”isDefault”:”Y”,”phoneNum”:”12345678″,”userAddress”:” 杭州市西湖区 xx 公司 ”,”userId”:”1″}]
elapsed: 14 ms.
学习链接:
其余 Telnet 命令相干操作,须要可参考 Dubbo 官网:
https://dubbo.apache.org/zh/d… 1
4.3 JMeter
对于 JMeter 测试 Dubbo 接口的办法,可参考往期文章:
《基于 Jmeter 实现 Dubbo 接口的测试》6
4.4 Dubbo-admin
对于 Dubbo-admin 的装置调试,可参考文章:
《dubbo-admin+zookeeper 的环境搭建实操与 Could not extract archive 报错踩坑》
dubbo-admin+zookeeper 的环境搭建实操与 Could not extract archive 报错踩坑_TesterAllen 的博客 -CSDN 博客_extract_archive 1
4.5 泛化调用
测试 Dubbo 服务的时候,咱们须要服务端的同学给咱们提供 API,没有这个 API 咱们是测不了的,而为了解决这个问题,Dubbo 官网又给咱们提供了另外一个办法,就是泛化调用,来看看官网的解释:
泛化调用的应用
Dubbo 给咱们提供了一个接口 GenericService,这个接口只有一个办法,就是 $invoke,它承受三个参数,别离为办法名、办法参数类型数组和参数值数组;
上面咱们间接上代码演示:
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.rpc.service.GenericService;
import org.junit.jupiter.api.Test;
public class TestDemo {
@Test
void testDubboGenericService(){
// 援用近程服务
// 该实例很分量,外面封装了所有与注册核心及服务提供方连贯,请缓存
ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
// 弱类型接口名
reference.setApplication(new ApplicationConfig(“order-service-consumer”));
reference.setInterface(“com.qinzhen.testmall.service.UserService”);
reference.setRegistry(new RegistryConfig(“zookeeper://127.0.0.1:2181”));
// 申明为泛化接口
reference.setGeneric(true);
// 用 org.apache.dubbo.rpc.service.GenericService 能够代替所有接口援用
GenericService genericService = reference.get();
Object result = genericService.$invoke(“getUserAddressList”, new String[] {“java.lang.String”}, new Object[] {“2”});
System.out.println(result);
}
}
运行后咱们来看看后果,咦~ 也胜利拜访了:
泛化调用的原理
咱们通过 debug 跟入 Dubbo 的源码中,能够失去如下的调用链:
服务生产端:
服务提供端:
从下面的调用链能够晓得实现一次泛化调用,Dubbo 框架经验了很多过滤器,咱们别离选取两端链路中的最初一步的 Filter 来简略理解一下泛化调用做了哪些事.
简化后的调用关系就如下:
先来看 consumer 端的 GenericImplFilter,大略看下外围的解决步骤:
// 判断是否为泛化调用
if (invocation.getMethodName().equals(Constants.$INVOKE)
&& invocation.getArguments() != null
&& invocation.getArguments().length == 3
&& ProtocolUtils.isGeneric(generic)) {
// 获取泛化调用参数
Object[] args = (Object[]) invocation.getArguments()[2];
// 判断是否为 nativejava 形式
if (ProtocolUtils.isJavaGenericSerialization(generic)) {
for (Object arg : args) {
if (!(byte[].class == arg.getClass())) {
error(byte[].class.getName(), arg.getClass().getName());
}
}
// 判断是否为 bean 形式
} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
for (Object arg : args) {
if (!(arg instanceof JavaBeanDescriptor)) {
error(JavaBeanDescriptor.class.getName(), arg.getClass().getName());
}
}
}
// 设置为泛化调用形式
((RpcInvocation) invocation).setAttachment(
Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));
}
// 发动近程调用
return invoker.invoke(invocation);
再来看 provider 端的 GenericFilter,大略的外围解决步骤如下:
package com.alibaba.dubbo.rpc.filter;
import …
/**
- GenericInvokerFilter.
*/
@Activate(group = Constants.PROVIDER, order = -20000)
public class GenericFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
// 判断是否为泛化申请
if (inv.getMethodName().equals(Constants.$INVOKE)
&& inv.getArguments() != null
&& inv.getArguments().length == 3
&& !ProtocolUtils.isGeneric(invoker.getUrl().getParameter(Constants.GENERIC_KEY))) {
// 获取参数名称、参数类型、参数值
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(Constants.GENERIC_KEY);
// 泛化类型为空的话就应用 generic=true 的形式
if (StringUtils.isEmpty(generic)
|| ProtocolUtils.isDefaultGenericSerialization(generic)) {
args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
// 判断是否为 generic=nativejava 形式
} else if (ProtocolUtils.isJavaGenericSerialization(generic)) {
for (int i = 0; i < args.length; i++) {
if (byte[].class == args[i].getClass()) {
try {
UnsafeByteArrayInputStream is = new UnsafeByteArrayInputStream((byte[]) args[i]);
args[i] = ExtensionLoader.getExtensionLoader(Serialization.class)
.getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA)
.deserialize(null, is).readObject();
} catch (Exception e) {
。。。
}
} else {
。。。
}
}
// 判断是否为 generic=bean 形式
} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof JavaBeanDescriptor) {
args[i] = JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) args[i]);
} else {
。。。
}
}
}
// 传递申请,执行服务
Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
。。。
}
下面的代码很多,着重来提取一小段看一下:
Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
Class<?>[] params = method.getParameterTypes();
从下面的代码中咱们便能够得悉原来泛化调用中应用了 Java 的反射技术来获取对应的办法信息实现调用的
4.6 用 Python 来测试 Dubbo
咱们晓得 Dubbo 是个 Java 我的项目,测试 Dubbo 就是模仿消费者去调用 Dubbo 的 Java 办法,那不言而喻,用 Python 是必定没法去间接调用 Java 的,然而在日常的工作中,很多小伙伴可能是 Pyhton 技术栈的,或者因为一些测试条件限度亦或历史起因,必须要将 Dubbo 测试用 Python 实现以满足各种接口测试的一个组合。
- python-hessian 库
Dubbo 是反对 hessian+http 协定调用的,hessian 是一种二进制序列化的形式。
理解到能够通过这种形式实现,具体没有尝试过,还须要开发在我的项目中将序列化的形式改为 hessian,并且须要晓得 URL,有趣味的小伙伴能够去理解一下。
- telnetlib 库
telnetlib 是 Python3 自带的一个库,能够调用 telnet 命令,其实也就相当于下面说到的应用 telnet 形式拜访 dubbo 的办法
- 开发 dubbo 测试服务
咱们能够应用 Java 来开发一个 Dubbo 测试的 Web 服务,实现上就能够应用 Dubbo 的泛化调用,而后咱们再用 HTTP 拜访的模式去拜访这个服务,将咱们的测试参数信息传过来,剩下的就交给 Java 去解决就好了。
这样通过封装设计后,能够实现 Python 端的使用者在拜访 Dubbo 时就像在测试 HTTP 接口一样(例如 Python 的 request 库);另外服务的 IP、端口、注册核心等信息都不必呈现在测试的工程中,只须要用环境标签做辨别,在服务端进行申请转发即可,也保障了肯定安全性。
大体上的思路流程如下:
以上,期待与大家多交流学习。