Dubbo调用的核心流程

Apache Dubbo 是一个高性能的 Java RPC 框架,广泛应用于微服务架构中。Dubbo 的 RPC 调用流程涉及多个核心组件和步骤。以下是一次典型的 Dubbo RPC 调用的核心流程分析:

一、核心流程概述

  1. 服务注册与发现
  2. 服务调用
  3. 结果返回

详细步骤

1. 服务注册与发现
  • 服务提供者(Provider)启动时注册服务

    • 服务提供者启动时,将其提供的服务及其元数据(如接口信息、网络地址等)注册到注册中心(如 ZooKeeper)。
    • 注册中心保存这些服务的元数据,供服务消费者查找。
  • 服务消费者(Consumer)订阅服务

    • 服务消费者启动时,向注册中心订阅所需的服务。
    • 注册中心将相关的服务信息推送给消费者。
    • 消费者根据这些信息创建本地缓存,用于后续的服务调用。
2. 服务调用
  • 调用准备

    • 服务消费者在调用远程服务时,通过动态代理生成服务接口的代理对象。这种代理模式在分布式系统中非常常见,主要用于简化远程服务调用,使其看起来像本地方法调用。具体来说,这个代理对象是一个动态生成的对象,实现了远程服务接口,并拦截所有方法调用,将其转换为远程过程调用(RPC)。代理对象的使用简化了远程调用的复杂性,使得开发者能够像调用本地方法一样调用远程服务。
    • 当消费者调用代理对象的方法时,代理对象拦截方法调用,并将其转化为远程调用请求。
  • 构造请求

    • 代理对象将方法调用的信息(如方法名、参数等)封装为一个 RPC 请求。
    • 请求信息包括目标服务的接口名、方法名、参数类型和参数值。
  • 负载均衡

    • 消费者通过负载均衡策略(如随机、轮询等)选择一个合适的服务提供者。
    • 负载均衡器根据缓存的服务提供者列表选择一个合适的提供者进行调用。
  • 请求发送

    • 消费者将 RPC 请求通过网络发送给选定的服务提供者。
    • Dubbo 使用 Netty 作为默认的网络传输框架,以确保高性能的网络通信。
  • 服务提供者接收请求

    • 服务提供者接收到 RPC 请求后,通过反序列化将请求数据还原为方法调用信息。
    • 提供者根据请求信息找到相应的服务实现,并调用相应的方法。
  • 方法执行

    • 服务提供者执行具体的业务逻辑,处理请求并生成响应结果。
3. 结果返回
  • 响应构造与发送

    • 服务提供者将方法执行结果封装为 RPC 响应,并通过网络发送给服务消费者。
    • 响应信息包括返回值或异常信息。
  • 消费者接收响应

    • 消费者接收到 RPC 响应后,通过反序列化将响应数据还原为方法调用的返回值或异常。
    • 代理对象将返回值或异常返回给调用者。

核心组件

  • 注册中心:用于服务注册与发现,常用的有 ZooKeeper、Nacos 等。
  • 服务提供者(Provider):提供具体的服务实现,注册服务到注册中心。
  • 服务消费者(Consumer):调用远程服务,从注册中心获取服务提供者列表。
  • 负载均衡器:在消费者端选择合适的服务提供者进行调用。
  • 网络传输框架:如 Netty,用于高性能的网络通信。
  • 序列化框架:如 Hessian、Protobuf,用于请求和响应的数据序列化与反序列化。

图解

可以通过以下流程图来更加直观地理解 Dubbo 的 RPC 调用流程:

[Consumer] -> [Proxy] -> [Load Balancer] -> [Netty Client] -> [Network] -> [Netty Server] -> [Service Implementation] -> [Netty Server] -> [Network] -> [Netty Client] -> [Proxy] -> [Consumer]

在这个流程中,每个步骤都涉及到 Dubbo 的关键组件和机制,确保了服务调用的高效和可靠。

二、调用中的各个步骤的细节

服务提供者暴露

所谓的服务提供者暴露,主要就是指在项目启动时服务提供者去做的两件事

第一件事就是,由于需要对外提供调用服务,接受消费者的请求

所以在启动时需要根据使用协议,以及协议对应的端口启动一个对应的服务

就拿前面DemoService来举例,由于@DubboService注解没有指定任何信息

所以DemoService默认就是使用Dubbo框架自己写的通信协议,也就是Dubbo协议,这个协议默认使用的端口就是20880

之后如果要调用DemoService的方法时,就可以按照Dubbo协议要求组装数据格式

向20880端口发送请求,从而就实现远程服务调用,如下图所示:

图片

当然除了默认的Dubbo协议之外,Dubbo还支持其它的通信协议,后面会详细介绍

