【Dubbo】分层及其拓展点

主要内容 :

  • 核心扩展点概述 ;
  • RPC 层扩展点 ;
  • Remote 层扩展点 ;
  • 其他扩展点

主要介绍在整个框架中有哪些已有的接口是可以扩展的 , 主要涉及扩展接口的作用 , 原理性的内容相对较少 。 首先介绍整个框架中核心扩展点的总体大图 , 让读者对这些扩展点有一个总体的了解 。 其次从上到下介绍整个 RPC层的扩展点 。 然后介绍 Remote 层的扩展点 。 最后会把其他一些零散的扩展点也简单介绍一下 。

1 Dubbo 核心扩展点概述

我们经常会听到一句话 : 唯一不变的 , 就是变化本身 。 面对互联网领域日新月异的业务发展变化 , 作为一个分布式服务框架 , 既需要提供非常强大的功能 , 满足业务开发者日常开发的需求 , 也需要在框架的内在结构里 , 提供足够多的特定扩展能力 , 使得用户可以在不改动内部代码和结构的基础上 , 按照这些接口的约定 , 即可简单方便地定制出自己想要的功能 。 Dubbo使用了扩展点的方式来实现这种能力 。 Dubbo 的总体流程和分层是抽象不变的 , 但是每一层都提供扩展接口 , 让用户可以自定义扩展每一层的功能

1.1 扩展点的背景

扩展机制和扩展点作为 Dubbo 的核心设计机制 , 不仅是 Dubbo 能够适应不同公司的具体技术需要 , 流行至今的重要因素 , 也是 Dubbo 本身生态不断完善 , 功能越来越强大的核心原因之一 。 这种灵活定制的设计 , 一方面让 Dubbo 项目近几年来在阿里巴巴集团和开源团队之外 , 吸引很多公司 “ fork ” 了源码自己进行维护和发展 , 另一方面让 Dubbo 于 2017 年重新启动开源后 ,本身能够根据技术趋势的发展和业内其他优秀的技术框架 , 不断地进行一些重大的重构调整 ,以及对项目进行拆分 , 把一些独立的功能模块和非核心的扩展逐步迁移到 Dubbo 生态项目 , 进而实现 Dubbo 的 “ 微内核 + 富生态 ” 的技术发展策略 。

实际上 Dubbo 本身的各类功能组件也是按照这些扩展点的具体实现构建出来的 , 例如Dubbo 默认的 Dubbo 协议 、 Hessian2 序列化和协议 、 fastjson 序列化 、 ZooKeeper 注册中心等 。更多的 “ 非官方 ” 扩展组件则是由广大开发者在自己的工作实践中创造并提交到 Dubbo 项目中的 , 最终一部分变成了目前的 “ 官方 ” 扩展组件, 比如 WebService 协议 、 REST 协议 、 Thrift协议 、 fst 和 kryo 序列化等 , 另一部分变成了 Dubbo 生态项目中的 “ 准官方 ” 扩展组件 , 例如JSON-RPC/XML-RPC 协议 、 JMS 协议 、 avro/gson 序列化 、 etcd/nacos 注册中心等 。 这些扩展的提交者也成为 Dubbo 项目的 Committer 或 PMC 成员

我们介绍了 Dubbo 的扩展机制和扩展组件加载的原理 。 下面我们将介绍 Dubbo 的核心扩展点具体有哪些 , 它们有什么特点 、 如何使用 , 以及具有哪些内置的扩展组件 。

1.2 扩展点整体架构

如果按照使用者和开发者两种类型来区分 , Dubbo 可以分为 API 层和 SPI 层 。 API 层让用户只关注业务的配置 , 直接使用框架的 API 即可 ; SPI 层则可以让用户自定义不同的实现类来扩展整个框架的功能 。

如果按照逻辑来区分 , 那么又可以把 Dubbo 从上到下分为业务 、 RPC 、 Remote 三个领域 。由于业务层不属于 SPI 的扩展 , 因此不是本章关注的内容 。 可扩展的 RPC 和 Remote 层继续细分 , 又能分出 7 层 , 如图所示 。
在这里插入图片描述

