RPC调用之Dubbo

366 阅读34分钟

什么是RPC

RPC是远程过程调用(Remote Procedure Call)。 RPC 的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC 框架需提供一种透明调用机制,让使用者不必显式的区分本地调用和远程调用。

都有哪些RPC框架

  • Dubbo:国内最早开源的 RPC 框架,由阿里巴巴公司开发并于 2011 年末对外开源,仅支持 Java 语言。
  • Motan:微博内部使用的 RPC 框架,于 2016 年对外开源,仅支持 Java 语言。
  • Tars:腾讯内部使用的 RPC 框架,于 2017 年对外开源,仅支持 C++ 语言。
  • Spring Cloud:国外 Pivotal 公司 2014 年对外开源的 RPC 框架,提供了丰富的生态组件。
  • gRPC:Google 于 2015 年对外开源的跨语言 RPC 框架,支持多种语言。
  • Thrift:最初是由 Facebook 开发的内部系统跨语言的 RPC 框架,2007 年贡献给了 Apache 基金,成为Apache 开源项目之一,支持多种语言。

RPC框架的特点

  • 1、RPC框架一般使用长链接,不必每次通信都要3次握手,减少网络开销。
  • 2、RPC框架一般都有注册中心,有丰富的监控管理。发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作协议私密,安全性较高
  • 3、RPC 协议更简单内容更小,效率更高,服务化架构、服务化治理,RPC框架是一个强力的支撑。

什么是Dubbo

Dubbo是阿里巴巴公司开源的一个高性能、轻量级的 Java RPC 框架

致力于提供高性能和透明化的 RPC 远程服务调用方案,以及 SOA 服务治理方案。

Dubbo主要特性

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

Dubbo的基本使用

Dubbo支持的注册中心

consul

zk

Eureka

Redis

ectd

nacos

Dubbo Spring Cloud 相比较

DubboSpring Cloud
服务注册中心ZookeeperSpring Cloud Netfix Eureka
服务调用方式RPCREST API
服务监控Dubbo-monitorSpring Boot Admin
熔断器不完善Spring Cloud Netflix Hystrix
服务网关Spring Cloud Netflix Zuul
分布式配置Spring Cloud Config
服务跟踪Spring Cloud Sleuth
数据流Spring Cloud Stream
批量任务Spring Cloud Task
信息总线Spring Cloud Bus

Dubbo Spring Cloud 对比

Spring Cloud

功能组件Spring CloudDubbo Spring Cloud
分布式配置(Distributed configuration)Git、Zookeeper、Consul、JDBCSpring Cloud 分布式配置 + Dubbo 配置中心
服务注册与发现(Service registration and discovery)Eureka、Zookeeper、ConsulSpring Cloud 原生注册中心 + Dubbo 原生注册中心
负载均衡(Load balancing)Ribbon(随机、轮询等算法)Dubbo 内建实现(随机、轮询等算法 + 权重等特性)
服务熔断(Circuit Breakers)Spring Cloud HystrixSpring Cloud Hystrix + Alibaba Sentinel 等
服务调用(Service-to-service calls)Open Feign、RestTemplateSpring Cloud 服务调用 + Dubbo @Reference
链路跟踪(Tracing)Spring Cloud Sleuth + ZipkinZipkin、opentracing 等

以上对比表格摘自Dubbo Spring Cloud官方文档。

本质上 都是为业务而生

Dubbo的主要特性:

Dubbo的新版本当中有非常多的支持分布式特性的一些功能:

  • 面向接口代理的高性能的RPC调用:提供了高性能的基于代理的远程调用能力,服务以接口为粒度,屏蔽了底层远程调用的细节. @Reference @DubboReference <dubbo:Reference....
  • 智能的负载均衡的能力:内置了多种负载均衡策略,智能的感知下游节点的健康状况,显著的减少调用延迟,提高系统吞吐。
  • 服务的自动注册与发现:支持我们多种注册中心,实现服务实例的上下线感知

image.png

  • 高度可拓展能力:遵循微内核+插件的设计原则,所有的核心能力,都会被设计为拓展点,平等的对待内置实现还是第三方的实现
  • 运行期流量调度:内置条件,脚本等路由策略,通过配置不同的路由规则,实现灰度发布,同机房优先等功能。
  • 可视化的服务治理以及运维:提供了丰富的服务治理工具,运维工具,随时可以查询服务的元数据,服务健康状态,调用统计,实时的下发路由策略,调整配置参数。

Dubbo默认使用什么协议

Dubbo缺省协议是dubbo协议,采用的是单一长连接和NIO异步通信的方式,适用于小数据量以及大并发的服务调用,消费者远大于提供者,Dubbo的传输层协议TCP,异步的,使用Hessian序列化

