微服务架构下的服务治理-Dubbo(源码分析)


前言

服务与服务之间的远程通信是分布式架构最基本的组成部分。随着业务规模的不断增长,会出现如何保障服务的高可用、如何动态对故障业务做降级、流量控制等、如何动态感知服务上下线问题。为了解决这些问题,就需要一个统一的服务治理框架对服务进行统一、有效的管控,从而保障服务的高效、健康运行,而Dubbo就是这样的一个框架。

Apache Dubbo是一个分布式服务框架,和普通RPC框架不同的是,它提供了服务治理的功能,比如服务注册、监控、路由、容错等。


一、Dubbo SPI扩展点

SPI主要用来做服务的扩展实现。SPI机制在很多场景都有运用,比如数据库连接等。

Dubbo的SPI扩展有两个规则:

  • 和JDK内置的SPI一样,需要在resources目录下创建任一目录结构:META-INF/dubboMETA-INF/dubbo/internalMETA-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)获得一个扩展类,通过反射实例化缓存中,再将扩展类进行包装最后返回。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_咸蛋.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值