图中己经把监控层 (Monitor 层)移除 , 因为监控层的实现过于简单 , 此外即使没有监控层也不会影响整个主流程的进行 , 因此不再单独讲解 。 另外 , 细分出来的每一层 (Proxy.Registry … ) 的作用 , 本章不再重复赘述 。

2 RPC 层扩展点

按照完整的 Dubbo 结构分层 , RPC 层可以分为四层 : Config 、 Proxy 、 Registry 、 Cluster ,由于 Config 属于 API 的范畴 ,因此我们只基于 Proxy 、 Registry 、 Cluster 层来介绍对应的扩展点 。

2.1 Proxy 层扩展点

Proxy 层主要的扩展接口是 ProxyFactoryo 我们在使用 Dubbo 框架的时候 , 明明调用的是一个本地的接口 , 为什么框架会自动帮我们发起远程请求 , 并把调用结果返回呢 ? 整个远程调用的过程对开发者完全是透明的 , 就像本地调用一样 。 这正是由于 ProxyFactory 帮我们生成了代理类 , 当我们调用某个远程接口时 , 实际上使用的是代理类 。 代理类远程调用过程如图所示 。
在这里插入图片描述
图中省略了很多细节 , 如序列化等 , 主要是为了说明整个代理调用过程 。 Dubbo 中的ProxyFactory 有两种默认实现: Javassist 和 JDK, 用户可以自行扩展自己的实现 , 如 CGLIB(CodeGeneration Library) o Dubbo 选用 Javassist 作为默认字节码生成工具 , 主要是基于性能和使用的简易性考虑 , Javassist 的字节码生成效率相对于其他库更快 , 使用也更简单 。 下面我们来看一下 ProxyFactory 接口有哪些具体的方法

ProxyFactory 接口
在这里插入图片描述
我们可以看到 ProxyFactory 接口有三个方法 , 每个方法上都有 @Adaptive 注解 , 并且方法会根据 URL 中的 proxy 参数决定使用哪种字节码生成工具 。 第二个方法的 generic 参数是为了标识这个代理是否是泛化调用 。
在这里插入图片描述
stub 比较特殊 , 它的作用是创建一个代理类 , 这个类可以在发起远程调用之前在消费者本地做一些事情 , 比如先读缓存 。 它可以决定要不要调用 Proxy

2.2 Registry 层扩展点

Registry 层可以理解为注册层 , 这一层中最重要的扩展点就是 org. apache, dubbo. registry.RegistryFactoryo 整个框架的注册与服务发现客户端都是由这个扩展点负责创建的 。 该扩展点有 @Adaptive ( { "protocol ") ) 注解 , 可以根据 URL 中的 protocol 参数创建不同的注册中心客户端 。 例如 : protocol=redis, 该工厂会创建基于 Redis 的注册中心客户端 。 因此 , 如果我们扩
展了自定义的注册中心 , 那么只需要配置不同的 Protocol 即可 。

RegistryFactory 接口
在这里插入图片描述
使用这个扩展点 , 还有一些需要遵循的 “ 潜规则 ” :

  • 如果 URL 中设置了 check-false, 则连接不会被检查 。 否则 , 需要在断开连接时抛出异常 。
  • 需要支持通过 usename:password 格式在 URL 中传递鉴权 。
  • 需要支持设置 backup 参数来指定备选注册集群的地址 。
  • 需要支持设置 file 参数来指定本地文件缓存 。
  • 需要支持设置 timeout 参数来指定请求的超时时间 。
  • 需要支持设置 session 参数来指定连接的超时或过期时间 。

在 Dubbo, 有 AbstractRegistryFactory 已经抽象了一些通用的逻辑 , 用户可以直接继承该抽象类实现自定义的注册中心工厂 。 已有的 RegistryFactory 实现如表所示
在这里插入图片描述

2.3 Cluster 层扩展点

Cluster 层负责了整个 Dubbo 框架的集群容错 , 涉及的扩展点较多 , 包括容错 ( Cluster) 、路由 ( Router)、 负载均衡( LoadBalance)、 配置管理工厂 ( ConfiguratorFactory ) 和合并器( Merger)

1、 Cluster 扩展点