Dubbo 支持哪些协议,每种协议的应用场景,优缺点?

  • dubbo:Dubbo缺省协议是dubbo协议,采用的是单一长连接和NIO异步通信的方式,适用于小数据量以及大并发的服务调用,消费者远大于提供者,Dubbo的传输层协议TCP,异步的,使用Hessian序列化
  • rmi(remote Method invocation):采用的是我们的JDK标准的rmi的实现,传输以及返回参数的时候都需要实现Serializable接口,使用java的标准的序列化机制,使用的是阻塞式的短连接,传输数据包可以大小混合,并且我们的消费者与提供者可以差不多数量,可以传文件。传输协议TCP,同步传输,适用于常规的远程服务调用以及rmi的相互操作。序列化安全漏洞。
  • webservice:基于 WebService 的远程调用协议,集成 CXF 实现,提供和原生 WebService 的互操作。多个短连接,基于 HTTP 传输,同步传输,适用系统集成和跨语言调用;
  • http:基于 Http 表单提交的远程调用协议,使用 Spring 的 HttpInvoke 实现。多个短连接,传输协议 HTTP,传入参数大小混合,提供者个数多于消费者,需要给应用程序和浏览器 JS 调用;
  • hessian:集成 Hessian 服务,基于 HTTP 通讯,采用 Servlet 暴露服务,Dubbo 内嵌 Jetty 作为服务器时默认实现,提供与 Hession 服务互操作。多个短连接,同步 HTTP 传输,Hessian 序列化,传入参数较大,提供者大于消费者,提供者压力较大,可传文件;

RPC与HTTP的区别

image.png

RPC(remote proedure call):Dubbo,Grpc,Thrift

如何选择RPC与Rest风格的HTTP

RPC:远程方法调用,远程直接调用你的方法,可以选择Http做为我们的最终协议。Http2.0,

Rest风格的HTTP:Rest风格,可读性好,跨语言,得到防火墙的支持。异构设计,开发快。

缺陷:效率低,带来大量的无用信息。

如果你的项目不是那么大,并且希望快速开发,那么你使用Rest风格的HTTP,但是如果你的公司牛人很多,建议你使用RPC,你的项目比较大,对于网络传输的速率要求比较严格,那么你也可以使用RPC。

Dubbo与Spring Cloud的区别

Spring Cloud Netfilx Spring Cloud Alibaba

开发效率快 网络传输速度慢 拓展性不强

Spring Cloud Alibaba

Dubbo是二进制传输 ,占用的带宽会更小

Spring Cloud Rest风格的HTTP 消耗很多

dubbo的开发难度大

Spring Cloud

Dubbo : Reids ZK .......

Spring Cloud :遵循Spring Cloud 规范的注册中心

Dubbo一般使用什么注册中心?还有别的选择吗?

Dubbo默认的注册中心是哪个,还有没有什么更好的选择

Zookeeper CP

consul go语言写的,跟docker完美兼容

Eureka AP Peer to Peer

Redis 不推荐

ectd 不推荐

nacos AP CP 最好的

为什么需要服务治理?

1.过多的服务 ,Url配置困难

2.负载均衡分配节点 ,在压力过大的情况下,我们是需要部署集群的

3.服务依赖混乱,启动顺序不清晰

4.过多的服务导致性能指标分析难度大,需要监控。

服务治理手段

服务注册:服务提供者注册到注册中心服务端

服务续约:服务提供者需要给到注册中心服务端一个信号,告诉他,我自身的状态(心跳)。

服务获取:消费者去注册中心服务端获取服务提供者列表,并且缓存到本地

服务调用:我通过我们从注册中心服务端获取到的服务提供者信息,调用服务提供者

服务下线:服务提供者从注册中心服务端取消注册自己,从而使消费者拉取不到他

失效剔除:服务续约是发送心跳,但是心跳异常之后,我们需要根据规则对服务提供者进行剔除

自我保护:我网不好,那么这个时候我会制定规则,防止失效剔除的异常剔除情况出现,保护注册中心的服务提供者列表

服务同步:注册中心是集群,那么这个时候就有可能出现,服务提供者注册到A上,而消费者消费的是B,那么这个时候A跟B需要同步注册信息。

动态感知:一般情况下都是通过观察者模式,服务启动,会有很多的事件(EVENT)进行发布,那么这个时候我们的注册中心会去监听其中的一些启动事件,那么我们就能够感知我们的服务启动。同时进行自动注册流程。

Dubbo的集群的容错方案

failover Cluster(默认:)失败自动进行切换,当出现失败之后,重试其他服务器,通常用于读操作。但是重试会带来更长的延迟。

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

failsafe Cluster: 失败安全, 出现异常的时候,直接忽略。通常这种策略是用于写入审计日志等操作。

failBack Cluster:失败自动恢复:后台一旦记录请求失败,定时重发。通常用于消息通知。

forking Cluster :并行调用多个服务器,只要有一个成功即返回。通常用于对于实时性要求较高的读操作,但是我们使用这种方式会极大的浪费服务器资源,可以设置最大的并行数。forks=‘3’。

broadcast Cluster:广播所有提供者,逐个调用。任何一台报错我就会直接报错。通常用于通知所有的提供者更新缓存或者日志等本地信息。

Dubbo 有哪些负载均衡策略?

Random LoadBalance

随机,按照权重的设置随机几率

