Dubbo框架架构

一、整体框架

1、Dubbo介绍

Apache Dubbo是一款高性能、轻量级的开源Java RPC框架。
它有三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

1、Dubbo特点

1、面向接口代理的高性能RPC调用:提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程调用底层细节。
2、智能负载均衡:内置多种负载均衡策略,智能感知下游节点健康状况,显著减少调用延迟,提高系统吞吐量。
3、服务自动注册与发现:支持多种注册中心服务,服务实例上下线实时感知。
4、高度可扩展能力: 遵循微内核+插件的设计原则,所有核心能力如Protocol、Transport、Serialization被设计为扩展点,平等对待内置实现和第三方实现。
5、运行期流量调度:内置条件、脚本等路由策略,通过配置不同的路由规则,轻松实现灰度发布,同机房优先等功能。
6、可视化的服务治理与运维:提供丰富服务治理、运维工具:随时查询服务元数据、服务健康状态及调用统计,实时下发路由策略、调整配置参数。

2、Dubbo缺点

1、跨语言支持较弱:目前Dubbo只在JAVA语言流行,其他语言支持较少。不适用跨语言调用。
2、Registry 严重依赖第三方组件(ZooKeeper 或者 Redis),当这些组件出现问题时,服务调用很快就会中断。

3、业内其他方案

gRPC

是Google开发的高性能、通用的开源RPC框架,基于ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。在业界非JAVA语言的框架中,GRPC是最为常用通用的rpc框架。不过GRPC没有服务发现功能,一般需要结合ETCD做服务发现功能。

spring-cloud

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。

Motan

motan是新浪微博开源的一套轻量级、方便使用的RPC框架。支持动态自定义负载均衡、跨机房流量调整等高级服务调度能力。基于高并发、高负载场景进行优化,保障生产环境下RPC服务高可用。

框架的对比

框架 开发语言 服务治理 跨语言支持 调用方式 管理中心 关注度
dubbo java zookeeper 基本不支持 tcp 支持
gRPC 多语言 不执行,需要自行实现 支持 http 不支持
springCloud java spring cloud Eureka 不支持 http-rest 支持
Motan java consul、zookeeper java、go、php tcp 支持

dubbox vs dubbo

另外由于dubbo在2012年开源之后,在2017年中旬一直没有做更新,故当当网基于dubbo改造了dubbox的版本。以下是dubbox和dubbo的对比只要特点如下:

  • dubbox支持rest协议:http+json的restful风格,dubbo本身只有tcp方式
  • dubbox支持更高效的序列化工具:Kryo和FST、JSON
  • dubbox相对dubbo升级了zookeeper客户端
  • 支持基于嵌入式Tomcat的HTTP remoting体系。
  • 升级Spring版本到3.x

2、Dubbo整体架构

1、生命周期架构

Dubbo框架架构_第1张图片
上述所述为Dubbo内部交互图,主要包括:

  • Provider 暴露服务的服务提供方
  • Consumer 调用远程服务的服务消费方
  • Registry 服务注册与发现的注册中心
  • Monitor 统计服务的调用次数和调用时间的监控中心

调用流程如下所述:
1、服务启动,包括服务提供者和消费者的启动,封装服务调用链路。
2、服务提供者在启动时,向注册中心注册自己提供的服务。
3、服务消费者在启动时,向注册中心订阅自己所需的服务。
4、注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
5、服务消费者,从提供者地址列表中,基于服务路由信息、负载均衡规则,选一台提供者进行调用。
6、服务消费者和提供者,在内存中累计调用次数和调用时间,定时发送一次统计数据到监控中心。
7、服务提供方停止服务或者服务调用方关闭JVM的时候,会将provider及consumer进行销毁处理。

2、层级架构

Dubbo框架架构_第2张图片
Dubbo框架设计一共划分了10个层,而最上面的Service层是留给实际想要使用Dubbo开发分布式服务的开发者实现业务逻辑的接口层。图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口, 位于中轴线上的为双方都用到的接口。

  • 服务接口层(Service):该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现。
  • 配置层(Config):对外配置接口,以ServiceConfig和ReferenceConfig为中心,可以直接new配置类,也可以通过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。Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
  • 信息交换层(Exchange):封装请求响应模式,同步转异步,以Request和Response为中心,扩展接口为Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。
  • 网络传输层(Transport):抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、Transporter、Client、Server和Codec。
  • 数据序列化层(Serialize):可复用的一些工具,扩展接口为Serialization、 ObjectInput、ObjectOutput和ThreadPool。

3、源码模块

可以通过Dubbo的代码(使用Maven管理)组织,与上面的模块进行比较。简单说明各个包的情况:

  • 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 容器模块,是一个Standalone的容器,以简单的Main加载Spring启动,因为服务通常不需要Tomcat/JBoss等Web容器的特性,没必要用Web容器去加载服务。

二、基础技术

