Dubbo原理解析,彻底搞懂dubbo (上)

Dubbo架构

概述

Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的RPC 实现服务的输出和输入功能,可以和Spring框架无缝集成。
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

调用流程:

  1. 服务容器负责启动,加载,运行服务提供者。
  2. 服务提供者在启动时,向注册中心注册自己提供的服务。
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

架构体系

源码结构

  • dubbo-common:公共逻辑模块: 包括Util类和通用模型
  • dubbo-remoting 远程通信模块: 相当于dubbo协议的实现,如果RPC使用RMI协议则不需要使用此包
  • dubbo-rpc 远程调用模块: 抽象各种协议,以及动态代理,包含一对一的调用,不关心集群的原理。
  • dubbo-cluster 集群模块: 将多个服务提供方伪装成一个提供方,包括负载均衡,容错,路由等,集群的地址列表可以是静态配置的,也可以是注册中心下发的.
  • dubbo-registry 注册中心模块: 基于注册中心下发的集群方式,以及对各种注册中心的抽象
  • dubbo-monitor 监控模块: 统计服务调用次数,调用时间,调用链跟踪的服务.
  • dubbo-config 配置模块: 是dubbo对外的api,用户通过config使用dubbo,隐藏dubbo所有细节
  • dubbo-container 容器模块: 是一个standlone的容器,以简单的main加载spring启动,因为服务通常不需要Tomcat/Jboss等web容器的特性,没必要用web容器去加载服务.

整体设计

  • 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
  • 图中从下至上分为十层,各层均为单向依赖,每一层都可以剥离上层被复用,其中,Service 和Config 层为API,其它各层均为SPI。
  • 图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。
  • 图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调时链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。

各层说明

  • config 配置层:对外配置接口,以 ServiceConfig , ReferenceConfig 为中心,可以直接初始化配置类,也可以通过spring 解析配置生成配置类
  • proxy 服务代理层:服务接口透明代理,生成服务的客户端Stub 和服务器端Skeleton, 以ServiceProxy 为中心,扩展接口为 ProxyFactory
  • registry 注册中心层:封装服务地址的注册与发现,以服务URL 为中心,扩展接口为RegistryFactory , Registry , RegistryService
  • cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster , Directory , Router , LoadBalance
  • monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为MonitorFactory , Monitor , MonitorService
  • protocol 远程调用层:封装RPC 调用,以 Invocation , Result 为中心,扩展接口为Protocol , Invoker , Exporter
  • exchange 信息交换层:封装请求响应模式,同步转异步,以 Request , Response 为中心,扩展接口为 Exchanger , ExchangeChannel , ExchangeClient , ExchangeServer
  • transport 网络传输层:抽象mina 和netty 为统一接口,以 Message 为中心,扩展接口为Channel , Transporter , Client , Server , Codec
  • serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization , ObjectInput ,ObjectOutput , ThreadPool

调用流程

对照上面的整体架构图可以大致分为以下步骤:

1、服务提供者启动,开启Netty服务,创建Zookeeper客户端,向注册中心注册服务。

2、服务消费者启动,通过Zookeeper向注册中心获取服务提供者列表,与服务提供者通过Netty建立长连接。

3、服务消费者通过接口开始远程调用服务,ProxyFactory通过初始化Proxy对象,Proxy通过创建动态代理对象。

4、动态代理对象通过invoke方法,层层包装生成一个Invoker对象,该对象包含了代理对象。

5、Invoker通过路由,负载均衡选择了一个最合适的服务提供者,在通过加入各种过滤器,协议层包装生成一个新的DubboInvoker对象。

6、再通过交换成将DubboInvoker对象包装成一个Reuqest对象,该对象通过序列化通过NettyClient传输到服务提供者的NettyServer端。

7、到了服务提供者这边,再通过反序列化、协议解密等操作生成一个DubboExporter对象,再层层传递处理,会生成一个服务提供端的Invoker对象.

8、这个Invoker对象会调用本地服务,获得结果再通过层层回调返回到服务消费者,服务消费者拿到结果后,再解析获得最终结果。

Dubbo中的SPI机制

什么是SPI

概述

在Dubbo 中,SPI 是一个非常重要的模块。基于SPI,我们可以很容易的对Dubbo 进行拓展。如果大家想要学习Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下Java SPI 与Dubbo SPI 的用法,然后再来分析Dubbo SPI 的源码。

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

JDK中的SPI

Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。

通过一个案例我们来认识下SPI

定义一个接口:

package com.laowang;

/**
 * @author 原
 * @date 2021/3/27
 * @since 1.0
 **/