实际上我们的随机,按照我们的权重设计 A 3 B 5 C 2

RoundRobin LoadBalance

轮询 默认将流量均匀的打到各个机器上。但是各个机器的配置假设不一样,那么这个时候性能也不一样,容易导致性能差的机器负载过高。所以,轮询还是需要调整权重

LeastActive LoadBalance

最小活跃调用数,评估你的服务器性能,判定假设你某台机器性能很差,这个时候相应的我去进行分配的请求也就越少。

ConsistentHash LoadBalance

一致性Hash算法

相同的参数请求一定是分发到同一台机器的,解决原有的Hash算法可能会导致的节点抖动问题

Dubbo 核心组件有哪些?

  • provider :暴露服务的服务提供方
  • Consumer:调用远程服务的消费方
  • Registry :服务注册与发现中心
  • Monitor:监控中心,访问统计
  • Container:服务运行容器

Dubbo 服务器注册与发现的流程?

1.服务容器负责启动,加载,以及运行服务提供者

2.服务提供者在启动的时候,向注册中心注册自己提供的服务

3.服务消费者启动时,需要向注册中心订阅自己所需要的服务提供者信息

4.注册中心向服务消费者返回服务提供者地址列表,并且一旦发生变更,那么注册中心会基于长连接推送变更数据

5.服务消费者会从本地缓存的服务提供者列表中,基于负载均衡算法,选择一台进行调用,一旦失败,他会基于容错机制FailOver Cluster,再次选择一台进行调用。

6.服务提供者与服务消费者,都会在内存中统计调用次数以及调用时间,定时每分钟发送一次统计数据给到监控中心。

Dubbo 的整体架构设计有哪些分层?

/dev-guide/images/dubbo-framework.jpg

十层分层,遵循了领域驱动设计原则,并且他是在分层架构设计的基础上进行衍生的。invoker调用器

严格分层架构设计

松散分层架构设计

Service 这一层次是与业务相关的,根据我们的服务提供者以及服务的消费者进行对应的业务接口设计与实现

Config 配置层:对外配置接口,以ServiceConfig以及ReferenceConfig做为中心。

Proxy 服务接口透明代理,生成客户端Stub以及服务端的骨架Skeketon,以ServiceProxy为中心点,扩展点是ProxyFactory

Registry:服务注册层:封装服务地址的注册与发现,

Cluster:封装了我们的提供者的路由以及负载策略,并且我们会通过这个层面去桥接注册中心,以invoker为中心,去进行相应的接口拓展

Monitor:监控层:PPC的调用次数和调用时间,Statistics为中心,去进行接口拓展

Protocol :封装RPC调用,以invocation以及Result做为中心点,去进行拓展接口

Exchange :封装请求响应的模式,同步转异步,Request以及Response为中心

Transport :抽象mina,或者netty3,Netty4。以Message做为中心

Sreialize :可复用的工具。

Dubbo配置文件是如何被加载到Spring当中的?

Spring容器启动的时候,schema机制解析XSD文件,读取到的默认的或者自定义的契约,我们会通过

DubboNamespaceHandler这个类通过一些DubboBeanDefinitionParser去解析我们的配置信息,并且转化成为我们需要加载的Bean对象。

说说核心的配置有哪些

标签用途解释
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/参数配置用于指定方法参数配置

Dubbo多注册中心

dubbo支持对于一个服务注册到多个注册中心,比如在北京的provider 将服务注册到北京的注册中心同时也将服务注册到上海的注册中心,这样上海的consumer可以快速的获取到可用的provider列表。

Dubbo多协议支持

dubbo的服务提供者可以同时提供多种协议调用。这样就不需要再多部署服务,一鱼多吃。

Dubbo超时设置有哪些方式

超时设置可以在服务提供者端设置也可以在服务消费者端设置,但是我们一般会选择设置在服务提供者这边,因为消费者是没有办法感知服务提供者的特性的,所以尽可能优先设置在服务提供者端,如果消费者以及提供者都设置了超时时间,那么这个时候默认优先消费端设置,如果你在消费端设置,也有好处,好处是如果消费方超时,不会影响到提供者,提供者只会报警告不会报异常。

如果我们的Dubbo调用超时了会怎么办?

dubbo在调用服务不成功的时候,默认重试两次。

Dubbo中的扩展点

指定名称的扩展点

ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("name");

1.找到Protocol的全路径名称,并且在META-INF/dubbo/internal下面找到Protocol的全路径名称为文件名字的文件

2.找到getExtension中的参数,并且根据参数找到对应的实现类

自适应拓展点

ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

1.如果@Adaptive注解是在类上,那么我的返回值会是当前类

2.如果@Adaptive注解是在方法上,那么我的返回值会是一个动态生成的代理类

激活拓展点

ExtensionLoader.getExtensionLoader(Protocol.class).getActiveExtension

有激活条件 满足激活条件 我就去加载

Dubbo中用到了哪些设计模式

ProxyFactory就是我们典型的工厂模式

简单工厂:提供一个方法,返回创建好的对象 他在Dubbo源码中的体现:LoggerFactory

