微服务面试必问的Dubbo,这么详细还怕自己找不到工作?

Dubbo 起源于阿里巴巴,对于我们做电商开发的人来说,基本是首选的技术,那么为何一个区区 soa 服务治理框架,会受到这么多人的青睐呢?

前言

互联网的不断发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对。

服务化的进一步发展,服务越来越多,服务之间的调用和依赖关系也越来越复杂,诞生了面向服务的架构体系(SOA),

也因此衍生出了一系列相应的技术,如对服务提供、服务调用、连接处理、通信协议、序列化方式、服务发现、服务路由、日志输出等行为进行封装的服务框架。

就这样分布式系统的服务治理框架就出现了,Dubbo也就这样产生了。

概念

Dubbo 是一款高性能、轻量级的开源 RPC 框架、提供服务自动注册、自动发现等高效治理方案,可以和 Spring 框架无缝集成。

简单的说,dubbo就是个分布式服务框架,在有分布式需要的时候可以使用 dubbo 的框架,使用 dubbo 的好处:

  • 1、_透明化的远程方法调用
  • 2、_软负载均衡及容错机制
  • 3、_服务自动注册与发现
  • 4、_提供了完善的服务接口管理与监控功能
image.png

创建项目

image.png

按照图片上的目录结构,创建一个maven项目,api包为需要远程调用的接口,该接口服务提供方和消费方都需要依赖。consumer为服务消费方,provider为服务提供方。

创建一个接口,服务提供方提供具体实现,消费者远程调用

//定义接口
public interface EchoDemoService {
    String echoMessage(String msg);
}
//服务方的实现,实现同时将实现类单例注入spring
@Component("echoServiceImpl")
public class EchoDemoServiceImpl implements EchoDemoService {
    @Override
    public String echoMessage(String msg) {
        System.out.println("receive msg : " + msg);
        return "OK";
    }
}

//在服务提供方,添加Spring的xml配置文件,内容如下

//定义服务基本信息
<dubbo:application name="demo-provider"/>
//定义注册中心地址
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
//定义对外提供服务的协议及端口
<dubbo:protocol name="dubbo" port="20880"/>
//定义对外提供的服务
<dubbo:service interface="com.myself.study.EchoDemoService" ref="echoServiceImpl"/>

//服务消费方也添加spring配置文件,内容如下

//定义服务基本信息
<dubbo:application name="consumer"/>
//定义注册中心地址
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
//定义远程调用的代理类
<dubbo:reference id="demoService" interface="com.myself.study.EchoDemoService" />

//添加上述配置后,我们便可以在spring中采用依赖注入的方式,调用远程接口了。
@Component
public class EchoConsumer {

    private EchoDemoService echoDemoService;

    public EchoConsumer(EchoDemoService echoDemoService) {
        this.echoDemoService = echoDemoService;
    }
    /*
        具体调用逻辑
    */
}

整个dubbo的入门使用及配置还是比较简单的。下面就开始阅读源码,了解其背后的原理。

开始分析源码

在上述入门demo中,我们使用了Spring的自定义命名来对dubbo进行配置,所以阅读源码先从解析配置开始。既然使用了自定义命名,那么必然有自定义的NamespaceHandler。我们查看dubbo的源码从META-INF文件夹下,找到了dubbo的NamespaceHandler配置。

image.png

查看文件,进入DubboNamespaceHandler这个类。首先NamespaceHandler创建后会调用init方法,在dubbo中,init方法定义了每个标签的解析器。我们 可以看到刚刚我们在配置文件里配置的application,registry,service,reference都在这里面。

    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
        registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
        registerBeanDefinitionParser("ssl", new DubboBeanDefinitionParser(SslConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }

那么接下来查看的就是parse方法。

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        BeanDefinitionParser parser = findParserForElement(element, parserContext);
        return (parser != null ? parser.parse(element, parserContext) : null);
    }