1、dubbo网络通信remoting及线程模型
dubbo中网络通信主要借助通过Netty来实现server及client。在netty接收完消息后,将通过不同的线程模型分配进行业务处理。
1、Dubbo中线程派发Dispatcher分配类型包括:
all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
direct 所有消息都不派发到线程池,全部在 IO 线程上直接执行。
message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
execution 只请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
connection 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。

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

3、消息发送涉及到:同步、异步、单向方式
4、dubbo数据包结构:Dubbo 数据包分为消息头和消息体,消息头用于存储一些元信息,比如魔数(Magic),数据包类型(Request/Response),消息体长度(Data Length)等。消息体中用于存储具体的调用消息

5、序列化:在远程通信过程中涉及到序列化内容,主要采用Hessian2。

2、SPI机制

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。
Dubbo中SPI机制类为ExtensionLoader,ExtensionLoader是扩展点载入器,用于载入Dubbo中的各种可配置组件,比如:动态代理方式(ProxyFactory)、负载均衡策略(LoadBalance)、RCP协议(Protocol)、拦截器(Filter)、容器类型(Container)、集群方式(Cluster)和注册中心类型(RegistryFactory)。
SPI 做了三个方面的扩展:
方便获取扩展实现:JDK SPI仅仅通过接口类名获取所有实现,而ExtensionLoader则通过接口类名和key值获取一个实现;
IOC依赖注入功能:Adaptive实现,就是生成一个代理类,这样就可以根据实际调用时的一些参数动态决定要调用的类了。
采用装饰器模式进行功能增强,自动包装实现,这种实现的类一般是自动激活的,常用于包装类,比如:Protocol的两个实现类:ProtocolFilterWrapper、ProtocolListenerWrapper。

Adaptive自适应扩展机制

自适应拓展机制的实现逻辑比较复杂,首先 Dubbo 会为拓展接口(Adaptive )生成具有代理功能的代码。然后通过 javassist 或 jdk 编译这段代码,得到 Class 类、

当 Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时,Dubbo 则会为该方法生成代理逻辑。

Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况,表示拓展的加载逻辑由人工编码完成。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成。Adaptive 注解的地方不同,相应的处理逻辑也是不同的。

三、核心功能拆解

1、服务启动

服务启动包括服务暴露及服务引入,主要涉及Exporter及Invoker的包装。

1、服务暴露

Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑。整个逻辑大致可分为三个部分,
第一部分是前置工作,主要用于检查参数,组装 URL。

第二部分是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。

第三部分是向注册中心注册服务,用于服务发现。

Dubbo框架架构_第3张图片

2、服务引入

Dubbo 服务引用的时机有两个,第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务,第二个是在 ReferenceBean 对应的服务被注入到其他类中时引用。这两个引用服务的时机区别在于,第一个是饿汉式的,第二个是懒汉式的。默认情况下,Dubbo 使用懒汉式引用服务。如果需要使用饿汉式,可通过配置 dubbo:reference 的 init 属性开启。下面我们按照 Dubbo 默认配置进行分析,整个分析过程从 ReferenceBean 的 getObject 方法开始。当我们的服务被注入到其他类中时,Spring 会第一时间调用 getObject 方法,并由该方法执行服务引用逻辑。按照惯例,在进行具体工作之前,需先进行配置检查与收集工作。接着根据收集到的信息决定服务用的方式,有三种,第一种是引用本地 (JVM) 服务,第二是通过直连方式引用远程服务,第三是通过注册中心引用远程服务。不管是哪种引用方式,最后都会得到一个 Invoker 实例。如果有多个注册中心,多个服务提供者,这个时候会得到一组 Invoker 实例,此时需要通过集群管理类 Cluster 将多个 Invoker 合并成一个实例。合并后的 Invoker 实例已经具备调用本地或远程服务的能力了,但并不能将此实例暴露给用户使用,这会对用户业务代码造成侵入。此时框架还需要通过代理工厂类 (ProxyFactory) 为服务接口生成代理类,并让代理类去调用 Invoker 逻辑。避免了 Dubbo 框架代码对业务代码的侵入,同时也让框架更容易使用。
Dubbo框架架构_第4张图片

2、注册中心注册及订阅

服务注册就是把已经暴露的服务信息注册到第三方平台,以供消费者使用。服务注册主要涉及的逻辑在RegistryProtocol.export方法。
服务订阅是消费端订阅所需的服务,并执行服务引用,服务订阅主要在RegistryProtocol 的 refer 方法逻辑。消费端完成订阅后,若注册中心存在服务提供者,那么消费端将对服务为服务做成服务目录,供调用的时候获取地址列表。
目前dubbo支持的注册中心包括zookeeper和redis等,笔者将在详细文档中做源码讲解,下面介绍下消费端如何维护服务列表。

服务字典
服务目录目前内置的实现有两个,分别为 StaticDirectory 和 RegistryDirectory,实际环境中主要使用RegistryDirectory,当注册中心节点信息发生变化后,RegistryDirectory 可以通过此接口方法得到变更信息,并根据变更信息动态调整内部 Invoker 列表。