工厂方法模式:对创建过程有修改,所以需要抽象一个工厂类,如果增加一个产品,就直接增加一个工厂类

Dubbo在CacheFactory中,会对结果进行缓存,而缓存的策略有非常多种,你会发现,一个策略对应一个缓存工厂

抽象工厂:抽象工厂就是我们可以把不用的结果放到同一个工厂类之中 源码体现:ProxyFactory

策略模式

SPI本身就是典型的策略模式,比如我们的负载均衡策略

装饰器模式:ClassLoadFilter的过滤器 ,在这里对于他的主功能做了增强,所以,是装饰器模式

观察者模式

服务自动注册的时候,或者说服务启动的时候,我们会有监听,我们的ApplicationContextListen

代理模式

我们的整个RPC实际上就是代理对象传递信息的过程

单例模式

private void openServer(URL url) {
        // find server.
        String key = url.getAddress();
        //client can export a service which's only for server to invoke
        boolean isServer = url.getParameter(IS_SERVER_KEY, true);
        if (isServer) {
            ProtocolServer server = serverMap.get(key);
            if (server == null) {
                synchronized (this) {
                    server = serverMap.get(key);
                    if (server == null) {
                        serverMap.put(key, createServer(url));
                    }
                }
            } else {
                // server supports reset, use together with override
                server.reset(url);
            }
        }
    }

适配器模式

Dubbo可以支撑多个日志框架,但是我们每个日志框架都需要有对应的Adapter类

所以我们需要用适配器模式去对应Dubbo本身以及实现类的日志级别。

责任链模式

我们会有判断回升测试的判断,ProtocolFilterWrapper的buildInvokerChain用到了责任链模式

Dubbo服务之间的调用是阻塞的吗?

默认是同步等待结果的阻塞的,但是我们可以去设置成异步

Dubbo是基于NIO非阻塞实现的并行调用,客户端不用多线程启动就可以完成并行调用远程服务,相对于原本的多线程,我们的线程开销较小。

Dubbo实现异步调用

Dubbo 提供了两种方式来实现异步调用:Future 异步和 Callback 异步。

  1. Future 异步:通过配置 async 参数为 true 来启用 Future 异步调用。这允许您发送异步请求并获得一个 ResponseFuture 对象,以便在未来获取调用结果。

例如,通过在 Dubbo 服务引用中设置 async 参数:

<dubbo:reference id="exampleService" interface="com.example.ExampleService" async="true" />

然后,您可以调用服务方法,并获取 ResponseFuture 对象来异步获取结果:

ResponseFuture future = exampleService.sayHello("Dubbo");
// 后续操作...

  1. Callback 异步:通过配置 async 参数为 true 以及指定 onreturnonthrow 回调方法,您可以使用 Callback 异步调用。这允许您在请求完成后执行相应的回调方法。

例如,通过在 Dubbo 服务引用中设置 async 参数和回调方法:

<dubbo:reference id="exampleService" interface="com.example.ExampleService" async="true" onreturn="onReturnMethod" onthrow="onThrowMethod" />

然后,您可以调用服务方法,并定义 onreturnonthrow 方法:

public void onReturnMethod(String result) {
    // 处理正常返回结果
}

public void onThrowMethod(Throwable ex) {
    // 处理异常情况
}

exampleService.sayHello("Dubbo");

在什么情况下使用异步调用

  1. 异步调用:当您需要在发出服务调用后立即继续执行其他任务而不阻塞当前线程时,可以使用异步调用。这可以提高系统的并发性和响应速度。
  2. 减少等待时间:在某些情况下,服务调用可能需要较长的时间来完成,如果在等待服务调用返回结果的同时执行其他任务,可以减少等待时间,提高系统的吞吐量。
  3. 并行调用:如果您需要同时调用多个服务,可以使用异步调用来并行执行这些调用,从而提高效率。
  4. 处理超时和错误情况:通过异步调用,您可以设置超时时间,如果服务调用超时或出现错误,可以及时处理异常情况,而不会阻塞整个应用程序。

具体来说:

  • Future 异步 通常用于需要获取服务调用结果的情况。通过 ResponseFuture 对象,您可以随时在未来获取服务调用的返回值,这对于需要等待结果后才能继续执行的任务非常有用。
  • Callback 异步 通常用于需要在服务调用完成后执行回调方法的情况。您可以指定 onreturn 回调方法来处理正常结果,以及 onthrow 回调方法来处理异常情况。这对于处理异步调用的结果非常有用,可以根据结果执行相应的处理逻辑。

Dubbo的管理控制台能够做什么?

管理控制台:路由规则,动态配置,服务降级,访问控制,权重调整,负载均衡策略等相关的管理功能

Dubbo-ADMIN打成war包,发布运行即可

当一个服务接口有多种实现的时候,我们应该怎么做?

分组问题:当一个接口有多种实现的时候,我们可以用group属性进行分组,可以让服务的提供方,以及服务的消费者都指定同一个Group。

服务上线的时候怎么兼容旧版本

