大家好,我是三友~~

今天继续探秘系列,扒一扒一次RPC请求在Dubbo中经历的核心流程。

本文是基于Dubbo3.x版本进行讲解

一个简单的Demo

这里还是老样子,为了保证文章的完整性和连贯性,方便那些没有使用过的小伙伴更加容易接受文章的内容,这里快速讲一讲Dubbo一个简单的Demo

如果你已经使用过了,可直接跳过本节,直接进入下一节

在Dubbo中RPC调用过程中主要分为以下两个角色:

  • 服务提供者:提供一个接口给消费者远程调用
  • 服务消费者:调用生产者提供的接口

于是一个简单的Dubbo示例工程就如下所示:

示例工程的创建步骤、使用配置、第三方的依赖等详细内容可参考官网:https://cn.dubbo.apache.org/zh-cn/overview/quickstart/java/spring-boot/

接口层,提供者消费者都需要依赖,服务提供者实现,服务消费者调用

服务提供者单独一个工程,实现DemoService接口,通过@DubboService表明提供DemoService这个服务

服务消费者单独一个工程,这里使用单元测试,通过@DubboReference注解表明消费DemoService这个服务接口

启动服务提供者,运行消费者单元测试,结果如下:

成功实现远程服务调用

服务提供者暴露

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

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

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

就拿前面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过滤环节

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在设计上的优秀之处。

好了,本文就讲到这里,如果觉得本文对你有所帮助,欢迎点赞、在看、收藏、转发分享给其他需要的人

你的支持就是我更新的最大动力,感谢感谢!

扫码或者搜索关注公众号 三友的java日记 ,及时干货不错过,公众号致力于通过画图加上通俗易懂的语言讲解技术,让技术更加容易学习,回复 面试 即可获得一套面试真题。

让我们下期再见,拜拜!

往期热门文章推荐

如何去阅读源码,我总结了18条心法

如何写出漂亮代码,我总结了45个小技巧

三万字盘点Spring/Boot的那些常用扩展点

三万字盘点Spring 9大核心基础功能

两万字盘点那些被玩烂了的设计模式

万字+20张图探秘Nacos注册中心核心实现原理

万字+20张图剖析Spring启动时12个核心步骤

1.5万字+30张图盘点索引常见的11个知识点

面试官:Dubbo一次RPC请求经历哪些环节?的更多相关文章

  1. 程序员过关斩将--面试官再问你Http请求过程,怼回去!

    菜菜哥,X总在产品部瞎指挥,作为程序媛的我都快撑不住了 不光你撑不住了,大家都要撑不住了,外行人指导内行人,呵呵 前天我偷偷的去面试了,结果挂了 出去转转其实是好事,面试官问你什么了? 他让我描述一个 ...

  2. 面试官问我Redis集群,我真的是

    面试官:聊下Redis的分片集群,先聊 Redis Cluster好咯? 面试官:Redis Cluser是Redis 3.x才有的官方集群方案,这块你了解多少? 候选者:嗯,要不还是从基础讲起呗? ...

  3. (转)史上最全 40 道 Dubbo 面试题及答案,看完碾压面试官!

    背景:因为自己的简历写了dubbo,面试时候经常被问到.实际自己对dubbo的认识只停留在使用阶段,所以有必要好好补充下基础的理论知识. https://zhuanlan.zhihu.com/p/45 ...

  4. 1.说一下的 dubbo 的工作原理?注册中心挂了可以继续通信吗?说说一次 rpc 请求的流程?

    作者:中华石杉 面试题 说一下的 dubbo 的工作原理?注册中心挂了可以继续通信吗?说说一次 rpc 请求的流程? 面试官心理分析 MQ.ES.Redis.Dubbo,上来先问你一些思考性的问题.原 ...

  5. 面试官:Dubbo是什么,他有什么特性?

    哈喽!大家好,我是小奇,一位热爱分享的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 书接上回,今天还是过周末,虽然 ...

  6. 面试官问我,使用Dubbo有没有遇到一些坑?我笑了。

    前言 17年的时候,因为一时冲动没把持住(当然最近也有粉丝叫我再冲动一把再更新一波),结合面试题写了一个系列的Dubbo源码解析.目前公众号大部分粉丝都是之前的粉丝,这里不过多介绍. 根据我的面试经验 ...

  7. 史上最全 40 道 Dubbo 面试题及答案,看完碾压面试官

    想往高处走,怎么能不懂 Dubbo? Dubbo是国内最出名的分布式服务框架,也是 Java 程序员必备的必会的框架之一.Dubbo 更是中高级面试过程中经常会问的技术,无论你是否用过,你都必须熟悉. ...

  8. 面试官问我,使用Dubbo有没有遇到一些坑?我笑了

    17年的时候,因为一时冲动没把持住(当然最近也有粉丝叫我再冲动一把再更新一波),结合面试题写了一个系列的Dubbo源码解析.目前公众号大部分粉丝都是之前的粉丝,这里不过多介绍. 根据我的面试经验而言, ...

  9. 面试官:一个 TCP 连接可以发多少个 HTTP 请求?

      曾经有这么一道面试题:从 URL 在浏览器被被输入到页面展现的过程中发生了什么? 相信大多数准备过的同学都能回答出来,但是如果继续问:收到的 HTML 如果包含几十个图片标签,这些图片是以什么方式 ...

  10. 面试官:Dubbo怎么实现服务降级,他有什么好处?

    哈喽!大家好,我是小奇,一位热爱分享的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 书接上回,今天周一了,招聘软件 ...