我们可以看到parse方法找到了每个标签自己的标签解析器,然后解析。我们可以看的在刚刚的init方法中,dubbo虽然为每个标签都添加了解析器,但其实他们都是一样的,都是DubboBeanDefinitionParser,只是参数不一样。所以我们先进入DubboBeanDefinitionParser大致看一下解析过程。

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        // Register DubboConfigAliasPostProcessor
        registerDubboConfigAliasPostProcessor(parserContext.getRegistry());

        return parse(element, parserContext, beanClass, required);
    }
    
    private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanClass);
        beanDefinition.setLazyInit(false);
        //...
    }

进入DubboBeanDefinitionParser后发现,其实主要的逻辑,就是在于把配置文件里的配置项,转换为DubboBeanDefinitionParser构造函数第一个参数类的属性。比方说如果配置了reference,那么这个配置就会被设置到ReferenceBean里面去,所以我们暂且不要太在意这些属性的细节,大致知道是这么个逻辑就行,等后面深入分析每个配置,分析其具体的功能。


RPC

简介

RPC 全称为 remote procedure call,即远程过程调用。比如两台服务器 A 和 B,A 服务器上部署一个应用,B 服务器上部署一个应用,A 服务器上的应用想调用 B 服务器上的应用提供的方法,由于两个应用不在一个内存空间,不能直接调用,所以需要通过网络来表达调用的语义和传达调用的数据。

RPC 并不是一个具体的技术,而是指整个网络远程调用过程。

RPC 是一个泛化的概念,严格来说一切远程过程调用手段都属于 RP C范畴。各种开发语言都有自己的 RPC 框架。Java 中的 RPC 框架比较多,广泛使用的有RMI、Hessian、Dubbo 等。

原理

服务消费方(client)调用以本地调用方式调用服务。客户端存根(client stub)接收到调用后负责将方法、参数等编码成能在网络中传输的消息体。然后,客户端存根找到服务地址后,将消息发送给服务端。

服务提供方(server)收到序列化后的消息,就按照解码该消息。然后,根据解码结果调用本地服务,执行完毕后,将结果打包发送给消费方。

服务消费方收到执行结果后,也是进行解码后得到结果。

image.png

原理

使用场景

RPC 分布式服务,拆分应用进行服务化,提高开发效率,调优性能,节省竞争资源

  • 配置管理,_解决服务的地址信息剧增,配置困难的问题
  • 服务依赖,_解决服务间依赖关系错踪复杂的问题
  • 服务扩容,_解决随着访问量的不断增大,动态扩展服务提供方的机器的问题

核心功能

Remoting:远程通讯,提供对多种 NIO 框架抽象封装,包括“同步转异步”和“请求-响应”模式的信息交换方式。

Cluster:服务框架,提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。

Registry:服务注册中心,服务自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

核心组件

  • Provider:_服务的提供方
  • Consumer:_调用远程服务的服务消费方
  • Registry:_服务注册和发现的注册中心
  • Monitor:_统计服务调用次数和调用时间的监控中心
  • Container:_服务运行容器
image.png

组件

服务注册与发现

流程如下:

  • 1、_Provider(提供者)绑定指定端口并启动服务
  • 2、_供者连接注册中心,并发本机 IP、端口、应用信息和提供服务信息发送至注册中心存储
  • 3、_Consumer(消费者),连接注册中心 ,并发送应用信息、所求服务信息至注册中心
  • 4、_注册中心根据消费者所求服务信息匹配对应的提供者列表发送至Consumer 应用缓存。
  • 5、_Consumer 在发起远程调用时基于缓存的消费者列表择其一发起调用。
  • 6、_Provider 状态变更会实时通知注册中心、在由注册中心实时推送至Consumer设计的原因:
    Consumer 与 Provider 解偶,双方都可以横向增减节点数。注册中心对本身可做对等集群,可动态增减节点,并且任意一台宕掉后,将自动切换到另一台
  • 7、_去中心化,双方不直接依懒注册中心,即使注册中心全部宕机短时间内也不会影响服务的调用
  • 8、_服务提供者无状态,任意一台宕掉后,不影响使用
