目录
1. 什么是Dubbo?
阿里巴巴中间件开源的一款高性能、轻量级的开源Java RPC(是远程过程调用Remote Procedure Call)微服务框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
Dubbo 服务框架的工作流程如下:
- 提供者在启动时,在注册中心注册服务。
- 消费者在启动时,在注册中心订阅所需的服务。
- 注册中心返回提供者地址列表给消费者。如果提供者发生变更,注册中心将推送变更数据给消费者。
- 消费者基于软负载均衡算法,从提供者地址列表中选一个提供者进行调用。
2. dubbo配置
Dubbo 采用全 Spring 配置方式,透明化接入应用,对应用没有任何 API 侵入,只需用 Spring 加载 Dubbo 的配置即可,Dubbo 基于Spring的schema扩展进行加载。目前项目中使用application.yml文件配置
provider方配置
spring:
dubbo:
consumer:
filter: tracing,log,insuranceDubboMonitorFilter
timeout: 30000
check: false // 关闭某个服务的启动时检查
provider:
filter: tracing,log,insuranceDubboMonitorFilter
timeout: 30000
retries: 1
registry:
address: zookeeper:... // 使用zookeeper广播注册中心暴露服务地址
protocol: // 用dubbo协议在20880端口暴露服务
name: dubbo
port: 20880
server: true // 声明需要暴露的服务接口
application: // 提供方应用信息,用于计算依赖关系
name: // 项目名称
base-package: com.zm // 基础包名
scan: com.zm.test // 扫描包名称
reference:
check: false
consumer方配置
spring:
dubbo:
consumer:
timeout: 60000
filter: tracing,log
check: false
application:
name: // 消费方应用名,用于计算依赖关系
registry:
address: zookeeper: // 使用zookeeper广播注册中心暴露发现服务地址
protocol:
name: dubbo
port: 21781
server: true
base-package: com.zm
scan: com.zm.test
reference: // 生成远程服务代理
check: false // 关闭某个服务的启动时检查
配置之间的关系
标签 | 用途 | 解释 |
---|---|---|
<dubbo:service/> | 服务配置 | 用于暴露一个服务,定义服务的元信息,一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心 |
<dubbo:reference/> | 引用配置 | 用于创建一个远程服务代理,一个引用可以指向多个注册中心 |
<dubbo:protocol/> | 协议配置 | 用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受 |
<dubbo:application/> | 应用配置 | 用于配置当前应用信息,不管该应用是提供者还是消费者 |
<dubbo:module/> | 模块配置 | 用于配置当前模块信息,可选 |
<dubbo:registry/> | 注册中心配置 | 用于配置连接注册中心相关信息 |
<dubbo:monitor/> | 监控中心配置 | 用于配置连接监控中心相关信息,可选 |
<dubbo:provider/> | 提供方配置 | 当 ProtocolConfig 和 ServiceConfig 某属性没有配置时,采用此缺省值,可选 |
<dubbo:consumer/> | 消费方配置 | 当 ReferenceConfig 某属性没有配置时,采用此缺省值,可选 |
<dubbo:method/> | 方法配置 | 用于 ServiceConfig 和 ReferenceConfig 指定方法级的配置信息 |
<dubbo:argument/> | 参数配置 | 用于指定方法参数配置 |
不同粒度配置的优先关系
- 方法级优先,接口级次之,全局配置再次之。
- 如果级别一样,则消费方优先,提供方次之。
服务提供方配置,通过 URL 经由注册中心传递给消费方
3. Dubbo SPI
SPI:service provider interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。Dubbo重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置类似内容如下。
3.1 源码实现
3.1.2 获取所有的拓展类
//从缓存中获取与拓展类对应的ExtensionLoader,若缓存未命中,则创建一个新的实例
ExtensionLoader: getExtensionLoader(classType)
//获取拓展类对象
ExtensionLoader: getExtension(name)
// Holder,顾名思义,用于持有目标对象,检查缓存
Holder<Object> holder = cachedInstances.get(name)
// 缓存未命中则创建拓展对象
->ExtensionLoader: createExtension(String name)
// 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表
->ExtensionLoader: getExtensionClasses()
//获取所有的拓展类
->ExtensionLoader: loadExtensionClasses()
// 加载指定文件夹下的配置文件
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); //META-INF/dubbo/internal/
loadDirectory(extensionClasses, DUBBO_DIRECTORY); //META-INF/dubbo/
loadDirectory(extensionClasses, SERVICES_DIRECTORY); //META-INF/services/
//通过反射创建实例
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
//向实例中注入依赖
injectExtension(instance);
//将拓展对象包裹在相应的 Wrapper 对象中
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
3.1.3 dubbo IOC
Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
// 遍历目标类的所有方法
for (Method method : instance.getClass().getMethods()) {
// 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
// 获取 setter 方法参数类型
Class<?> pt = method.getParameterTypes()[0];
try {
// 获取属性名,比如 setName 方法对应属性名 name
String property = method.getName().length() > 3 ?
method.getName().substring(3, 4).toLowerCase() +
method.getName().substring(4) : "";
// 从 ObjectFactory 中获取依赖对象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 通过反射调用 setter 方法设置依赖
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method...");
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
4. SPI 自适应拓展
有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。这听起来有些矛盾。
拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载。
对于这个矛盾的问题,Dubbo 通过自适应拓展机制很好的解决了。自适应拓展机制的实现逻辑比较复杂:首先 Dubbo 会为拓展接口生成具有代理功能的代码;然后通过 javassist 或 jdk 编译这段代码,得到 Class 类;最后再通过反射创建代理类,整个过程比较复杂。
public interface WheelMaker {
Wheel makeWheel(URL url);
}
//WheelMaker 接口的自适应实现类
public class AdaptiveWheelMaker implements WheelMaker {
public Wheel makeWheel(URL url) {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
// 1.从 URL 中获取 WheelMaker 名称
String wheelMakerName = url.getParameter("Wheel.maker");
if (wheelMakerName == null) {
throw new IllegalArgumentException("wheelMakerName == null");
}
// 2.通过 SPI 加载具体的 WheelMaker
WheelMaker wheelMaker = ExtensionLoader
.getExtensionLoader(WheelMaker.class).getExtension(wheelMakerName);
// 3.调用目标方法
return wheelMaker.makeWheel(url);
}
}
AdaptiveWheelMaker 所代理的对象是在 makeWheel 方法中通过 SPI 加载得到的。makeWheel 方法主要做了三件事情:
- 从 URL 中获取 WheelMaker 名称
- 通过 SPI 加载具体的 WheelMaker 实现类
- 调用目标方法
有了这个自适应实现类,来看看具体的使用
public interface CarMaker {
Car makeCar(URL url);
}
public class RaceCarMaker implements CarMaker {
WheelMaker wheelMaker;
// 通过 setter 注入 AdaptiveWheelMaker
public setWheelMaker(WheelMaker wheelMaker) {
this.wheelMaker = wheelMaker;
}
public Car makeCar(URL url) {
Wheel wheel = wheelMaker.makeWheel(url);
return new RaceCar(wheel, ...);
}
}
RaceCarMaker 持有一个 WheelMaker 类型的成员变量,
第一步:在程序启动时,将AdaptiveWheelMaker(WheelMaker接口的自适应实现类)通过 setter 方法注入到 RaceCarMaker 中。
第二步:在运行时,假设有这样一个 url 参数传入:
dubbo://192.168.0.101:20880/XxxService?wheel.maker=MichelinWheelMaker
第三步:RaceCarMaker 的 makeCar 方法将上面的 url 作为参数传给 AdaptiveWheelMaker 的 makeWheel 方法,makeWheel 方法从 url 中提取 wheel.maker 参数,得到 MichelinWheelMaker。
第四步:通过 SPI 加载配置名为 MichelinWheelMaker 的实现类,得到具体的 WheelMaker 实例。
以上就是自适应拓展类的核心实现:在拓展接口的方法被调用时,通过 SPI 加载具体的拓展实现类,并调用拓展对象的同名方法。
5. dubbo特性
1.智能容错与负载均衡
2.直连提供者
点对点直连方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。使用文件配置,dubbo2.0 以上版本自动加载 ${user.home}/dubbo-resolve.properties文件,配置内容类似如下:
com.alibaba.xxx.XxxService=dubbo://localhost:20890
3.多协议与多注册中心
Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议。目前项目中仅使用dubbo
Dubbo 支持同一服务向多注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。另外,注册中心是支持自定义扩展的,目前项目中仅使用zookeeper
4.参数验证
/**
* 实体类参数验证
*/
@NotNull // 不允许为空
@Size(min = 1, max = 20) // 长度或大小范围
private String name;
@NotNull(groups = ValidationService.Save.class) // 保存时不允许为空,更新时允许为空 ,表示不更新该字段
@Pattern(regexp = "^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$")
private String email;
@Min(18) // 最小值
@Max(100) // 最大值
private int age;
@Past // 必须为一个过去的时间
private Date loginDate;
@Future // 必须为一个未来的时间
private Date expiryDate;
/**
* 接口参数验证
*/
public interface ValidationService {
void save(@NotNull ValidationParameter parameter); // 验证参数不为空
void delete(@Min(1) int id); // 直接对基本类型参数验证
}
/**
* 关联验证
*/
public interface ValidationService {
@GroupSequence(Update.class)// 同时验证Update组规则
void save(ValidationParameter parameter);
}
5.上下文信息
上下文中存放的是当前调用过程中所需的环境信息。所有配置信息都将转换为 URL 的参数。RpcContext 是一个 ThreadLocal 的临时状态记录器,当接收到 RPC 请求,或发起 RPC 请求时,RpcContext 的状态都会变化。
6.consumer异步调用