dubbo

目录

1. 什么是Dubbo?

2. dubbo配置

3. Dubbo SPI

3.1 源码实现

3.1.2 获取所有的拓展类

3.1.3 dubbo IOC

4. SPI 自适应拓展

5. dubbo特性

1.智能容错与负载均衡

2.直连提供者

3.多协议与多注册中心

4.参数验证

5.上下文信息

6.consumer异步调用


1. 什么是Dubbo?

阿里巴巴中间件开源的一款高性能、轻量级的开源Java RPC(是远程过程调用Remote Procedure Call)微服务框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现

Dubbo 服务框架的工作流程如下:

  1. 提供者在启动时,在注册中心注册服务。
  2. 消费者在启动时,在注册中心订阅所需的服务。
  3. 注册中心返回提供者地址列表给消费者。如果提供者发生变更,注册中心将推送变更数据给消费者。
  4. 消费者基于软负载均衡算法,从提供者地址列表中选一个提供者进行调用。

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 方法主要做了三件事情:

  1. 从 URL 中获取 WheelMaker 名称
  2. 通过 SPI 加载具体的 WheelMaker 实现类
  3. 调用目标方法

有了这个自适应实现类,来看看具体的使用

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异步调用

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值