虽然第一件事成功让接口可以对外提供访问,但是对于消费者来说,它其实还是无法访问接口

因为消费者并不知道接口使用的是哪个通信协议、端口,也不知道接口所在的服务器的ip

于是,在启动时就会去做第二件事

第二件事是将每个接口的详细信息,包括接口的全限定名、方法名称、方法参数、服务器的ip、端口、通信协议等等按照一定的格式组装好

存放到元数据中心和服务提供者本地缓存中

注意这是3.x版本时的存储情况,跟2.x有点不同。并且元数据中心其实就是使用的Nacos或者Zookeeper来实现的,所以你可以认为就是存储在Nacos或者Zookeeper中

之后消费者需要调用接口时,就可以从元数据中心或者服务提供者本地缓存中获取到接口的详细信息(具体从哪取决于配置,默认是从本地缓存中获取)

图片

这里你肯定有疑问消费者是如何从服务提供者本地缓存获取,这就涉及到Dubbo3.x应用级服务注册的逻辑了,所以就不详细展开了,不过立个flag,如果本篇文章点赞达到38个,就再来一篇,单独讲一讲Dubbo3.x应用级服务注册的原理。

当需要发起调用时,就可以按照接口使用的协议组装数据,向接口所在的服务器ip和端口发送请求

所以总的来说,服务提供者暴露主要就是这两件事:

  • 根据接口使用协议和端口开启服务,对外提供接口访问

  • 将当前服务支持的接口,以及每个接口使用的协议、端口、服务器ip等信息存到元数据中心或者本地缓存,供消费者获取

消费者引用

前面提到,如果消费者想引用远程服务,可以通过@DubboReference注解触发引用的逻辑

消费者引用也会去做两件事

第一件事我们都知道,那就是创建接口的动态代理

由于消费者使用的DubboService是一个接口,所以会给DubboService创建一个动态代理

这个动态代理最终也会发送请求RPC请求

Dubbo支持两种动态搭理生成方式:

  • JDK动态搭理

  • Javassist动态生成字节码

默认使用的Javassist动态生成字节码的方式

除了创建动态搭理之外,还会去获取服务提供者的接口详细信息

上面一节说了,可以从元数据中心或者是服务提供者本地缓存中获取到

当获取到接口详情数据之后,会为之后的RPC调用做一些准备工作

比如如果接口使用的是Dubbo通信协议的话,准备工作就是消费者会跟服务提供者机器建立长连接

好了,到这里我们就把服务者暴露和消费者引用都讲完了

接下来就会进入本文的主题,一次RPC调用,也就是调用动态代理之后在Dubbo中会经历哪些环节

参数封装

熟悉JDK动态代理的同学肯定知道,当调用动态代理方法时,最终会走到InvocationHandler的实现

图片

在Dubbo中,调用消费者动态代理的时候,不论是JDK动态代理还是使用Javassist方式生成动态代理

最终都会走到InvokerInvocationHandler这个InvocationHandler的实现

图片

所以这个整个RPC调用的起点就是invoke方法的实现

图片

如图所示,首先将RPC调用的接口、方法名、参数封装到RpcInvocation中

接着会走到下面这行代码

invoker.invoke(rpcInvocation)

而这看似简简单单一行代码就会触发RPC调用的整个核心流程

ClusterFilter过滤

当参数封装完成之后,接下来就会走到ClusterFilter过滤环节。在 Dubbo 中,ClusterFilter 是用于处理集群相关逻辑的过滤器。它在客户端发起 RPC 调用时,对请求进行处理,包括负载均衡、故障转移等集群管理的功能。ClusterFilter 通过对 Invoker 进行拦截和处理,确保请求能够通过合理的策略进行路由和处理,从而提高系统的容错性和稳定性。

图片

ClusterFilter本质是一种责任链模式,是Dubbo提供的一个重要扩展点

通过实现invoke方法对请求进行自定义预处理操作

Dubbo默认提供了几种实现

图片

比如就拿MonitorClusterFilter来说

这个实现主要是去统计每个接口的每个方法调用成功多少次,调用失败多少次等等调用的信息

除了默认实现之外,很多我们熟悉的一些框架也是通过这个扩展点跟Dubbo进行整合的

就比如常见的流控框架Sentinel

图片

集群调用逻辑决策

当走完ClusterFilter之后,接下来就会来到集群调用逻辑决策的环节

这个集群调用逻辑决策是什么意思呢?

在实际生产环境中,一般服务都会以集群的方式来部署

这就会产生一个问题,面对多服务情况下,怎么去调用?

图片

举个例子,按图上所示,有三个服务

那么集群调用逻辑就是去决定

应该每个服务都去调用一次,还是只去调用其中一个?