Cluster 需要与 Cluster 层区分开 , Cluster 主要负责一些容错的策略 , 也是整个集群容错的入口 。 当远程调用失败后 , 由 Cluster 负责重试 、 快速失败等 , 整个过程对上层透明 。 整个集群容错层之间的关系 , 不再赘述 。

Cluster 扩展点主要负责 Dubbo 框架中的容错机制 , 如 Failover 、 Failfast 等,默认使用 Failover机制 。

Cluster 扩展点接口
在这里插入图片描述
Cluster 接口只有一个 join 方法 , 并且有 @Adaptive 注解 , 说明会根据配置动态调用不同的容错机制 。 已有的 Cluster 实现如表所示
在这里插入图片描述

2、 RouterFactory 扩展点

RouterFactory 是一个工厂类 , 顾名思义 , 就是用于创建不同的 Router 。 假设接口 A 有多个服务提供者提供服务 , 如果配置了路由规则(某个消费者只能调用某个几个服务提供者) , 则Router 会过滤其他服务提供者 , 只留下符合路由规则的服务提供者列表 。

现有的路由规则支持文件 、 脚本和自定义表达式等方式 。 接口上有 @Adaptive(“protocol” )注解 , 会根据不同的 protocol 自动匹配路由规则

RouterFactory 扩展点实现
在这里插入图片描述
在 2.7 版本之后 , 路由模块会做出较大的更新 , 每个服务中每种类型的路由只会存在一个 ,它们会成为一个路由器链 。 因此新的运行模式需要关注 2.7 版本 。 已有的 RouterFactory 实现如表所示
在这里插入图片描述
3、 LoadBalance 扩展点

LoadBalance 是 Dubbo 框架中的负载均衡策略扩展点 , 框架中已经内置随机 ( Random) 、轮询 ( RoundRobin) 、 最小连接数 ( LeastActive) 、 一致性 Hash (ConsistentHash) 这几种负载均衡的方式 , 默认使用随机负载均衡策略 。 LoadBalance 主要负责在多个节点中 , 根据不同的负载均衡策略选择一个合适的节点来调用 。

LoadBalance 扩展点接口源码
在这里插入图片描述

框架中己有的 LoadBalance 实现如表 8-5 所示
在这里插入图片描述
4、 ConfiguratorFactory 扩展点

ConfiguratorFactory 是创建配置实例的工厂类 , 现有 override 和 absent 两种工厂实现 , 分别会创建 OverrideConfigupator 和 AbsentConfigurator 两种配置对象 。 默认的两种实现 ,OverrideConfigurator 会直接把配置中心中的参数覆盖本地的参数 ; AbsentConfigurator 会先看本地是否存在该配置 , 没有则新增本地配置 , 如果己经存在则不会覆盖 。

ConfiguratorFactory 扩展点源码
在这里插入图片描述
该扩展点的方法上也有 @Adaptive(“protocol”) 注解 , 会根据 URL 中的 protocol 配置值使用不同的扩展点实现 。 框架中内置的扩展点实现如表所示
在这里插入图片描述
5、 Merger 扩展点

Merger 是合并器 , 可以对并行调用的结果集进行合并 , 例如 : 并行调用 A 、 B 两个服务都会返回一个 List 结果集 , Merger 可以把两个 List 合并为一个并返回给应用 。 默认已经支持 map 、set 、 list 、 byte 等 11 种类型的返回值 。 用户可以基于该扩展点 , 添加自定义类型的合并器 。

Merger 扩展点源码
在这里插入图片描述
已有的 Merger 扩展点实现如表所示
在这里插入图片描述

3 Remote 层扩展点

Remote 处于整个 Dubbo 框架的底层 , 涉及协议 、 数据的交换 、 网络的传输 、 序列化 、 线程池等 , 涵盖了一个远程调用的所有要素 。Remote 层是对 Dubbo 传输协议的封装 , 内部再划为 Transport 传输层和 Exchange 信息交换层 。 其中 Transport 层只负责单向消息传输 , 是对 Mina、 Netty 等传输工具库的抽象 。 而 Exchange 层在传输层之上实现了 Request-Response 语义 , 这样我们可以在不同传输方式之上都能做到统一的请求 / 响应处理 。 Serialize 层是 RPC 的一部分 , 决定了在消费者和服务提供者之间的二进制数据传输格式 。 不同的序列化库的选择会对 RPC 调用的性能产生重要影响 , 目前默认选择是Hessian2 序列化 。

