Dubbo服务Spi机制和原理

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 该文章主要介绍了Dubbo中的SPI(Service Provider Interface)机制和原理,包括SPI的基本概念、Dubbo中的SPI分类以及SPI机制的实现细节。

什么是Dubbo的spi机制?

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。

在Jdk中也有Spi机制,Dubbo就是基于Jdk的Spi机制进行完善,弥补了jdk的Spi机制的缺点。SPI机制在Dubbo源码中处处可见,因此对SPI机制了解可以帮助更好的学习Dubbo。

Dubbo的spi分类

- 加载固定的扩展类

一种常见思路是读取特定目录下的配置文件,然后解析出全类名,通过反射机制来实例化这个类,然后将这个类放在集合中存起来,如果有需要的时候,直接从集合中取。Dubbo 中的实现也是这么一个思路。不过在 Dubbo 中,实现的更加完善,它实现了 IOC 和 AOP 的功能。IOC 就是说如果这个扩展类依赖其他属性,Dubbo 会自动的将这个属性进行注入。这个功能如何实现?一个常见思路是获取这个扩展类的 setter 方法,调用 setter 方法进行属性注入。AOP 指的是什么?这个说的是 Dubbo 能够为扩展类注入其包装类。比如 DubboProtocol 是 Protocol 的扩展类,ProtocolListenerWrapper 是 DubboProtocol 的包装类。

举例:指定名称random获取随机负载均衡实现类

RandomLoadBalance rd= (RandomLoadBalance)ExtensionLoader.getExtensionLoader(Loadbalance.class).getExtension("random");

总结:加载固定扩展点,根据扩展点查找目录下指定class并反射实例化,然后完成di。

加载指定路径下的文件内容,保存到集合中

会对存在依赖注入的扩展点进行依赖注入

会对存在Wrapper类的扩展点,实现扩展点的包装

- 加载自适应扩展类

先说明下自适应扩展类的使用场景。比如我们有需求,在调用某一个方法时,基于参数选择调用到不同的实现类。和工厂方法有些类似,基于不同的参数,构造出不同的实例对象。在 Dubbo 中实现的思路和这个差不多,不过 Dubbo 的实现更加灵活,它的实现和策略模式有些类似。每一种扩展类相当于一种策略,基于 URL 消息总线,将参数传递给 ExtensionLoader,通过 ExtensionLoader 基于参数加载对应的扩展类,实现运行时动态调用到目标实例上。

@Adaptive 该注解可以声明在类级别上,也可以声明在方法级别

实现原理:

如果修饰在类级别,那么直接返回修饰的类

如果修饰在方法界别,动态创建一个代理类(javassist)

Dubbo 中有两种类型的自适应拓展,一种是手工编码的,一种是自动生成的。手工编码的自适应拓展中可能存在着一些依赖,而自动生成的 Adaptive 拓展则不会依赖其他类。

- 激活扩展点

相当于Spring中的conditional @ConditionalOnBean(TTT.class)

ExtensionLoader extensionLoader=ExtensionLoader.getExtensionLoader(Filter.class); URL url=new URL("","",0); url=url.addParameter("cache","cache”);//根据url获取 指定一个key,如果url匹配到key,则获取匹配到的value名对应的扩展点List filters=extensionLoader.getActivateExtension(url,"cache”);

实现条件:只要url参数中包含CACHE_KEY,那么 CacheFilter就会被激活

@Activate(group = {
   
   CONSUMER, PROVIDER}, value = CACHE_KEY) public class CacheFilter implements Filter {
   
   }

自定义扩展点

​以自定义负载均衡器为例

第1步,创建自定义的负载均衡器类,并实现负载均衡器接口

package cn.supfox.dubbo;import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.LoadBalance;import java.util.List;
//实现负载均衡父接口
public class FirstInvokerLoadBalance implements LoadBalance {
   
   
    //构造函数打印提示,方便测试
    public FirstInvokerLoadBalance() {
   
   
        System.out.println("自定义的Dubbo负载均衡器");
    }/**
     * 实现自己的负载均衡逻辑
     * @param invokers 所有的提供者信息
     * @param url 服务url,包含配置信息
     * @param invocation 调用器
     * @param <T>
     * @return
     * @throws RpcException
     */
    @Override
    public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
   
   
        //这里根据自己的需求,选择一个最合适的服务提供者
        Invoker invoker = invokers.get(0);
        //返回第一个提供者
        return invoker;
    }
}

第二步,配置自定义的负载均衡器,在resources创建META-INF/services文件夹,并创建org.apache.dubbo.rpc.cluster.LoadBalance文件:

第三步,配置自定义的负载均衡器

到这里,自定义的负载均衡器就可以被Dubbo识别了,写一个测试类验证下