如果只调用其中一个,比如调用服务1,如果失败了,那么此时是直接抛异常还是选择继续去调用服务2,还是做其它的事?

所以集群调用逻辑就是解决多服务实例下,应该怎样合理地调用服务实例

Dubbo提供了以下几种集群调用逻辑:

  • 广播,也就是每个服务都调用(broadcast)

  • 调用前会去判断服务是不是可用,如果可用,那么就直接进行调用(available)

  • 调用失败,会开启定时任务进行重试调用,最大重试3次(failback)

  • 调用失败就直接抛出异常(failfast)

  • 调用失败直接调用其它服务进行重试,故障转移(failover)

  • 调用失败不会抛异常,而是直接返回(failsafe)

  • 同时调用指定个数的服务,直接最快返回结果当做这个调用的结果(forking)

  • 调用每个服务,合并服务返回的数据作为调用的结果,结果怎么合并需要我们自定义相关逻辑(mergeable)

在默认情况下使用的就是failover,也就是出现异常时会调用其它的服务再返回结果

当然我们也可以按照如下的方式指定调用策略

图片

路由策略

上一节是解决集群中众多实例时应该如何调用的问题

而路由策略其实是选择允许调用哪些服务实例

因为并不是所有的服务实例都符合调用要求

什么意思呢?

举个例子,现在有个灰度发布的场景

假设所有的服务都处于同一套环境中,有一群机器运行者之前正式版本的服务,有一群机器运行着灰度版本的服务,如下图所示

图片

那么对于处于灰度的消费者肯定要调用处于灰度的服务提供者

但是由于在同一套环境,那么处于灰度的消费者其实是能获取到处于之前正式环境的服务接口信息

如果就这么直接调用,那么处于灰度的消费者就可能调用非灰度的服务提供者

这肯定是不允许的

所以必须在调用前过滤掉非灰度发布的服务

而这种情况就可以交给路由来过滤

假设如果想做到灰度区分,可以使用Dubbo提供了一种叫tag的路由策略

灰度的服务提供者可以指定自己的tag属性为gray(灰色的意思),如下所示

图片

而对于处于灰度的消费者,只需要指定消费tag为gray的服务提供者,如下所示

图片

这样在真正调用前就会通过tag路由的方式过滤出处于灰度的服务提供者

所以集群调用逻辑所能使用的服务实例只能是经过路由策略选择出来

图片

除了tag路由策略之外,Dubbo还提供了以下几种路由策略

  • 条件路由,可以指定某些条件下可以调用哪些服务实例

  • 脚本路由,可以写一段JavaScript脚本,更加灵活地选择哪些服务实例

顺带说一句,这个路由功能可以用来实现一个更高大上的功能,叫做流量管控

负载均衡

所谓的负载均衡就是说,面对多个服务实例,我们应该按照何种算法选择一个供我们调用

Dubbo提供了以下几种负载均衡策略:

  • 随机(random),随机选择一个

  • 轮询(roundrobin),每次调用按照顺序选择一个

  • 最少活跃优先(leastactive),优先选择被最少调用的服务

  • 最短响应优先(shortestresponse),优先选择响应时间断的服务调用

  • 一致性Hash(consistenthash)

在没有指定的情况下,默认使用的就是随机(random)算法

如果想进行修改,可以按照如下方式:

图片

这里你肯定有疑问

这个负载均衡和集群调用策略有什么关系?感觉这两者有点像,又感觉这两者有点冲突。

其实集群调用策略的优先级会大于负载均衡

比如说如果集群调用策略选择默认,也就是故障转移(failover)

那么对于路由策略过滤出来的服务实例,会根据负载均衡算法选择一个进行调用

但是如果集群调用策略选择的是广播调用(broadcast)

那么对于路由策略过滤出来的服务实例,实际上每个都需要去调用

所以此时压根不需要走负载均衡策略,因为没有意义,即使你配置了,也不会生效

所以需不需要负载均衡这件事,取决于使用什么集群调用策略

总的来说,集群调用策略、路由策略、负载均衡策略它们一步一步去决定本次RPC调用具体应该调用哪个或者哪些服务实例

三者关系入下图所示:

图片

Filter过滤

经过上面的几步,终于知道本地RPC请求需要请求哪个或者哪些具体的服务实例

接下来只需要向对应的服务实例发送请求就可以了

不过在发送请求前,Dubbo还预留了一个扩展点,叫做Filter

本质也是一种责任链模式

图片

通过Filter,我们可以在RPC调用前对整个请求再进行自定义扩展

这里你肯定又会有一个疑问

Filter和前面提到的ClusterFilter有什么区别?

的确它两真的很像,甚至都继承同一个接口BaseFilter,但是它两还有一些区别

第一点,两者作用时机不同