3.1 Protocol 层扩展点

Protocol 层主要包含四大扩展点 , 分别是 Protocol 、 Filter 、 ExporterListener 和 InvokerListener 。其中 Protocol、 Filter 这两个扩展点使用得最多 。 下面分别介绍每个扩展点 。

1. Protocol 扩展点

Protocol 是 Dubbo RPC 的核心调用层 , 具体的 RPC 协议都可以由 Protocol 点扩展 。 如果想增加一种新的 RPC 协议 , 则只需要扩展一个新的 Protocol 扩展点实现即可 。

Protocol 扩展点接口
在这里插入图片描述
Protocol 的每个接口会有一些 “ 潜规则 ” , 在实现自定义协议的时候需要注意 。

export 方法 :

  • (1) 协议收到请求后应记录请求源 IP 地址 。 通过 RpcContext.getContext ().setRemote-Address() 方法存入 RPC 上下文 。
  • (2) export 方法必须实现幕等 , 即无论调用多少次 , 返回的 URL 都是相同的 。
  • (3) Invoker 实例由框架传入 , 无须关心协议层 。

refer 方法

  • (1) 当我们调用 refer 。 方法返回 Invoker 对象的 invoke() 方法时 , 协议也需要相应地执行 invoke () 方法 。 这一点在设计自定义协议的 Invoker 时需要注意 。
  • (2) 正常来说 refer 。 方法返回的自定义 Invoker 需要继承 Invoker 接口 。
  • (3) 当 URL 的参数有 check=false 时 , 自定义的协议实现必须不能抛出异常 , 而是在出现连接失败异常时尝试恢复连接 。

destroy 方法 :

  • (1) 调用 destroy 方法的时候 , 需要销毁所有本协议暴露和引用的方法 。
  • (2) 需要释放所有占用的资源 , 如连接 、 端口等 。
  • (3) 自定义的协议可以在被销毁后继续导出和引用新服务 。

整个 Protocol 的逻辑由 Protocok Exporter> Invoker 三个接口串起来 :

  • com.alibaba.dubbo.rpc.Protocol ;
  • com.alibaba.dubbo.rpc.Exporter ;
  • com. alibaba. dubbo .rpc .Invoker

其中 Protocol 接口是入口 , 其实现封装了用来处理 Exporter 和 Invoker 的方法 :Exporter 代表要暴露的远程服务引用 , Protocol#export 方法是将服务暴露的处理过程 ,Invoker 代表要调用的远程服务代理对象 , Protocol#refer 方法通过服务类型和 URL 获得要调用的服务代理 。

由于 Protocol 可以实现 Invoker 和 Exporter 对象的创建 , 因此除了作为远程调用对象的构造 ,还能用于其他用途 , 例如 : 可以在创建 Invoker 的时候对原对象进行包装增强 , 添加其他 Filter进去 , ProtocolFilterWrapper 实现就是把 Filter 链加入 Invoke。 如果对这一段并不是十分了解 ,则可以先了解设计模式中的装饰器模式 , 对最原始的 Invoker 进行增强 。因此 , 下面我们只列出框架中用于调用的协议 , 已有的 Protocol 扩展点实现如表所示
在这里插入图片描述

2. Filter 扩展点

Filter 是 Dubbo 的过滤器扩展点 , 可以自定义过滤器 , 在 Invoker 调用前后执行自定义的逻辑 。 在 Filter 的实现中 , 必须要调用传入的 Invoker 的 invoke 方法 , 否则整个链路就断了 。

Filter 接口定义及实现的示例
在这里插入图片描述
可以看到 , Filter 接口使用了 JDK8 的新特性 , 接口中有 default 方法 onResponse, 默认返回收到的结果 。

3. ExporterListener/lnvokerListener 扩展点

ExporterListener 和 InvokerListener 这两个扩展点非常相似 , ExporterListener 是在暴露和取消暴露服务时提供回调 ;InvokerListener 则是在服务的引用与销毁引用时提供回调 。

ExporterListener 与 InvokerListener 扩展接口
在这里插入图片描述

3.2 Exchange 层扩展点

