微服务架构下的服务治理-Dubbo
- 前言
- 一、Dubbo SPI扩展点
- 二、Dubbo SPI扩展点源码分析
- 三、Dubbo的IoC
- 总结
前言
服务与服务之间的远程通信是分布式架构最基本的组成部分。随着业务规模的不断增长,会出现如何保障服务的高可用、如何动态对故障业务做降级、流量控制等、如何动态感知服务上下线问题。为了解决这些问题,就需要一个统一的服务治理框架对服务进行统一、有效的管控,从而保障服务的高效、健康运行,而Dubbo就是这样的一个框架。
Apache Dubbo是一个分布式服务框架,和普通RPC框架不同的是,它提供了服务治理的功能,比如服务注册、监控、路由、容错等。
一、Dubbo SPI扩展点
SPI主要用来做服务的扩展实现。SPI机制在很多场景都有运用,比如数据库连接等。
Dubbo的SPI扩展有两个规则:
- 和JDK内置的SPI一样,需要在resources目录下创建任一目录结构:META-INF/dubbo、META-INF/dubbo/internal、META-INF/services,在对应的目录下创建以接口全路径名命名的文件,Dubbo会去这三个目录下加载相应的扩展点
- 文件内容和JDK内置的SPI不一样,内容是一种以key和value形式的数据,key是一个字符串,value是一个对应扩展点的实现。
例如:
- 创建一个扩展点以及一个实现,其中扩展点需要声明@SPI(Dubbo包里的)注解。
package com.xd;
@SPI
public interface DriverSPI {
String connect();
}
public class DriverSPIImpl implements DriverSPI{
@Override
public String connect(){
return "连接成功";
}
}
- 在resources/META-INF/dubbo目录下创建一个以SPI接口命名的文件com.xd.DriverSPI
driverSPI=com.xd.DriverSPIImpl
- 创建测试类,使用ExtensionLoader.getExtensionLoader.getExtension(“driverSPI”)获得指定名称的扩展点实现
@Test
public void test(){
ExtensionLoader<DriverSPI> extensionLoader = ExtensionLoader.getExtensionLoader(DriverSPI.class);
DriverSPI driver = extensionLoader.getExtension("driverSPI");
System.out.println(driver.connect);
}
二、Dubbo SPI扩展点源码分析
观察以上代码,我们可以得到,通过ExtensionLoader.getExtensionLoader来获得一个ExtensionLoader实例,再通过getExtension()方法来获得指定名称的扩展点。
A. 通过点击getExtensionLoader()方法中,我们可以看到
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
// 抛出异常,判空处理
} else {
// 1. 先从缓存中获取与扩展类对应的ExtensionLoader
ExtensionLoader<T> loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
if (loader == null) {
// 2. 如果缓存未命中,则创建一个新实例,并且保存到EXTENSION_LOADERS集合中缓存起来
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
}
return loader;
}
}
// ExtensionLoader的构造方法
private ExtensionLoader的构造方法(Class<?> type) {
this.type = type;
// 3. 在构造方法中初始化一个objectFactory
this.objectFactory = type == ExtensionFactory.class ? null : (ExtensionFactory)getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension();
}
以上代码主要是返回一个实例,首先对type进行空值处理,else后面是分为重要的三部分
B. 通过点击getExtension()方法中,我们可以看到
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
// 1. 用name做判断,当name = "true"的使用,返回一个默认的扩展点
} else if ("true".equals(name)) {
return this.getDefaultExtension();
} else {
// 2. 创建一个Holder对象,用于缓存该扩展点的实例
Holder<Object> holder = this.getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized(holder) {
instance = holder.get();
// 3. 如果缓存不命中,则通过createExtension(name)创建一个扩展点
if (instance == null) {
instance = this.createExtension(name);
holder.set(instance);
}
}
}
return instance;
}
}
此方法主要用于根据指定名称获得对应的扩展点并返回,分为三个重要部分
C. 再来点击createExtension()方法来看
private T createExtension(String name) {
// 1. 通过getExtensionClasses().get(name)获得一个扩展类
Class<?> clazz = (Class)this.getExtensionClasses().get(name);
if (clazz == null) {
throw this.findException(name);
} else {
try {
// 2. 通过反射实例化之后缓存到EXTENSION_INSTANCES集合中
T instance = EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = EXTENSION_INSTANCES.get(clazz);
}
// 3. 实现依赖注入
this.injectExtension(instance);
// 4. 通过warpper进行包装
Set<Class<?>> wrapperClasses = this.cachedWrapperClasses;
Class wrapperClass;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for(Iterator var5 = wrapperClasses.iterator(); var5.hasNext(); instance = this.injectExtension(wrapperClass.getConstructor(this.type).newInstance(instance))) {
wrapperClass = (Class)var5.next();
}
}
this.initExtension(instance);
return instance;
} catch (Throwable var7) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " + this.type + ") couldn't be instantiated: " + var7.getMessage(), var7);
}
}
}
此方法主要是去指定路径下查找name对应的扩展点的实现,并且实例化后返回,分为四个重要的部分
D. 上述代码中,第一步是加载扩展类的关键实现,其他部分是辅助性的功能,通过点击etExtensionClasses(),我们可以看到
private Map<String, Class<?>> getExtensionClasses() {
// 1. 先从缓存中获取已经被加载的扩展类
Map<String, Class<?>> classes = (Map)this.cachedClasses.get();
if (classes == null) {
synchronized(this.cachedClasses) {
classes = (Map)this.cachedClasses.get();
if (classes == null) {
// 2. 如果缓存未命中,则调用loadExtensionClasses()方法来加载扩展类
classes = this.loadExtensionClasses();
this.cachedClasses.set(classes);
}
}
}
return classes;
}
此方法返回一个Map集合,key和value分别对应我们前面提到过的文件中的key和value,分为两个重要部分
E. 通过点击loadExtensionClasses()方法,我们可以看到
private Map<String, Class<?>> loadExtensionClasses() {
// 1. 通过cacheDefaultExtensionName()方法获得当前扩展接口的默认扩展对象,并且缓存
this.cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap();
// 2. 调用loadDirectory()方法加载指定文件目录下的配置文件
this.loadDirectory(extensionClasses, "META-INF/dubbo/internal/", this.type.getName(), true);
this.loadDirectory(extensionClasses, "META-INF/dubbo/internal/", this.type.getName().replace("org.apache", "com.alibaba"), true);
this.loadDirectory(extensionClasses, "META-INF/dubbo/", this.type.getName());
this.loadDirectory(extensionClasses, "META-INF/dubbo/", this.type.getName().replace("org.apache", "com.alibaba"));
this.loadDirectory(extensionClasses, "META-INF/services/", this.type.getName());
this.loadDirectory(extensionClasses, "META-INF/services/", this.type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
可以看到此方法很简单,就是去指定的目录下找到对应的文件,并且将解析的内容保存到extensionClasses中,主要分为两个重要部分
F. 通过点击cacheDefaultExtensionName()方法,我们可以看到
private void cacheDefaultExtensionName() {
// 1. 获得指定扩展接口的@SPI注解
SPI defaultAnnotation = (SPI)this.type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("More than 1 default extension name on extension " + this.type.getName() + ": " + Arrays.toString(names));
}
// 2. 得到@SPI注解中的名字,保存到cachedDefaultName属性中
if (names.length == 1) {
this.cachedDefaultName = names[0];
}
}
}
}
以上方法主要是对@SPI注解进行解析,在@SPI注解中有一个默认值dubbo,这意味着没有显式的指定协议内容,默认采用Dubbo协议来发布服务
以上就是Dubbo SPI扩展机制原理,其实可以看到,主要做一件事,查缓存,缓存未命中则创建一个实例
三、Dubbo的IoC
对于学过Spring的小伙伴们,IoC(控制反转)并不陌生,其是在系统运行时,动态地向某个对象提供它所需要的其他对象,这种机制是通过依赖注入完成的。
在分析Dubbo SPI机制时,createExtension()方法中有一段代码this.injectExtension(instance),我们主要来分析它
private T injectExtension(T instance) {
if (this.objectFactory == null) {
return instance;
} else {
try {
Method[] var2 = instance.getClass().getMethods();
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
Method method = var2[var4];
// 1. 遍历被加载的扩展类所有的set方法
if (this.isSetter(method) && method.getAnnotation(DisableInject.class) == null) {
Class<?> pt = method.getParameterTypes()[0];
if (!ReflectUtils.isPrimitives(pt)) {
try {
// 2. 得到的set方法中的参数类型,如果参数类型是对象类型,则获得这个set方法中的属性
String property = this.getSetterProperty(method);
// 3. 根据class以及name,使用自适应扩展点加载该属性名称对应的扩展类
Object object = this.objectFactory.getExtension(pt, property);
if (object != null) {
// 4. 调用set方法完成赋值
method.invoke(instance, object);
}
} catch (Exception var9) {
logger.error("Failed to inject via method " + method.getName() + " of interface " + this.type.getName() + ": " + var9.getMessage(), var9);
}
}
}
}
} catch (Exception var10) {
logger.error(var10.getMessage(), var10);
}
return instance;
}
}
其实依赖注入方法思路主要就是,加载扩展类中所有set方法,该set方法中的参数类型是对象类型则获得set方法的名称,再根据该参数对象class已经set方法的名字进行加载,再用反射invoke该扩展类进行赋值。
总结
本文主要针对Dubbo SPI扩展机制来进行分析,我们可以得到一个总结:
Dubbo的SPI相关逻辑封装在ExtensionLoader类中。首先在getExtensionLoader()方法中获取一个需要扩展类的实例,再根据getExtension()方法中指定名称获得对应的扩展点并返回。其创建扩展对象是该方法中的createExtension()方法,其实现步骤是通过getExtensionClass.get(name)获得一个扩展类,通过反射实例化缓存中,再将扩展类进行包装最后返回。