import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.rpc.cluster.LoadBalance;public class FirstInvokerLoadBalanceTest {
   
   public static void main(String[] args) {
   
   
        //通过ExtensionLoader类获取自定义的负载均衡器,框架源码也是这样获取的
        LoadBalance firstInvoker = ExtensionLoader.getExtensionLoader(LoadBalance.class)
                .getExtension("firstInvoker");
        System.out.println("打印自定义负载均衡器类:"+firstInvoker.getClass());
    }
}

运行结果:

可以看到Dubbo扩展器中获取到自定义的负载均衡器啦,Dubbo源码中也是这样获取的。

如何在项目中使用呢?

按上图方式就可以使用到自定义的负载均衡器了。

SPI原理

从上文的测试方法中跟进实现

LoadBalance firstInvoker = ExtensionLoader.getExtensionLoader(LoadBalance.class)                .getExtension("firstInvoker");

getExtensionLoader方法的实现

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
   
   
        if (type == null) {
   
   
            throw new IllegalArgumentException("Extension type == null");
        }
        if (!type.isInterface()) {
   
   
            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        }
        if (!withExtensionAnnotation(type)) {
   
   
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }
​
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
   
   
            //为每一类扩展类new一个扩展器
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

getExtension方法

public T getExtension(String name) {
   
   
        if (StringUtils.isEmpty(name)) {
   
   
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
   
   
            return getDefaultExtension();
        }
        Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        if (instance == null) {
   
   
            synchronized (holder) {
   
   
                instance = holder.get();
                if (instance == null) {
   
   
                    //开始创建扩展点
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

createExtension方法


private T createExtension(String name) {
   
   
        //找扩展点Class
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
   
   
            throw findException(name);
        }
        try {
   
   
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
   
   
                //通过反射创建扩展点实例
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //注入依赖类
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            //将扩展点实例传入包装器构造器,这样包装器类就拥有了
            //扩展点引用。起到ioc,aop的作用
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
   
   
                for (Class<?> wrapperClass : wrapperClasses) {
   
   
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
   
   
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

getExtensionClasses方法

private Map<String, Class<?>> getExtensionClasses() {
   
   
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
   
   
            synchronized (cachedClasses) {
   
   
                classes = cachedClasses.get();
                if (classes == null) {
   
   
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

loadExtensionClasses方法

private Map<String, Class<?>> loadExtensionClasses() {
   
   
        cacheDefaultExtensionName();
​
        Map<String, Class<?>> extensionClasses = new HashMap<>();
        //加载META-INF/dubbo/internal目录下文件名org.apache开头的扩展点
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        //加载META-INF/dubbo/internal目录下文件名com.alibaba开头的扩展点
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        //加载META-INF/dubbo目录下文件名org.apache开头的扩展点
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        //加载META-INF/dubbo目录下文件名com.alibaba开头的扩展点
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        //加载META-INF/services目录下文件名org.apache开头的扩展点
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        //加载META-INF/services目录下文件名com.alibaba开头的扩展点
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }

loadDirectory类就不继续看下去了,它功能是解析每一个扫描到的扩展点文件,解析出类,最终缓存到下面的Holder对象中,其实是一个map,里面key是扩展点名,value是扩展点Class。

private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

injectExtension依赖注入实现,通过set方法注入依赖的对象

private T injectExtension(T instance) {
   
   
        try {
   
   
            if (objectFactory != null) {
   
   
                for (Method method : instance.getClass().getMethods()) {
   
   
                    if (isSetter(method)) {
   
   
                        /**
                         * Check {@link DisableInject} to see if we need auto injection for this property
                         */
                        if (method.getAnnotation(DisableInject.class) != null) {
   
   
                            continue;
                        }
                        Class<?> pt = method.getParameterTypes()[0];
                        if (ReflectUtils.isPrimitives(pt)) {
   
   
                            continue;
                        }
                        try {
   
   
                            String property = getSetterProperty(method);
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
   
   
                                //调用set方法,注入依赖项
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
   
   
                            logger.error("Failed to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
   
   
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

总结下扩展点源码的流程

  • 为扩展点类型创建一个ExtensionLoader实例

  • 到配置文件中获取所有的扩展点Class信息

  • 根据名称找到具体的扩展点Class

  • 实例化找到的扩展点

  • 根据set方法注入扩展点依赖项

  • 将扩展点实例传入包装类构造器,实现AOP功能

总结

通过本文我们知道了Dubbo的SPI机制,学会了使用姿势,也知道了怎么扩展,最后还了解了基本实现,这种SPI机制在我们平时开发中也可以使用起来,对系统的扩展性非常好。

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
1月前
|
负载均衡 监控 Dubbo
Dubbo 原理和机制详解(非常全面)
本文详细解析了 Dubbo 的核心功能、组件、架构设计及调用流程,涵盖远程方法调用、智能容错、负载均衡、服务注册与发现等内容。欢迎留言交流。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Dubbo 原理和机制详解(非常全面)
|
2月前
|
监控 Dubbo Java
dubbo学习三:springboot整合dubbo+zookeeper,并使用dubbo管理界面监控服务是否注册到zookeeper上。
这篇文章详细介绍了如何将Spring Boot与Dubbo和Zookeeper整合,并通过Dubbo管理界面监控服务注册情况。
101 0
dubbo学习三:springboot整合dubbo+zookeeper,并使用dubbo管理界面监控服务是否注册到zookeeper上。
|
4月前
|
JSON Dubbo Java
【Dubbo协议指南】揭秘高性能服务通信,选择最佳协议的终极攻略!
【8月更文挑战第24天】在分布式服务架构中,Apache Dubbo作为一款高性能的Java RPC框架,支持多种通信协议,包括Dubbo协议、HTTP协议及Hessian协议等。Dubbo协议是默认选择,采用NIO异步通讯,适用于高要求的内部服务通信。HTTP协议通用性强,利于跨语言调用;Hessian协议则在数据传输效率上有优势。选择合适协议需综合考虑性能需求、序列化方式、网络环境及安全性等因素。通过合理配置,可实现服务性能最优化及系统可靠性提升。
60 3
|
4月前
|
C# 开发者 Windows
勇敢迈出第一步:手把手教你如何在WPF开源项目中贡献你的第一行代码,从选择项目到提交PR的全过程解析与实战技巧分享
【8月更文挑战第31天】本文指导您如何在Windows Presentation Foundation(WPF)相关的开源项目中贡献代码。无论您是初学者还是有经验的开发者,参与这类项目都能加深对WPF框架的理解并拓展职业履历。文章推荐了一些适合入门的项目如MvvmLight和MahApps.Metro,并详细介绍了从选择项目、设置开发环境到提交代码的全过程。通过具体示例,如添加按钮点击事件处理程序,帮助您迈出第一步。此外,还强调了提交Pull Request时保持专业沟通的重要性。参与开源不仅能提升技能,还能促进社区交流。
47 0
|
4月前
|
缓存 负载均衡 Dubbo
Dubbo服务集群容错原理(重要)
该文章主要介绍了Dubbo服务集群容错的原理,包括集群容错技术的概念、Dubbo中使用的集群容错技术种类及其原理。
|
7月前
|
Dubbo Java 应用服务中间件
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
|
2月前
|
Dubbo Java 应用服务中间件
Spring Cloud Dubbo:微服务通信的高效解决方案
【10月更文挑战第15天】随着信息技术的发展,微服务架构成为企业应用开发的主流。Spring Cloud Dubbo结合了Dubbo的高性能RPC和Spring Cloud的生态系统,提供高效、稳定的微服务通信解决方案。它支持多种通信协议,具备服务注册与发现、负载均衡及容错机制,简化了服务调用的复杂性,使开发者能更专注于业务逻辑的实现。
66 2
|
4月前
|
Dubbo Java 应用服务中间件
💥Spring Cloud Dubbo火爆来袭!微服务通信的终极利器,你知道它有多强大吗?🔥
【8月更文挑战第29天】随着信息技术的发展,微服务架构成为企业应用开发的主流模式,而高效的微服务通信至关重要。Spring Cloud Dubbo通过整合Dubbo与Spring Cloud的优势,提供高性能RPC通信及丰富的生态支持,包括服务注册与发现、负载均衡和容错机制等,简化了服务调用管理并支持多种通信协议,提升了系统的可伸缩性和稳定性,成为微服务通信领域的优选方案。开发者仅需关注业务逻辑,而无需过多关心底层通信细节,使得Spring Cloud Dubbo在未来微服务开发中将更加受到青睐。
87 0
|
2月前
|
Dubbo Java 应用服务中间件
Dubbo学习圣经:从入门到精通 Dubbo3.0 + SpringCloud Alibaba 微服务基础框架
尼恩团队的15大技术圣经,旨在帮助开发者系统化、体系化地掌握核心技术,提升技术实力,从而在面试和工作中脱颖而出。本文介绍了如何使用Dubbo3.0与Spring Cloud Gateway进行整合,解决传统Dubbo架构缺乏HTTP入口的问题,实现高性能的微服务网关。
|
3月前
|
Dubbo 应用服务中间件 Apache
Star 4w+,Apache Dubbo 3.3 全新发布,Triple X 领衔,开启微服务通信新时代
在 Apache Dubbo 突破 4w Star 之际,Apache Dubbo 团队正式宣布,Dubbo 3.3 正式发布!作为全球领先的开源微服务框架,Dubbo 一直致力于为开发者提供高性能、可扩展且灵活的分布式服务解决方案。此次发布的 Dubbo 3.3,通过 Triple X 的全新升级,突破了以往局限,实现了对南北向与东西向流量的全面支持,并提升了对云原生架构的友好性。
146 8