image.png

流程

服务治理

治理原因

Dubbo的服务治理主要原因:

  • 1、_过多的服务 URL 配置困难。
  • 2、_负载均衡分配节点压力过大的情况下也需要部署集群。
  • 3、_服务依赖混乱,启动顺序不清晰。
  • 4、_过多服务导致性能指标分析难度较大,需要监控。

主要特性

  • 透明远程调用:_就像调用本地方法一样调用远程方法;只需简单配置,没有任何 API 侵入
  • 负载均衡机制:_Client 端 LB,可在内网替代 F5 等硬件负载均衡器
  • 容错重试机制:_服务 Mock 数据,重试次数、超时机制等
  • 自动注册发现:_注册中心基于接口名查询服务提 供者的 IP 地址,并且能够平滑添加或删除服务提供者
  • 性能日志监控:_Monitor 统计服务的调用次调和调用时间的监控中心
  • 服务治理中心:_路由规则,动态配置,服务降级,访问控制,权重调整,负载均衡,等手动配置
  • 自动治理中心:_无,比如:熔断限流机制、自动权重调整等(因此可以搭配SpringCloud的熔断机制等进行开发)
image.png

服务治理

架构设计

整体架构

先看下 Dubbo 的整体架构图:

图例说明:

image.png

整体架构

图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。

图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,ServiceConfig 层为 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

主要模块

主要模块

  • 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 容器去加载服务。

image.png

调用方式

异步调用

基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小

image.png

异步调用

本地调用

使用了Injvm协议,是一个伪协议,它不开启端口,不发起远程调用,只在JVM内直接关联,但执行Dubbo的Filter链。

Define injvm protocol:

<dubbo:protocol name="injvm" /> 

Set default protocol:

`<dubbo:provider protocol="injvm" />`

Set service protocol:

<dubbo:service protocol="injvm" />

Use injvm first:(服务暴露与服务引用都需要声明injvm=“true”)

`<dubbo:consumer injvm="true" .../>`
`<dubbo:provider injvm="true" .../>`
`或`
`<dubbo:reference injvm="true" .../>   <dubbo:service injvm="true" .../>`

容错机制

调用流程

1、_Cluster 将 Directory 中的多个 Invoker 伪装成一个Invoker,对上层透明,伪装过程包含了容错逻辑

2、_Router 负责从多个 Invoker 中按路由规则选出子集,比如读写分离,应用隔离等

3、_LoadBalance 负责从多个 Invoker 中选出具体的一个用于本次调用,选的过程包含了负载均衡算法

image.png

调用流程

容错策略

Dubbo 官网提出总共有六种容错策略

1、_Failover Cluster

失败自动切换,当出现失败,重试其它服务器。(默认)

2、_Failfast Cluster

快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

3、_Failsafe Cluster

失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

4、_Failback Cluster

失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

5、_Forking Cluster

并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。
可通过 forks=”2”来设置最大并行数。

6、_Broadcast Cluster

广播调用所有提供者,逐个调用,任意一台报错则报错。(2.1.0 开始支持) 通常用于通知所有提供者更新缓存或日志等本地资源信息。

总结:在实际应用中查询语句容错策略建议使用默认 Failover Cluster,而增删改建议使用 Failfast Cluster 或者使用 Failover Cluster(retries=”0”)策略,防止出现数据重复添加等等其它问题!建议在设计接口时候把查询接口方法单独做一个接口提供查询。

连接方式

Dubbo 的客户端和服务端有三种连接方式,分别是:广播、直连和使用Zookeeper注册中心。

Dubbo 广播

这种方式是dubbo官方入门程序所使用的连接方式,但是这种方式有很多问题,在企业开发中不使用广播的方式。

服务端配置:

`<!--配制dubbo-->`
`<!--提供应用信息,用于计算依赖关系-->`
`<dubbo:application name="demo-service"/>`
`<!--使用multicast广播注册暴露服务地址-->`
`<dubbo:registry address="multicast://192.168.9.4:88888" />`
`<!--使用dubbo协议在20880端口暴露服务-->`
`<dubbo:protocol name="dubbo" port="20880"/>`
`<!--声明暴露的服务接口-->`
`<dubbo:service interface="com.demo.manger.service.TestService" ref="testServiceImpl" />`

客户端配置:

`<!--配合dubbo-->`
`<!--提供应用信息,用于计算依赖关系-->`
`<dubbo:application name="demo-web"/>`
`<!--使用multicast广播注册中心暴露服务地址 -->`
`<dubbo:registry address="multicast://19.188.8.9:8888"/>`
`<!--声明需要暴露的接口-->`
`<dubbo:reference interface="com.demo.manager.service.TestService" id="testService" timeout="1000000" />`

Dubbo 直连

这种方式在企业中一般在开发中环境中使用,但是生产环境很少使用,因为服务是直接调用,没有使用注册中心,很难对服务进行管理。Dubbo 直连,首先要取消广播,然后客户端直接到指定需要的服务的 url 获取服务即可。

服务端配置:

`<!--配制dubbo-->`
`<!--提供应用信息,用于计算依赖关系-->`
`<dubbo:application name="demo-service"/>`
`<!--使用multicast广播注册暴露服务地址-->`
`<-- <dubbo:registry address="multicast://192.168.9.4:88888" /> -->`
`<dubbo:registry adress="N/A">`
`<!--使用dubbo协议在20880端口暴露服务-->`
`<dubbo:protocol name="dubbo" port="20880"/>`
`<!--声明暴露的服务接口-->`
`<dubbo:service interface="com.demo.manger.service.TestService" ref="testServiceImpl" />`

客户端配置:

`<!--配合dubbo-->`
`<!--提供应用信息,用于计算依赖关系-->`
`<dubbo:application name="demo-web"/>`
`<!--使用multicast广播注册中心暴露服务地址 -->`
`<-- <dubbo:registry address="multicast://19.188.8.9:8888"/> -->`
`<!--声明需要暴露的接口-->`
`<dubbo:reference interface="com.demo.manager.service.TestService" id="testService" timeout="1000000" url="dubbo://127.0.0.1:20880" />`

zookeeper 注册中心

Dubbo 注册中心和广播注册中心配置类似,不过需要指定注册中心类型和注册中心地址,这个时候就不是把服务信息进行广播了,而是告诉给注册中心进行管理,这个时候我们就需要有一个注册中心,官方推荐使用 zookeeper 作为注册中心。

image.png

注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者在启动时与注册中心交互,消费者不断的发起请求获取服务信息,注册中心不转发请求,压力较小

服务端配置:

`<!--配制dubbo-->`
`<!--提供应用信息,用于计算依赖关系-->`
`<dubbo:application name="demo-service"/>`
`<!--使用multicast广播注册暴露服务地址-->`
`<!-- <dubbo:registry address="multicast://192.168.9.4:88888" /> -->`
`<!--<dubbo:registry adress="N/A"> -->`
`<dubbo:registry protocol="zookeeper" address="192.168.37,136:2181">`
`<!--使用dubbo协议在20880端口暴露服务-->`
`<dubbo:protocol name="dubbo" port="20880"/>`
`<!--声明暴露的服务接口-->`
`<dubbo:service interface="com.demo.manger.service.TestService" ref="testServiceImpl" />`

客户端配置:

`<!--配合dubbo-->`
`<!--提供应用信息,用于计算依赖关系-->`
`<dubbo:application name="demo-web"/>`
`<!--使用multicast广播注册中心暴露服务地址 -->`
`<-- <dubbo:registry address="multicast://19.188.8.9:8888"/> -->`
`<dubbo:registry protocol="zookeeper" address="192.168.37.1336:2181"/>` 
`<!--声明需要暴露的接口-->`
`<dubbo:reference interface="com.demo.manager.service.TestService" id="testService" timeout="1000000" />`