Exchange 层只有一个扩展点接口 Exchanger, 这个接口主要是为了封装请求 / 响应模式 , 例如 : 把同步请求转化为异步请求 。 默认的扩展点实现是 org.apache.dubbo.remoting.exchange,support .header .HeaderExchanger 每个方法上都有 @Adaptive 注解 , 会根据 URL 中的Exchanger 参数决定实现类 。

Exchanger 扩展点源码
在这里插入图片描述
既然已经有了 Transport 层来传输数据了 , 为什么还要有 Exchange 层呢 ? 因为上层业务关注的并不是诸如 Netty 这样的底层Channel 。 上层一个 Request 只关注对应的 Response, 对于是同步还是异步请求 , 或者使用什么传输根本不关心 。 Transport 层是无法满足这项需求的 ,Exchange 层因此实现了 Request-Response 模型 , 我们可以理解为基于 Transport 层做了更高层次的封装

3.3 Transport 层扩展点

Transport 层为了屏蔽不同通信框架的异同 , 封装了统一的对外接口 。 主要的扩展点接口有Transporter 、 Dispatcher、 Codec2 和 ChannelHandler 。

其中 , ChannelHandler 主要处理连接相关的事件 , 例如 : 连接上 、 断开 、 发送消息 、 收到消息 、 出现异常等 。 虽然接口上有 SPI 注解 , 但是在框架中实现类的使用却是直接 “ new ” 的方式 。 因此不在做过多介绍 。

1. Transportor 扩展接口

Transporter 屏蔽了通信框架接口 、 实现的不同 , 使用统一的通信接口 。

Transporter 扩展接口
在这里插入图片描述
bind 方法会生成一个服务 , 监听来自客户端的请求 ; connect 方法则会连接到一个服务 。两个方法上都有 @Adaptive 注解 , 首先会根据 URL 中 server 的参数值去匹配实现类 , 如果匹配不到则根据 transporter 参数去匹配实现类 。 默认的实现是 netty4 。然后我们看一下框架中已有的扩展点实现 , 如表所示
在这里插入图片描述
2. Dispatcher 扩展接口

如果有些逻辑的处理比较慢 , 例如 : 发起 I/O 请求查询数据库 、 请求远程数据等 , 则需要使用线程池 。 因为 I/O 速度相对 CPU 是很慢的 , 如果不使用线程池 , 则线程会因为 I/O 导致同步阻塞等待 。 Dispatcher 扩展接口通过不同的派发策略 , 把工作派发到不同的线程池 , 以此来应对不同的业务场景 。

Dispatcher 扩展接口
在这里插入图片描述
Dispatcher 现有的扩展接口实现如表所示
在这里插入图片描述

3. Codec2 扩展接口

Codec2 主要实现对数据的编码和解码 , 但这个接口只是需要实现编码 / 解码过程中的通用逻辑流程 , 如解决半包 、 粘包等问题 。 该接口属于在序列化上封装的一层 。

Codec2 扩展接口
在这里插入图片描述Codec2 现有的实现如表 8-11 所示
在这里插入图片描述

4. ThreadPool 扩展接口
我们在 Transport 层由 Dispatcher 实现不同的派发策略 , 最终会派发到不同的 ThreadPool 中执行 。 ThreadPool 扩展接口就是线程池的扩展 。

ThreadPool 扩展接口
在这里插入图片描述
现阶段 , 框架中默认含有四种线程池扩展的实现 , 以下内容摘自官方文档 :

  • fixed, 固定大小线程池 , 启动时建立线程 , 不关闭 , 一直持有 。
  • cached, 缓存线程池 , 空闲一分钟自动删除 , 需要时重建 。
  • limited, 可伸缩线程池 , 但池中的线程数只会增长不会收缩 。 只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题 。
  • eager, 优先创建 Worker 线程池 。 在任务数量大于 corePoolSize 小于 maximumPoolSize时 , 优先创建 Worker 来处理任务 。 当任务数量大于 maximumPoolSize 时 , 将任务放入阻塞队列 。 阻塞队列充满时抛出 RejectedExecutionException (cached 在任务数量超过 maximumPoolSize 时直接抛出异常而不是将任务放入阻塞队列)