可以用版本号(version)进行过渡,多个不同的版本的服务注册到注册中心,版本号不相同,那么则不会互相引用。使用蓝绿部署的方式进行版本升级。

出现调用超时com.alibaba.dubbo.remoting.TimeoutException异常怎么办?

一般情况下都是你的业务处理太慢,那么这个时候你可以首先去jstack.log分析线程是卡在哪个方法的调用上。

如果不能将业务处理好的话,那么这个时候请调整超时时间timeout。

出现java.util.concurrent.RejectedExecutionException或者Thread pool exhausted怎么办?

代表你的线程池已经到达了最大值,并且没有空闲的连接。拒绝执行你的任务。

//建议你出现了这样的错误,将最大的线程池连接数以及最小的设置成一样

dubbo.service.min.thread.pool.size=200  
dubbo.service.max.thread.pool.size=200

这个参数是Dubbo.properties中的。

Dubbo的安全性如何得到保障

在有注册中心的情况下,我们是可以通过Dubbo的路由规则,指定固定的IP的消费方来访问

在直连的情况下,那么这个时候我可以通过服务提供者的令牌(token),我们的消费方也需要在消费的时候输入这个token,才能够直连成功。Dubbo还可以提供服务白名单,防止接口被刷。

服务发布

服务的暴露起始于 Spring IOC 容器刷新完毕之后,会根据配置参数组装成 URL, 然后根据 URL 的参数来进行本地或者远程调用。

会通过 proxyFactory.getInvoker,利用 javassist 来进行动态代理,封装真的实现类,然后再通过 URL 参数选择对应的协议来进行 protocol.export,默认是 Dubbo 协议。

服务注册

饿汉式,懒汉式

根据配置的参数组成URL 构建一个注册中心文件夹 RegistryDirectory

协议层invoker Cluster failOverCluster 最终返回代理类

问你的点 留余地

服务获取

获取代理类 invoker Map ID

Dubbo异步

SPI

Dubbo 使用 SPI(Service Provider Interface)机制的地方非常多,主要用于扩展点的加载和自定义。以下是一些 Dubbo 中使用 SPI 的主要配置和扩展点:

  1. Protocol SPI:Dubbo 中的协议实现,例如 Dubbo 协议、HTTP 协议等,都是通过 SPI 机制加载的。可以通过配置文件或 Java 代码来指定使用的协议。
  2. Cluster SPI:Dubbo 提供了多种集群容错机制,如 Failover、Failfast、Failsafe 等,这些机制也是通过 SPI 进行加载和选择的。
  3. LoadBalance SPI:Dubbo 支持多种负载均衡策略,如 Random、RoundRobin、LeastActive 等,这些策略也是通过 SPI 加载的。
  4. ExtensionLoader:Dubbo 的 ExtensionLoader 类用于加载和管理各种扩展点的实现,包括上述的 Protocol、Cluster、LoadBalance 等。ExtensionLoader 自身也是使用 SPI 加载扩展点。
  5. Filter SPI:Dubbo 支持自定义过滤器来处理请求和响应,例如参数校验、权限验证等。这些过滤器也是通过 SPI 加载的。
  6. Serialization SPI:Dubbo 支持多种序列化和反序列化方式,如 Hessian、Java、JSON 等,这些方式也是通过 SPI 加载的。
  7. Transporter SPI:Dubbo 支持多种网络传输方式,如 Netty、Mina 等,也是通过 SPI 进行加载。
  8. Registry SPI:Dubbo 支持多种注册中心,如 ZooKeeper、Nacos 等,这些注册中心也是通过 SPI 加载的。
  9. Adaptive Extension:Dubbo 还支持自适应扩展点,允许用户在运行时选择扩展点的实现。这也是通过 SPI 机制实现的。

Dubbo中为什么不使用JDK的SPI

  1. 更丰富的扩展能力:Dubbo 的扩展点机制提供了更丰富的扩展能力,允许用户以更灵活的方式来扩展 Dubbo 的功能。Dubbo 的 SPI 机制支持不仅仅是简单的类加载和实例化,还包括参数配置、自动装配、适配器等更多功能。
  2. 更多的元信息:Dubbo 的 SPI 机制允许扩展点提供更多的元信息,如扩展点的名称、版本、条件等。这些元信息对于扩展点的管理和自动化配置非常有用。
  3. 便于集成和管理:Dubbo 的 SPI 机制允许用户将扩展点注册到 Dubbo 的扩展点管理中心,以便进行集中管理和配置。这对于大规模的应用程序和分布式系统来说非常重要。
  4. 自定义加载策略:Dubbo 的 SPI 机制允许用户定义自己的加载策略,以满足特定的需求。这使得 Dubbo 可以支持更多的扩展点,不仅限于 JDK SPI 的机制。

尽管 JDK SPI 提供了一种基本的扩展机制,但它相对简单,不能满足 Dubbo 复杂的扩展点需求。因此,Dubbo 选择了自己实现 SPI 机制,以提供更多功能和更丰富的扩展能力。这也是 Dubbo 成为一个强大的分布式服务框架的原因之一。