随机推荐

  1. 🔥 Java Solon v2.7.6 发布

    Java Solon 是什么框架? Java "新的"应用开发框架.开放原子开源基金会,孵化项目.从零开始构建(非 java-ee 架构),有灵活的接口规范与开放生态. 追求: 更 ...

  2. 有隙可乘 - Android 序列化漏洞分析实战

    作者:vivo 互联网大前端团队 - Ma Lian 本文主要描述了FileProvider,startAnyWhere实现,Parcel不对称漏洞以及这三者结合产生的漏洞利用实战,另外阐述了漏洞利用 ...

  3. Linux中默认的shell如何切换为其他类型的shell

    1.一般linux系统会默认使用一种shell,比如我当前系统使用的默认shell是bash,可以使用如下方法查看. [root@node5 ~]# echo $SHELL /bin/bash 2.当 ...

  4. pgsql安装与主从配置搭建

    一:安装环境 查看一下安装环境:cat /etc/centos-release CentOS Linux release 7.7.1908 (Core) 二:软件下载 https://www.post ...

  5. Maven项目中整合SSH(pom.xml文件的配置详解)

    Maven项目中整合SSH比较繁琐,需要解决版本冲突问题,博主在下面给出了pom.xml文件的配置信息,改配置文件整合的是:struts2-2.3.24.spring4.2.4.hibernate5. ...

  6. 做程序员这么久,你知道UTF-8和Unicode的关系吗?

    UTF-8和Unicode到底有什么区别?是存储方式不同?编码方式不同?它们看起来似乎很相似,但是实际上他们并不是同一个层次的概念. 要想先讲清楚他们的区别,首先应该讲讲Unicode的来由: 众所周 ...

  7. 使用 OWIN Self-Host ASP.NET Web API 自宿主 Swagger Swashbuckle 在线文档

    使用 OWIN Self-Host ASP.NET Web APIhttps://learn.microsoft.com/zh-cn/aspnet/web-api/overview/hosting-a ...

  8. vm ware 安装 ubuntu server linux

    在:https://ubuntu.com/download/server 中下拉找到"Ubuntu Server 20.04 LTS",下载ISO. 在VM WARE 中,如果鼠标 ...

  9. vm ware cent os 共享文件夹

    1.VM中安装vm-tools 2.在VM 虚拟机设置中添加共享文件夹. 3.重启虚拟机 4.在/mnt/ 里新建一个名为"win"的文件夹 5.在cent os 中执行: vmw ...

  10. Selenium模块的使用(二)

    selenium处理iframe - 如果定位的标签存在于iframe标签之中,则必须使用switch_to.frame(id) - 动作链(拖动):from selenium.webdriver i ...