策略

负载均衡策略

_1、_Random LoadBalance,随机(默认的负载均衡策略)

RandomLoadBalance 是加权随机算法的具体实现,可以完全随机,也可以按权重设置随机概率。

_2、_RoundRobin LoadBalance,轮循

可以轮询和加权轮询。存在响应慢的提供者会累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

_3、_LeastActive LoadBalance,最少活跃调用数

活跃调用数越小,表明该服务提供者效率越高,单位时间内可处理更多的请求。此时应优先将请求分配给该服务提供者。

_4、_ConsistentHash LoadBalance,一致性 Hash

一致性 Hash 算法,相同参数的请求一定分发到一个 provider 上去。provider 挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。

集群容错策略

_1、_failover cluster(默认)

失败自动切换,调用失败时,自动重试其他机器。通常用于读操作,但重试会带来更长延迟。

_2、_Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

_3、_Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

_4、_Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

_5、_Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。

动态代理策略

默认使用 javassist 动态字节码生成,创建代理类。也可以通过 spi 扩展机制配置自己的动态代理策略。

集群容错方案

配置说明,方案配置方式,优先使用消费端配置

`<!--服务端配置-->`
`<dubbo:service cluster="failover"/>`
`<!--消费端配置-->`
`<dubbo:reference cluster="failover"/>`
  • 尽量在只在服务端进行配置
  • cluster类型均为小写
  • 默认为FailoverCluster失败切换方案

集群容错方案support

FailoverCluster(默认):失败切换

  • 场景:调用失败后切换其他服务
  • 配置:
`<!--`
`retries:重试次数,不包括第一次,默认2次`
`-->`
`<dubbo:service cluster="failover" retries="3"/>`

代码实现逻辑:

  1. 根据负载均衡策略选出需要调用的服务实例,排除已调用的

  2. 执行选出的实例,并将其保存到已调用列表中

  3. 执行实例成功即返回

  4. 执行实例不成功,为到最大重试次数则执行第一步,否则抛出RpcException异常

FailbackCluster:失败重试

  • 场景:调用失败时记录失败请求,定时重发

  • 配置:

`<!--`
`retries:重试次数,不包括第一次,默认3次`
`failbacktasks:定时器中最大挂起任务数,默认100`
`-->`
`<dubbo:service cluster="failback" retries="5" failbacktasks="200"/>`

代码实现逻辑

  1. 根据负载均衡策略选出需要调用的服务实例

  2. 执行选出的实例

  3. 执行实例成功即返回

  4. 执行异常则创建延时5秒的定时任务,并加入时间轮定时器,第一次需要进行定时器初始化,分为32个时间片,每1秒滚动一次,最大挂起任务默认100个,超出最大任务数时抛出RejectedExecutionException异常。

  5. 重试执行定时任务,次数超出最大执行次数停止,并输出error日志,默认为3次。

FailfastCluster:快速失败

  • 场景:调用失败立即报错
  • 配置:
`<dubbo:service cluster="failfast"/>`

代码实现逻辑

  1. 根据负载均衡策略选出需要调用的服务实例

  2. 执行选出的实例

  3. 执行实例成功即返回,失败抛出RpcException异常

FailsafeCluster:安全失败

  • 场景:调用失败后忽略
  • 配置:
`<dubbo:service cluster="failsafe"/>`

代码实现逻辑

  1. 根据负载均衡策略选出需要调用的服务实例

  2. 执行选出的实例

  3. 执行实例成功即返回,失败输出error日志,并返RpcResult,视为忽略。

ForkingCluster:并发处理

  • 场景:并发调用指定数量的服务,一个成功则返回,对实时性要求高的场景,要求快速返回,需要使用更多服务器资源。
  • 配置:
`<!--`
`forks:最大并发数,默认2`
`timeout:并发返回超时时间,默认1000ms`
`-->`
`<dubbo:service cluster="forking" forks="3" timeout="500"/>`

代码实现逻辑

  1. 根据负载均衡策略选出几个不同的服务实例

  2. 并发执行选出的几个实例,并将返回结果放入堵塞队列中

  3. 返回堵塞队列中的第一个值,如规定时间内未获取到队列中的值或获取到异常值则返回RPC异常。

BroadcastCluster:广播

  • 场景:广播方式逐个调用服务提供者,有一个报错则返回错误,多用于通知服务提供者更新本地资源信息,如缓存,日志等。
  • 配置:
`<dubbo:service cluster="broadcast"/>`

代码实现逻辑

  1. 循环逐个执行所有服务实例信息

  2. 保存一份返回结果和异常信息

  3. 执行完全部实例后,如异常信息不为空,则抛出异常信息,否则返回最后一个实例的结果。

AvailableCluster:可用服务

  • 场景:调用第一个可用服务
  • 配置:
`<dubbo:service cluster="available"/>`

代码实现逻辑

  1. 循环所有服务实例信息

  2. 执行第一个可用的实例,并返回结果

  3. 如无可用实例则返回RpcException异常

MergeableCluster:合并处理

  • 场景:返回合并或叠加处理结果
  • 配置:
`<!--`
`merger:合并发放名`
`timeout:调用服务超时时间,默认1000ms`
`-->`
`<dubbo:service cluster="mergeable" merger="true" timeout="500"/>`

代码实现逻辑

  1. 判断merger,为空、null、0、false、N/A是执行第一个可用服务并返回结果,无可用则执行第一个实例,并返回结果。

  2. 获取方法实例的返回类型

  3. 异步调用所有实例,并将异步结果Result存储到结果集中,返回异常输出error日志

  4. 结果集为空返回 RpcException,大小为 1时返回第一个Result

  5. 当merger的第一个字符为“.”时,判断当 merger 实例返回类型不为void,且返回类型必须是结果集中第一个返回类型的父类型或相同类型时,循环执行merger实例,每一次都传入上一次的返回结果,最终返回获取最后一次结果,非上述情况时循环执行merger实例,返回结果集中的第一个结果。

  6. 当merger为true或default时使用Dubbo默认合并器,否则使用自定义merger合并器,合并后返回

RegistryAwareCluster:默认标识、注册标识

  • 场景:调用注册默认标识的服务
  • 配置:
`<!--`
`default:默认标识`
`-->`
`<dubbo:registry address="zookeeper://xxx..." default="true"/>`
`<dubbo:service cluster="registryaware"/>`

代码实现逻辑

1.8 循环所有服务实例信息

  1. 执行第一个可用的实例且default为true的实例

  2. 无默认实例则执行第一个可用的实例

  3. 无可用的实例则抛出RpcException异常

主要配置

配置应用信息:

`<dubbo:application name=“appName-provider” />`

配置注册中心相关信息:

`<dubbo:registryid=“zk” protocol=“zookeeper” address=“127.0.0.1:2181” />`

配置服务协议:

`<dubbo:protocol name=“dubbo” port=“20880” threadpool=“cached” threads=“80” />`

配置所有暴露服务缺省值:

`<dubbo:provider registry=“zk” protocol=“dubbo” retries=“0” version=“1.0.0” timeout=“3000” threadpool=“cached” threads=“4”/>`

配置暴露服务:

`<dubbo:service interface=“com.orgname.app.serviceX” ref=“serviceX” />`

配置所有引用服务缺省值:

`<dubbo:consumer check=“false” timeout=“1000” version=“1.0” retries=“0” async=“false” />`

注解配置:

`com.alibaba.dubbo.config.annotation.Service 配置暴露服务`
`com.alibaba.dubbo.config.annotation.Reference配置引用服务`

超时设置

Dubbo消费端

全局超时配置