其接口实现的类如下 :

  • org.apache .dubbo .common.threadpool. support, fixed. F ixedThreadPool ;
  • org.apache.dubbo.common.threadpool.support.cached.CachedThreadPool ;
  • org.apache.dubbo.common.threadpool.support.limited.LimitedThreadPool ;
  • org.apache.dubbo.common.threadpool.support.eager.EagerThreadPoolo

3.4 Serialize 层扩展点

Serialize 层主要实现具体的对象序列化 , 只有 Serialization 一个扩展接口 。 Serialization 是具体的对象序列化扩展接口 , 即把对象序列化成可以通过网络进行传输的二进制流 。

1. Serialization 扩展接口

Serialization 就是具体的对象序列化

Serialization 扩展接口
在这里插入图片描述
Serialization 默认使用 Hessian2 做序列化, 已有的 Serialization 扩展实现如表所示
在这里插入图片描述

其中 compactedjava 是在 Java 原生序列化的基础上做了压缩 , 实现了自定义的类描写叙述符的写入和读取 。 在序列化的时候仅写入类名 , 而不是完整的类信息 , 这样在对象数量很多的情况下 , 可以有效压缩体积 。

NativeJavaSerialization 是原生的 Java 序列化的实现方式 。JavaSerialization 是原生 Java 序列化及压缩的封装 。其他的序列化实现则封装了现在比较流行的各种序列化框架 , 如 kryo> protostuff 和 fastjson等 。

4 其他扩展点

还有其他的一些扩展点接口 : TelnetHandler、StatusChecker、 Container、 CacheFactory 、Validation 、 Logger Adapter 和 Compiler 。 由于平时使用得比较少 , 因此归类到其他扩展点中 , 下面简单介绍每个扩展点的用途 。

1. TelnetHandler 扩展点

我们知道 , Dubbo 框架支持 Telnet 命令连接 , TelnetHandler 接口就是用于扩展新的 Telnet命令的接口 。 已知的命令与接口实现之间的关系如下
在这里插入图片描述

2. StatusChecker 扩展点

通过这个扩展点 , 可以让 Dubbo 框架支持各种状态的检查 , 默认已经实现了内存和 load 的检查 。 用户可以自定义扩展 , 如硬盘 、 CPU 等的状态检查 。 已有的实现如下所示
在这里插入图片描述
3. Container 扩展点

服务容器就是为了不需要使用外部的 Tomcat 、 JBoss 等 Web 容器来运行服务 , 因为有可能服务根本用不到它们的功能 , 只是需要简单地在 Main 方法中暴露一个服务即可 。 此时就可以使用服务容器 。 Dubbo 中默认使用 Spring 作为服务容器 。

4. CacheFactory 扩展点

我们可以通过 dubbo:method 配置每个方法的调用返回值是否进行缓存 , 用于加速数据访问速度 。 已有的缓存实现如下所示
在这里插入图片描述
其中 :

  • Iru, 基于最近最少使用原则删除多余缓存 , 保持最热的数据被缓存 。
  • threadlocal, 当前线程缓存 , 比如一个页面渲染 , 用到很多 portal, 每个 portal 都要去查用户信息 , 通过线程缓存可以减少这种多余访问 。
  • jcache, 与 JSR107 集成 , 可以桥接各种缓存实现 。
  • expiring, 实现了会过期的缓存 , 有一个守护线程会一直检查缓存是否过期 。

5. Validation 扩展点

该扩展点主要实现参数的校验 , 我们可以在配置中使用 <dubbo: service validation=" 校验实现名" /> 实现参数的校验 。 己知的扩展实现有 org . apache . dubbo . validation . support .jvalidation.Validation, 扩展 key 为 jvalidation 。

6. LoggerAdapter 扩展点
日志适配器主要用于适配各种不同的日志框架 , 使其有统一的使用接口 。 已知的扩展点实现如下
在这里插入图片描述

7. Compiler 扩展点

我们在讲 Dubbo 扩展点加载机制的时候就提到 : ©Adaptive 注解会生成 Java 代码,然后使用编译器动态编译出新的 Classo Compiler 接口就是可扩展的编译器 , 现有两个具体的实现 ( adaptive 不算在内 )
在这里插入图片描述


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部