大家好,我是三友~~
在之前写的7000字+22张图探秘Dubbo一次RPC调用的核心流程这篇阅读量非常感人的文章中我留了一个小坑。
图片
在发文之前我也猜到了这篇文章阅读量大概率会很感人,所以压根儿觉得不可能完成。但是结果是,阅读量虽然如预期所料,但是点赞量却着实给了个小惊喜。
图片
既然现在已经满38个了,那么我这就来把这个坑给填一填。讲一讲Dubbo在3.x版本之后为什么使用应用级服务注册以及它背后的实现原理。
还是一个简单的Demo
同样地,为了保证文章的完整性和连贯性,防止你忘记了,我把之前那篇文章的Demo再拿过去。
如果你还记得,可直接跳过本节,直接进入下一节
在Dubbo中RPC调用过程中主要分为以下两个角色:
- 服务提供者:提供一个接口给消费者远程调用
- 服务消费者:调用生产者提供的接口
一个简单的Dubbo示例工程就如下所示:
Demo中Dubbo使用的是3.0.7的版本,Nacos使用的是2.3.2版本,代码地址 https://github.com/sanyou3/dubbo-demo.git接口层,提供者消费者都需要依赖,服务提供者实现,服务消费者调用;
图片
服务提供者单独一个工程,实现DemoService接口,通过@DubboService表明提供DemoService这个服务;
图片
服务提供者配置文件;
图片
服务消费者单独一个工程,这里使用单元测试,通过@DubboReference注解表明消费DemoService这个服务接口;
图片
服务消费者配置文件;
图片
启动服务提供者,运行消费者单元测试,结果如下:
成功实现远程服务调用
应用级服务注册和接口级服务注册
1、应用级服务注册
谈到应用级服务注册,其实我们都很了解
就比如说在SpringCloud环境下,服务实例在启动的时候会将自身的服务名、IP、端口外加一些其它的数据注册到注册中心,但是在这一过程中并不会将服务的接口信息注册到注册中心。所以对于服务调用者(消费者)来说,它也只能从注册中心获取到服务名、IP和端口这些信息,无法获取到服务提供者提供了哪些接口,这就是应用级服务注册,如下图所示
图片
所以应用级服务注册用一句话概括就是:
一个服务不管对外提供了多少接口,它都是作为一个整体注册到服务注册中心
2、接口级服务注册
接口级服务注册就跟应用级服务注册相反了。接口级服务注册就是把每个单独的接口看成一个服务进行注册。所以服务在启动的时候,每个接口都将作为单独的服务注册到注册中心。也就是说,有几个接口,就有几个服务,就注册几次。Dubbo在2.x版本的时候就是使用的接口级服务注册。所以在前面的Demo中,你可以在注册中心中看到如下接口级服务注册的服务信息。
图片
Dubbo3.x兼容2.x接口级服务注册,所以也能看到
当然也包括接口的详细信息;
图片
消费者在订阅的时候也就是订阅所需要消费的接口对应的服务信息。所以接口级服务注册用一句话概括就是。
以接口为单位进行服务注册和发现,每个服务的每个接口都单独注册和发现
为什么Dubbo在3.x版本要使用应用级服务注册
之所以Dubbo在3.x版本之后放弃使用接口级服务注册,转而使用应用级服务注册,主要包括以下两点原因:
第一点就是接口级服务注册会增加注册中心的压力。
压力主要来自三个部分:
- 服务注册的压力
- 服务变更通知的压力
- 服务数据存储的压力
对于应用级服务来说,一个服务实例只需要注册1次。但是对于接口级服务注册来说,有多少接口就得注册多少次,如果有100个,那么就得注册100次。这就大大提高了注册中心服务注册的压力。至于服务变更通知的压力,这也很好理解。我们都知道,服务注册中心一般都有一个服务数据变更通知的功能。当有服务实例注册时,注册中心会去通知订阅了这个服务的其它服务,告诉其它服务所订阅的服务实例数据有变更。
图片
对于应用级服务来说,一个服务实例上线只需要给另一个订阅了该服务的服务实例推送1次就可以了。但是对于接口级服务注册来说,如果服务提供者有100个接口,服务消费者订阅了这100个接口服务。那么一个服务实例上线,注册中心需要给另一个服务消费者推送100次服务变更的消息。这就造成了注册中心服务变更推送的压力。至于第三个就更好理解了,注册的服务数据变多了,那么存储的压力就会变大。所以这么一对比就可以清晰的得出一个结论
接口级服务注册的压力远远大于服务级注册的压力
这就是为什么要换成应用级服务注册的第一个原因。至于第二个原因,就听起来这就比较高大上了。主要是为了向SpringCloud和K8S等生态靠齐。因为SpringCloud和K8S它们其实都是应用级的注册和发现。所以为了更好的融入SpringCloud和K8S的生态。Dubbo在3.x就开始转向应用级的服务注册和发现。如果非得换一句逼格高的措辞来表示,那就是对现代微服务架构和云原生技术趋势的适应和支持。
Dubbo3.x应用级服务注册的实现原理
前面铺垫完了,接下来我们就来讲一讲Dubbo3.x应用级服务注册的实现原理。虽然采用了应用级服务注册,但是Dubbo的本质并没有改变。依然还是使用接口来调用。所以对于消费者来说,还是必须得知道接口的详情数据,包括接口所在服务器的IP、端口、通信协议等等。但是现在注册中心只有应用级服务信息,并没有接口级服务信息,怎么获取呢?
Dubbo将整个实现总共拆为两步:
- 消费者需要先获取消费的接口所在的服务名
- 消费者通过获取到服务名再去获取接口详情数据
1、接口是哪个服务提供的?
首先第一步,消费者需要先获取消费的接口所在的服务名,那么问题来了。
消费者如何去获取到消费的接口所在的服务名?
但是当你仔细思考一下时,你其实会发现这并不算是一个问题,因为很简单,服务提供者和服务消费者一般都我们自己开发的服务,所以我们肯定知道接口在哪个服务上,就像OpenFeign一样,我们每次使用时都会自己指明接口所在的服务。
图片
所以Dubbo也给我们提供了3种配置方式,让我们可以手动指定接口所在的服务。
第一种,使用@DubboReference#providedBy属性配置。
图片
第二种,通过消费者配置文件配置接口所在的服务名。
图片
第三种,在消费者配置文件注册中心的配置中加上接口所在的服务名
图片
当你加了这些配置的时候,Dubbo就认为消费的接口就在这些服务中,虽然通过配置可以指定,但是我不知道你有没有发现,前面演示的Demo中我并没有进行任何配置,也能调用成功。所以除了这种配置的方式之外,Dubbo还提供了第二种方式,叫做服务名自动探测。服务提供者在启动的时候,将接口全限定名以及当前服务名的映射关系存到一个中间的地方,而消费者只需要根据消费的接口到这个中间的地方就可以查到接口所在的服务名,这个中间的地方在Dubbo中被称为元数据中心。
图片
这里你肯定有一个疑问?
元数据中心又是什么?
其实元数据中心仅仅是一个概念上的东西,只要可以存数据,都可以被称为元数据中心。
Dubbo默认支持三种组件作为元数据中心:
- Redis
- Nacos
- Zookeeper
当你使用Nacos或者Zookeeper作为注册中心时,Dubbo会默认使用它们作为元数据中心(当然也可以禁用)。并且Nacos使用的是它配置中心的功能。所以在前面的Demo中你就可以在Nacos配置中心模块中看到下面这条接口和服务名的映射数据。
图片
Group是mapping,也就是映射的意思。这就是为什么Demo中没有配置服务提供者也可以调用成功的原因。到这我们来总结一下消费者知道所消费的接口在哪个服务上的两种方式:
手动配置,有三种不同的方式
- @DubboReference#providedBy属性配置。
- 通过消费者配置文件配置接口所在的服务名。
- 在消费者配置文件注册中心的配置中加上接口所在的服务名。
自动探测
服务提供者启动时将自身所提供的接口和服务名的映射关系存到元数据中心。服务消费者在启动的时候,会去从元数据中心查到自己所消费的接口属于哪个服务。自动探测方式需要引入元数据中心,使用Nacos或者Zookeeper作为注册中心时,Dubbo默认会使用它们作为元数据中心。如果项目中没有使用元数据中心,那么只能使用第一种手动配置的方式。
2、服务接口详情数据如何获取?
通过上一节的方式我们可以成功知道消费者所消费的接口在哪个服务上。但是仅仅知道接口在哪个服务上还是无法调用。因为必须得知道接口使用IP、端口、通信协议等。所以消费者此时就会进行第二步,获取接口详情数据。Dubbo也提供了两种获取方式。第一种,从服务提供者本地缓存中获取,这种方式也是默认的。对于接口服务提供者来说,它会将接口详情数据存到本地缓存。所以消费者可以从服务本地缓存中获取,入下图所示:
图片
但是有一个问题,怎么获取呢?Dubbo做的就很巧妙了。
首先服务提供者在启动的时候,会去启动并暴露一个接口是MetadataServiceRPC接口服务。
图片
之后在注册服务实例的时候,会将暴露出去的MetadataService这个RPC接口的协议和端口一起存到注册中心,如下图所示:
图片
默认使用的端口就是20880,通信协议是Dubbo协议。由于经过第一步之后,消费者已经知道接口在哪个服务上了。
所以就可以从注册中心中获取这个服务对应的服务实例信息。也就能知道MetadataService这个RPC接口所在的服务器IP、端口、通信协议。之后消费者就可以通过这些信息,构建RPC请求,从服务提供者获取到接口的详细数据。
整个过程如下图所示:
图片
还有一点,之所以说默认是从本地缓存中获取,是因为在服务实例信息中还存在这么一条信息。
图片
dubbo.metadata.storage-type=local
消费者在获取接口详情数据时,会先判断dubbo.metadata.storage-type这个属性值是多少。如果是local,那么就按照前面说的从服务提供者本地缓存中获取。当然这个配置还可以在服务提供者的配置文件中按照如下方式进行修改。
图片
如果改成remote,那么应该从哪获取呢?这就对应第二种情况了,我们接着往下看。
第二种情况也会用到元数据中心。
服务提供者在启动的时候,会将接口的详情数据全部存到元数据中心。对于消费者来说,只需要从元数据中心就可以获取到接口的详情数据了。
图片
所以在前面的Demo中你就可以在Nacos配置中心模块中看到下面这条包含所有接口详情数据的配置。
图片
小总结
到这就讲完了Dubbo3.x应用级服务注册的实现原理。
这里我画一张图再从整体总结一下前面提到的整个过程。
图片
首先第一步,需要知道接口在哪个服务上,总共有两种办法:
- 手动配置
- 通过服务接口从元数据中心获取所在的服务名
当仅仅知道接口在哪个服务上还是无法调用,必须知道接口的详情数据。
接口的详情数据可以存在两个地方:
- 服务提供者的本地缓存
- 元数据中心
对于消费者来说,首先得知道应该是从服务提供者的本地缓存还是元数据中心种获取。所以消费者会先根据第一步获取到的服务名从服务注册中心获取服务实例信息。
因为服务提供者注册的时候会携带dubbo.metadata.storage-type属性,告诉消费者应该从哪获取。默认是服务提供者的本地缓存,可通过配置修改。
消费者会根据所配置的属性值通过对应的方式获取到接口的详情数据。之后就可以基于这些接口的详情信息发送接口级别的RPC调用了。