public interface User {

    String showName();
}

定义两个实现类

package com.laowang.impl;

import com.laowang.User;

/**
 * @author 原
 * @date 2021/3/27
 * @since 1.0
 **/
public class Student implements User {
    @Override
    public String showName() {
        System.out.println("my name is laowang");
        return null;
    }
}
package com.laowang.impl;

import com.laowang.User;

/**
 * @author 原
 * @date 2021/3/27
 * @since 1.0
 **/
public class Teacher implements User {
    @Override
    public String showName() {
        System.out.println("my name is zhangsan");
        return null;
    }
}

在resources目录下创建文件夹META-INF.services,并在该文件夹下创建一个名称与User的全路径一致的文件com.laowang.User

在文件中写入,两个实现类的全路径名

编写测试类:

package com.laowang;

import java.util.ServiceLoader;

/**
 * @author 原
 * @date 2021/3/27
 * @since 1.0
 **/
public class SpiTest {
    public static void main(String[] args) {
        ServiceLoader<User> serviceLoader = ServiceLoader.load(User.class);
        serviceLoader.forEach(User::showName);
    }
}

运行结果:

我们发现通过SPI机制,帮我们自动运行了两个实现类。

通过查看ServiceLoader源码:

其实通过读取配置文件中实现类的全路径类名,通过反射创建对象,并放入providers容器中。

总结:

调用过程
应用程序调用ServiceLoader.load方法,创建一个新的ServiceLoader,并实例化该类中的成员变量
应用程序通过迭代器接口获取对象实例,ServiceLoader先判断成员变量providers对象中(LinkedHashMap<String,S>类型)是否有缓存实例对象,如果有缓存,直接返回。如果没有缓存,执行类的装载,
优点
使用Java SPI 机制的优势是实现解耦,使得接口的定义与具体业务实现分离,而不是耦合在一起。应用进程可以根据实际业务情况启用或替换具体组件。
缺点
不能按需加载。虽然ServiceLoader 做了延迟载入,但是基本只能通过遍历全部获取,也就是接口的实现类得全部载入并实例化一遍。如果你并不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
获取某个实现类的方式不够灵活,只能通过Iterator 形式获取,不能根据某个参数来获取对应的实现类。
多个并发多线程使用ServiceLoader 类的实例是不安全的。
加载不到实现类时抛出并不是真正原因的异常,错误很难定位。

Dubbo中的SPI

Dubbo 并未使用Java SPI,而是重新实现了一套功能更强的SPI 机制。Dubbo SPI 的相关逻辑被封装在了ExtensionLoader 类中,通过ExtensionLoader,我们可以加载指定的实现类。

栗子

与Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。下面来演示Dubbo SPI 的用法:
Dubbo SPI 所需的配置文件需放置在META-INF/dubbo 路径下,与Java SPI 实现类配置不同,DubboSPI 是通过键值对的方式进行配置,配置内容如下。

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

在使用Dubbo SPI 时,需要在接口上标注@SPI 注解。

@SPI
public interface Robot {
void sayHello();
}

通过ExtensionLoader,我们可以加载指定的实现类,下面来演示Dubbo SPI :

public class DubboSPITest {
   @Test
   public void sayHello() throws Exception {
       ExtensionLoader<Robot> extensionLoader =
           ExtensionLoader.getExtensionLoader(Robot.class);
       Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
       optimusPrime.sayHello();
       Robot bumblebee = extensionLoader.getExtension("bumblebee");
       bumblebee.sayHello();
   }
}

Dubbo SPI 除了支持按需加载接口实现类,还增加了IOC 和AOP 等特性,这些特性将会在接下来的源码分析章节中一一进行介绍。

源码分析

ExtensionLoader 的getExtensionLoader 方法获取一个ExtensionLoader 实例,然后再通过ExtensionLoader 的getExtension 方法获取拓展类对象。下面我们从ExtensionLoader 的getExtension 方法作为入口,对拓展类对象的获取过程进行详细的分析。

public T getExtension(String name) {
       if (StringUtils.isEmpty(name)) {
           throw new IllegalArgumentException("Extension name == null");
       }
       if ("true".equals(name)) {
            // 获取默认的拓展实现类
           return getDefaultExtension();
       }
       // Holder,顾名思义,用于持有目标对象 就是从容器中获取,如果没有直接new一个Holder
       Holder<Object> holder = getOrCreateHolder(name);
       //获取目标对象实例
       Object instance = holder.get();
        // 如果目标对象实例为null 就需要通过双重检查创建实例
       if 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值