通过讲解顺序我们可以看出,ClusterFilter作用在路由和负载均衡前,而Filter在路由和负载均衡后

所以只要我们愿意,我们可以通过ClusterFilter去影响后面的路由和负载均衡,而Filter是做不到的

第二点就是Filter是跟服务实例走的

在调用每个服务实例之前,Filter一定会都会重新调用一遍

比如假设这次RPC最终需要选择调用两个服务实例,那么Filter会走两遍

但是对于ClusterFilter,在整个调用过程中它仅仅只会执行一次

所以官方也是建议,在无特殊情况下,优先选择使用ClusterFilter而不是Filter

到这,画一张图总结一下前面整个调用环节用

图片

通信协议

当Filter责任链走完之后,接下来就到了向服务实例发送请求的时候了

一旦涉及到服务与服务之间的调用,那么就离不开通信协议

所谓的通信协议,讲的简单点就是发送方把需要发送的数据按照一定的格式组装好之后再发送给接收方

Dubbo需要发送数据包括调用但不限于接口全限定名、调用的方法名、调用参数等等

而接收方在获取到数据时再使用对应的格式去解析,从而获取到请求数据

前面提到,Dubbo默认使用的通信协议是Dubbo自己的写的,叫做Dubbo协议

除了Dubbo协议之外,Dubbo还支持以下几种通信协议:

  • Rest

  • gRPC

  • Triple

  • ...

Rest,就是我们说的Http协议

当使用这种协议的时候,Dubbo在启动的时候会去创建一个Http的服务

默认使用的是Jetty,当然也支持切换成Tomcat

gRPC,谷歌开源的高性能RPC框架

当然使用gRPC的时候,服务提供者会启动一个gRPC的服务端

这里你可能有疑问,Dubbo是RPC框架,gRPC也是RPC框架,为什么要集成gRPC

其实这是因为Dubbo和gRPC定位不同

Dubbo其实不仅仅是一个RPC框架,它其实是一套微服务解决方案,会承担更多的服务治理相关的逻辑

而gRPC的定位是通信协议与实现,是一款纯粹的RPC框架

Triple协议就比较厉害了,它是Dubbo在3.x时发布的通信协议

Triple完全兼容gRPC协议,可同时运行在HTTP/1和HTTP/2传输协议之上,让你可以直接使用curl、浏览器访问后端Dubbo服务

如果要想使用上面的这些协议,代码可能需要进行一些改动,这里就不演示了

序列化协议

上一节提到,数据在发送的时候需要根据通信协议按照要求去组装数据

但是我们都知道,数据在网络中传输使用的是二进制

所以在实际开发中,要想发送数据,一般都是先将需要传输的数据转换成字节序列(数组),之后再交由操作系统转换成二进制进行传输

于是就有了一个问题,比如我们想传输一个对象的数据,那么我们应该按照什么样的格式将对象的数据转换成字节序列呢?

而这个按照什么样的格式就被称为序列化协议

整个转换过程就被称为序列化,也可以被称为编码

图片

既然有序列化,那么就有反序列化

所谓反序列化就是根据序列化协议将字节序列转换成数据,也被称为解码

当通信协议使用Dubbo协议时,Dubbo支持以下几种序列化协议:

  • Java原生

  • Hessian2

  • Fastjson2

  • ...

Dubbo在3.2.0版本之前默认使用的Hessian2协议,3.2.0之后默认使用Fastjson2作为序列化协议

到这里其实就算讲完了消费者整个调用的过程了

因为当序列化完成之后,接下来就只需要将字节序列通过网络发送出去即可

服务提供者处理请求

当服务提供者监听到有请求时,会获取到请求的字节序列

然后根据通信协议,序列化协议反序列化出传输的数据

从而获取到消费者需要调用的、接口、方法以及入参等数据

之后就可以找到调用接口对应的实现,通过反射进行调用,获取结果

然后再将结果序列化成字节数组,返回给消费者

这样服务提供者就处理完成了一次请求

不过这里面有一个小细节,那就是在调用接口的实现之前,也会经过Filter过滤

所以Filter过滤其实在提供者和消费者两者都有

图片

但是需要注意的是,两边的Filter不一定相同,具体取决于这个Filter是作用在消费者端还是提供者端,可通过如下方式配置

图片

总结

到这终于讲完了一次RPC请求在Dubbo中经历整个核心流程

不知道你看完有什么感受

这里我再来画一张图总结整个调用过程

图片

值得注意是,上面提到的所有调用环节,注意说的是所有,Dubbo都留了对应的扩展点。

也就是说,小到一个Filter,大到整个通信协议你都可以进行自定义扩展。

从这也可以看出,Dubbo在设计上的优秀之处 。

  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值