`<dubbo:consumer timeout="5000" />`

指定接口以及特定方法超时配置

`<dubbo:reference interface="com.foo.BarService" timeout="2000">`
 `<dubbo:method name="sayHello" timeout="3000" />`
`</dubbo:reference>`

Dubbo服务端

全局超时配置

`<dubbo:provider timeout="5000" />`

指定接口以及特定方法超时配置

`<dubbo:provider interface="com.foo.BarService" timeout="2000">`
 `<dubbo:method name="sayHello" timeout="3000" />`
`</dubbo:provider>`

支持协议

_1、_Dubbo 协议(官方推荐协议)

优点:采用NIO复用单一长连接,并使用线程池并发处理请求,减少握手和加大并发效率,性能较好(推荐使用)

缺点:大文件上传时,可能出现问题(不使用 Dubbo 文件上传)

_2、_RMI(Remote Method Invocation)协议

优点:JDK 自带的能力。可与原生 RMI 互操作,基于 TCP 协议

缺点:偶尔连接失败.

_3、_Hessian协议

优点:可与原生 Hessian 互操作,基于 HTTP 协议

缺点:需 hessian.jar 支持,http 短连接的*_开销大_8

常用设计模式

Dubbo 框架在初始化和通信过程中使用了多种设计模式,可灵活控制类加载、权限控制等功能。

工厂模式

Provider 在 export 服务时,会调用 ServiceConfig 的 export 方法。ServiceConfig 中有个字段:

`private static final Protocol protocol =`
`ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();`

Dubbo 里有很多这种代码。这也是一种工厂模式,只是实现类的获取采用了 JDK SPI 的机制。这么实现的优点是可扩展性强,想要扩展实现,只需要在 classpath下增加个文件就可以了,代码零侵入。另外,像上面的 Adaptive 实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。

装饰器模式

Dubbo 在启动和调用阶段都大量使用了装饰器模式。以 Provider 提供的调用链为例,具体的调用链代码是在 ProtocolFilterWrapper 的buildInvokerChain 完成的,具体是将注解中含有 group=provider 的 Filter 实现,按照 order 排序,最后的调用顺序是:

`EchoFilter -> ClassLoaderFilter -> GenericFilter -> ContextFilter ->`
`ExecuteLimitFilter -> TraceFilter -> TimeoutFilter -> MonitorFilter ->`
`ExceptionFilter`

更确切地说,这里是装饰器和责任链模式的混合使用。例如,EchoFilter 的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现。而像ClassLoaderFilter 则只是在主功能上添加了功能,更改当前线程的 ClassLoader,这是典型的装饰器模式。

观察者模式

Dubbo 的 Provider 启动时,需要与注册中心交互,先注册自己的服务,再订阅自己的服务,订阅时,采用了观察者模式,开启一个 listener。注册中心会每 5 秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个 notify 消息,provider 接受到 notify 消息后,即运行 NotifyListener 的 notify 方法,执行监听器方法。

动态代理模式

Dubbo 扩展 JDK SPI 的类 ExtensionLoader 的 Adaptive 实现是典型的动态代理实现。Dubbo 需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是 ExtensionLoader 的 createAdaptiveExtensionClassCode 方法。代理类的主要逻辑是,获取 URL 参数中指定参数的值作为获取实现类的 key

工作流程

整体流程:

  • 第一步:provider 向注册中心去注册
  • 第二步:consumer 从注册中心订阅服务,注册中心会通知 consumer 注册好的服务
  • 第三步:consumer 调用 provider
  • 第四步:consumer 和 provider 都异步通知监控中心
image.png

最后用一张图来形象的模拟 Dubbo 的使用:

image.png

使用

以上只是我总结的一些关于 dubbo 最基础的原理及使用介绍,至于代码编写过程的 bug 处理经验,环境搭建、项目布局等等问题,需要我们在平时开发中,将系统知识与实战经验相结合去总结,这样才能真正的去掌握这项技术点。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容