Dubbo为什么默认使用javassist

  1. 性能优化:Java动态代理基于Java的反射机制,虽然灵活,但在性能上存在一定的开销。对于高性能和低延迟要求的分布式系统,这种开销可能会显著影响性能。JavaAssist等字节码生成库允许Dubbo在运行时生成高效的字节码,避免了反射的开销,提高了性能。
  2. 支持更多语言:使用字节码生成库,Dubbo不限于Java语言。Dubbo支持多种编程语言的客户端,如Java、Python、JavaScript等。这些客户端可以使用相同的Dubbo服务,因为服务的契约不依赖于特定的编程语言。
  3. 更灵活的代理方式:使用字节码生成库,Dubbo可以生成自定义的代理类,以满足特定需求。这使得Dubbo能够支持更多的代理方式,如动态代理、字节码增强、CGLIB代理等。Dubbo可以根据需要选择最合适的代理方式。
  4. 支持更多协议和序列化方式:Dubbo支持多种协议和序列化方式,而不仅限于HTTP和Java序列化。字节码生成库允许Dubbo在不同协议和序列化方式下更自由地生成和解析二进制数据,以实现跨语言的通信。

ASM比javassist更快,为什么不用

ASM(字节码操作框架)和 JavaAssist(字节码生成库)都是用于处理Java字节码的工具,它们各自具有一些优势和不同的使用场景。ASM 相对于 JavaAssist 确实更接近字节码层面,因此在性能方面通常更快。但 Dubbo 选择使用 JavaAssist 而不是 ASM 有一些原因:

  1. 易用性:JavaAssist 提供了更高级别的 API,使得生成和修改字节码更加容易。它的 API 更加直观,可以更方便地创建和操作类。相比之下,ASM 的 API 更底层,使用起来更复杂。
  2. 可读性:JavaAssist 生成的字节码通常更容易阅读和调试,因为它更接近于源代码。这对于开发者来说是一个重要的优点。
  3. 跨平台性:JavaAssist 在不同的 Java 虚拟机上表现稳定,并且更容易实现跨平台兼容性。ASM 在某些情况下可能需要更多的调整来适应不同的 JVM 版本。
  4. 生态系统:Dubbo 的开发人员可能更熟悉 JavaAssist,并且 Dubbo 已经使用了它很长时间。这使得维护和开发更加容易。

dubbo如何实现灰度发布

  1. 服务版本控制

    • 在Dubbo中,您可以为服务提供者配置版本信息。服务提供者可以提供多个不同版本的服务,然后消费者可以根据需要选择特定版本的服务。
    • 通过配置服务提供者的版本信息,可以实现灰度发布。例如,您可以在新版本的服务提供者上配置一个新的版本号,然后在消费者端逐步将请求路由到新版本的服务。
  2. 权重调整

    • Dubbo支持服务提供者的权重配置。您可以为不同版本的服务提供者分配不同的权重。逐渐增加新版本服务提供者的权重,减少旧版本服务提供者的权重,以实现渐进式的流量迁移。
  3. 分组策略

    • Dubbo还支持服务提供者的分组配置。您可以将不同版本的服务提供者分组,并在消费者端配置分组策略。通过逐步切换分组策略,您可以将请求路由到新版本的服务提供者。
  4. 路由规则

    • 使用Spring Cloud Gateway或Nginx等反向代理服务器,您可以配置特定的路由规则,以将一部分请求路由到新版本的服务提供者。这是一种更灵活的方式,可以根据请求的特定条件来决定灰度发布。
  5. 条件过滤器

    • 在Dubbo消费者端,您可以使用条件过滤器来选择特定的服务提供者。您可以根据请求中的条件,例如请求头、IP地址等,来选择要调用的服务提供者。
  6. 实时监控和回滚机制

    • 实施灰度发布时,确保实时监控新版本的服务,以便及时发现问题并进行回滚操作,以保证系统的可用性和稳定性。

dubbo如何实现的优雅停机

  1. 配置关闭等待时间:在服务提供者的 Dubbo 配置中,可以配置关闭等待时间(dubbo.protocol.shutdown.wait)。这个配置项定义了服务提供者在关闭之前等待正在处理的请求的时间。
  2. 关闭通知:当服务提供者决定要停止时,可以发送一个关闭通知,以通知 Dubbo 框架停止接受新请求,并开始优雅停机过程。
  3. 等待处理中的请求:服务提供者在关闭通知后等待配置的关闭等待时间。这段时间内,服务提供者会等待正在处理的请求完成。
  4. 停止接受新请求:服务提供者停止接受新的请求。这意味着不再会接受新的请求,但会继续处理现有的请求。
  5. 关闭服务:在关闭等待时间结束后,服务提供者可以关闭其 Dubbo 服务,终止正在处理的请求。

以下是一个简化的示例配置:

<dubbo:protocol name="dubbo" port="20880" shutdown="graceful" 
                shutdown.wait="10000" />

Dubbo如何实现同机房优先调度

服务提供者配置:

# application.properties
spring.application.name=your-provider
server.port=8080

# Dubbo配置
dubbo.application.name=your-provider
dubbo.registry.address=nacos://localhost:8848
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
dubbo.metadata=dc1

在这里,我们将数据中心信息设置为dc1

消费者配置: 配置 Dubbo 消费者并指定同机房优先的负载均衡策略。

# application.properties
spring.application.name=your-consumer
server.port=8081

# Dubbo配置
dubbo.application.name=your-consumer
dubbo.registry.address=nacos://localhost:8848
dubbo.protocol.name=dubbo
dubbo.protocol.port=20881

# 使用同机房优先的负载均衡策略
dubbo.consumer.loadbalance=yourLoadBalance
dubbo.metadata=dc1

在这里,我们将数据中心信息也设置为dc1,以匹配提供者的数据中心信息。

继承RandomLoadBalance 实现同机房优先的负载均衡策略

import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance;

import java.util.List;

public class YourLoadBalance extends RandomLoadBalance {

    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        String consumerDataCenter = url.getParameter("datacenter");

        for (Invoker<T> invoker : invokers) {
            URL providerUrl = invoker.getUrl();
            String providerDataCenter = providerUrl.getParameter("datacenter");

            if (consumerDataCenter != null && consumerDataCenter.equals(providerDataCenter)) {
                return invoker;
            }
        }

        return super.doSelect(invokers, url, invocation);
    }
}

dubbo如何实现限流

  1. 基于 Dubbo 参数限流:您可以在 Dubbo 服务接口的方法上配置 tps 参数,以限制每秒的请求次数。例如:

    javaCopy code
    @Service(version = "1.0", parameters = {"tps", "100"})
    public class MyService implements MyServiceInterface {
        // ...
    }
    

    在上述示例中,tps 参数限制了每秒的请求次数为 100。这个方式适合在服务提供者端实现限流。

  2. 使用 Sentinel:Dubbo 可以与 Sentinel 集成,Sentinel 提供了更强大的限流和熔断功能。您可以通过配置 Sentinel 规则来实现限流控制,包括 QPS 限流、并发限流等。Dubbo 和 Sentinel 的结合使用可以更灵活地实现限流策略。

  • 消费端实现限流

创建一个 Dubbo 服务消费者:

@RestController
public class DubboConsumerController {
    @Reference
    private DubboProviderService dubboProviderService;

    @GetMapping("/sayHello")
    public String sayHello(@RequestParam String name) {
        return dubboProviderService.sayHello(name);
    }
}

使用 Sentinel 配置限流规则。您可以在 Sentinel 控制台上配置限流规则,或通过代码配置规则。以下是一个代码配置规则的示例:

@Configuration
public class SentinelConfiguration {
    @PostConstruct
    public void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("sayHello");  // 资源名,与 Dubbo 服务方法名一致
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(10);  // 限流阈值,每秒最多允许 10 次请求
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}
  • 服务端实现限流
@Service
public class MyDubboProviderServiceImpl implements MyDubboProviderService {

    
    public String myServiceMethod(String param) {
        // Your service logic here
        return "Hello, " + param;
    }

  
}

sentinel 代码配置限流:

import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.Rule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;

import java.util.ArrayList;
import java.util.List;

public class SentinelFlowControlConfig {

    public static void configureFlowRules() {
        // 创建流控规则列表
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("myServiceMethod"); // 资源名,可以是方法名或其他标识
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 限流规则类型,这里是 QPS
        rule.setCount(10); // QPS 限制为 10
        rules.add(rule);

        // 加载规则
        FlowRuleManager.loadRules(rules);
    }

    public static void myServiceMethod() {
        // 定义资源名,与流控规则中的资源名一致
        String resourceName = "myServiceMethod";
        try {
            // 通过 Sentinel 保护的逻辑
            SphU.entry(resourceName, EntryType.IN);
            // Your service logic here
        } catch (BlockException ex) {
            // 被限流时的处理逻辑
            // 可以返回一个 fallback 响应或进行其他操作
        } finally {
            SphU.exit(resourceName);
        }
    }
}

当然这些也可以通过sentinel客户端来进行配置,不过代码里面就直接是使用 @SentinelResource(value = "myServiceMethod", blockHandler = "handleBlock")注解 在抛出去的接口实现上面

消费者重写重试策略(比如限流导致的请求失败可以再次重试)

首先,创建一个自定义的重试策略类,继承 Dubbo 提供的 RetryPolicy

import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance;
import org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker;

public class CustomRetryPolicy extends RoundRobinLoadBalance implements RetryPolicy {

    @Override
    public boolean needRetry(Invocation invoker, Invoker<?> invoker, RpcException e) {
        if (e.getCode() == RpcException.LIMIT_EXCEEDED_EXCEPTION) {
            // 如果是因为限流导致的请求失败,进行重试
            return true;
        } else {
            // 对于其他报错不进行重试
            return false;
        }
    }
}