RegistryDirectory服务字典
RegistryDirectory 收到变更通知后,可根据配置变更信息刷新 Invoker 列表。RegistryDirectory 中有几个比较重要的逻辑,第一是 Invoker 的列举逻辑,第二是接收服务配置变更的逻辑,第三是 Invoker 列表的刷新逻辑。

3、调用链路

Dubbo框架架构_第5张图片
Dubbo 服务调用过程比较复杂,包含众多步骤,比如发送请求、编解码、服务降级、过滤器链处理、序列化、线程派发以及响应请求等步骤。
Dubbo 支持同步和异步两种调用方式,其中异步调用还可细分为“有返回值”的异步调用和“无返回值”的异步调用。所谓“无返回值”异步调用是指服务消费方只管调用,但不关心调用结果,此时 Dubbo 会直接返回一个空的 RpcResult。若要使用异步特性,需要服务消费方手动进行配置。默认情况下,Dubbo 使用同步调用方式。
从下往上看,首先服务消费者通过代理对象 Proxy 发起远程调用,接着通过网络客户端 Client 将编码后的请求发送给服务提供方的网络层上,也就是 Server。Server 在收到请求后,首先要做的事情是对数据包进行解码。然后将解码后的请求发送至分发器 Dispatcher,再由分发器将请求派发到指定的线程池上,最后由线程池调用具体的服务。这就是一个远程调用请求的发送与接收过程。

1、服务路由

服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由,筛选出符合路由规则的服务提供者。在详细分析服务路由的源码之前,先来介绍一下服务路由是什么。服务路由包含一条路由规则,路由规则决定了服务消费者的调用目标,即规定了服务消费者可调用哪些服务提供者。Dubbo 目前提供了三种服务路由实现,分别为条件路由 ConditionRouter、脚本路由 ScriptRouter 和标签路由 TagRouter。其中条件路由是我们最常使用的。

2、集群

Dubbo框架架构_第6张图片
集群工作过程可分为两个阶段:
第一个阶段是在服务消费者初始化期间,集群 Cluster 实现类为服务消费者创建 Cluster Invoker 实例,即上图中的 merge 操作。
第二个阶段是在服务消费者进行远程调用时。以 FailoverClusterInvoker 为例,该类型 Cluster Invoker 首先会调用 Directory 的 list 方法列举 Invoker 列表(可将 Invoker 简单理解为服务提供者)。Directory 的用途是保存 Invoker,可简单类比为 List。其实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Invoker 列表会随着注册中心内容的变化而变化。每次变化后,RegistryDirectory 会动态增删 Invoker,并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker。当 FailoverClusterInvoker 拿到 Directory 返回的 Invoker 列表后,它会通过 LoadBalance 从 Invoker 列表中选择一个 Invoker。最后 FailoverClusterInvoker 会将参数传给 LoadBalance 选择出的 Invoker 实例的 invoker 方法,进行真正的远程调用。

Dubbo 主要提供了这样几种容错方式:

  • Failover Cluster - 失败自动切换 最为常用
  • Failfast Cluster - 快速失败
  • Failsafe Cluster - 失败安全
  • Failback Cluster - 失败自动恢复
  • Forking Cluster - 并行调用多个服务提供者

3、负载均衡

在 Dubbo 中,也有负载均衡的概念和相应的实现。Dubbo 需要对服务消费者的调用请求进行分配,避免少数服务提供者负载过大。服务提供者负载过大,会导致部分请求超时。因此将负载均衡到每个服务提供者上,是非常必要的。Dubbo 提供了4种负载均衡实现:

  • 基于权重随机算法的 RandomLoadBalance
  • 基于最少活跃调用数算法的 LeastActiveLoadBalance
  • 基于 hash 一致性的 ConsistentHashLoadBalance
  • 基于加权轮询算法的 RoundRobinLoadBalance

4、Fifter链及Monitor监控统计

filter在dubbo中的应用非常广泛,它通过在启动中对服务端及消费端方法前注入fifter链,使得在调用的时候它可以对服务端、消费端的调用过程进行拦截,从而对dubbo进行功能上的扩展。
dubbo中比较常见的fifter链包括:EchoFilter、GenericFilter、TokenFilter、MonitorFilter、ContextFilter等。
其中GenericFilter是泛化调用的核心,它能将调用泛化的url地址转换为真实的类及方法地址。
MonitorFilter是监控统计的核心,MonitorFilter统计计算数据,然后通过DubboMonitor将数据进行聚合,并发送给外界。
GenericFilter及MonitorFilter笔者将在详细文档中做源码剖析。

5、服务优雅销毁停机

在整个链路调用完成后,总有一个时间点需要将服务从注册中心取消,并销毁服务提供者。这个时候如果在服务切换的过程中老的服务没有正常关闭的话,容易造成内存清理问题,所以优雅停机是dubbo的重要的一环。
Dubbo的优雅停机是依赖于JDK的ShutdownHook函数。主要涉及到在运行时注册钩子函数,并在销毁是主动调用钩子函数。

你可能感兴趣的:(java)