在上述代码中,我们扩展了 RoundRobinLoadBalance,并实现了 RetryPolicy 接口的 needRetry 方法来控制重试的逻辑。如果 RpcException 的错误码是 RpcException.LIMIT_EXCEEDED_EXCEPTION(代表限流导致的请求失败),则返回 true 进行重试;否则返回 false

接下来,将这个自定义的重试策略配置到 Dubbo 消费者的配置中:

# application.properties

# Dubbo 配置
dubbo.application.name=your-consumer
dubbo.registry.address=nacos://localhost:8848
dubbo.protocol.name=dubbo
dubbo.protocol.port=20881
dubbo.consumer.retry=yourCustomRetryPolicy

在上述配置中,我们将自定义的重试策略 yourCustomRetryPolicy 配置到了 Dubbo 消费者中。

最后,在服务消费者中,您可以使用 @Reference 注解引用服务,并进行方法调用。如果提供者因限流导致请求失败,将会根据自定义的重试策略进行重试。

@Service
public class HelloConsumer {

    @Reference
    private HelloService helloService;

    public String callProvider() {
        return helloService.sayHello("World");
    }
}

消费端配置多个重试策略

首先,在 Dubbo 的消费者端配置文件中,您可以为每个服务引用配置不同的重试策略:

# application.properties

# Dubbo 配置
dubbo.application.name=your-consumer
dubbo.registry.address=nacos://localhost:8848
dubbo.protocol.name=dubbo
dubbo.protocol.port=20881

# 第一个服务引用的重试策略
dubbo.consumer.helloService.retry=yourFirstRetryPolicy

# 第二个服务引用的重试策略
dubbo.consumer.anotherService.retry=yourSecondRetryPolicy

在上述配置中,我们为两个不同的服务引用(helloServiceanotherService)配置了不同的重试策略,分别是 yourFirstRetryPolicyyourSecondRetryPolicy

然后,您需要在代码中为每个服务引用指定要使用的重试策略。在服务消费者类中,使用 @Reference 注解时,可以为每个服务引用指定重试策略:

@Service
public class HelloConsumer {

    @Reference(retries = 2, cluster = "failfast", mock = "true", loadbalance = "roundrobin", timeout = 3000, group = "group1", version = "1.0.0", async = true, check = false, sticky = true, proxy = "jdk", validation = "true", invoker = "invoker", parameters = "key=value", application = "consumer", module = "module", monitor = "monitor", cache = "lru", actives = 3, callbacks = 3, onconnect = "onconnect", ondisconnect = "ondisconnect", owner = "owner", retries = 3, onreturn = "onreturn", onthrow = "onthrow", cache = "cache", filter = {"filter1", "filter2"}, listener = {"listener1", "listener2"}, loader = "loader", extension = "extension", append = true, deprecated = true, interfaceClass = HelloService.class, stub = "true", reconnect = "reconnect", forking = "forking", agent = "agent",mock = "true", connection = "connection", layer = "layer", register = "register", proxy = "proxy", invoker = "invoker", stubevent = "stubevent", local = "local", connections = 1, callbacks = 1, scope = "scope", onconnect = "onconnect", ondisconnect = "ondisconnect", owner = "owner", application = "consumer", module = "module", monitor = "monitor", interfaceName = "com.example.HelloService", params = {"key1=value1", "key2=value2"}, retries = 2, loadbalance = "roundrobin", actives = 3, group = "group1", version = "1.0.0", async = true, validation = "true", mock = "true", connection = "connection", onthrow = "onthrow", owner = "owner", timeout = 2000)
    private HelloService helloService;

    @Reference(retries = 3, cluster = "failfast", mock = "true", loadbalance = "roundrobin", timeout = 3000, group = "group2", version = "2.0.0", async = true, check = false, sticky = true, proxy = "jdk", validation = "true", invoker = "invoker", parameters = "key=value", application = "consumer", module = "module", monitor = "monitor", cache = "lru", actives = 3, callbacks = 3, onconnect = "onconnect", ondisconnect = "ondisconnect", owner = "owner", retries = 3, onreturn = "onreturn", onthrow = "onthrow", cache = "cache", filter = {"filter1", "filter2"}, listener = {"listener1", "listener2"}, loader = "loader", extension = "extension", append = true, deprecated = true, interfaceClass = AnotherService.class, stub = "true", reconnect = "reconnect", forking = "forking", agent = "agent",mock = "true", connection = "connection", layer = "layer", register = "register", proxy = "proxy", invoker = "invoker", stubevent = "stubevent", local = "local", connections = 1, callbacks = 1, scope = "scope", onconnect = "onconnect", ondisconnect = "ondisconnect", owner = "owner", application = "consumer", module = "module", monitor = "monitor", interfaceName = "com.example.AnotherService", params = {"key1=value1", "key2=value2"}, retries = 2, loadbalance = "roundrobin", actives = 3, group = "group2", version = "2.0.0", async = true, validation = "true")
    private AnotherService anotherService;
}

在上述代码中,我们为两个不同的服务引用(helloServiceanotherService)指定了不同的重试策略,包括重试次数(retries)、集群策略(cluster)、超时时间(timeout)等。