Apache Dubbo 是一个用于接口代理的高性能RPC 框架,提供服务注册和发现功能。下图显示了基础设施。
(图1)
Provider是一个服务提供者,提供Java服务接口的实现,并将其元信息注册到Dubbo注册中心(如流程1.register所示)。 Consumer是服务消费者,获取订阅的Java服务接口的元信息。来自Dubbo注册中心的数据(流程2.subscribe所示)经过框架处理后生成,用于执行远程方法调用(流程4.invoke所示)。注册表属于注册中心。注册元信息集中基础设施(例如Apache Zookeeper或阿里巴巴Nacos)。它为提供商提供注册渠道,为消费者提供订阅渠道。同时,注册中心支持注册元信息变更通知,通知消费者上游提供者节点的变化,如扩容或收缩等。注册元信息以Dubbo URL的形式存储,Monitor作为服务管理平台,为开发和运维人员提供服务查询、路由规则、服务模拟和测试等治理功能。
总结一下,Dubbo注册和发现的对象就是Dubbo服务(Java接口**),其载体是****注册元信息,或者**Dubbo URL(例如dubbo://192.168)。 1.2:20880/com.foo.BarService?version=1.0.0group=default 通常包含必要的信息,例如服务提供商的IP 和端口、Java 接口以及可选的版本和组。服务URL 包含唯一标识服务提供商进程的信息。
Dubbo 服务自省架构设计
为了进一步符合Java开发者的编程习惯,Dubbo使用Java服务接口作为注册对象。面临的主要实际挑战是:
如何解决或减轻注册中心的压力和过载如何支持每个应用程序的服务注册和发现如何简化Dubbo URL元数据。
1、背景
2、术语约定
Dubbo 注册中心是中心化的基础设施,大多数注册中心,如Zookeeper、Nacos、Consul、Eureka 都是作为内存存储实现的。注册中心的内存消耗与Dubbo服务注册数量成正比。由于Dubbo服务接口可以注册N个,所以N太大会增加注册中心的负载。据不完全统计,Dubbo 核心提供者通常会暴露20 到50 个服务接口。招生中心是中心化的基础设施,其稳定性面临严峻考验。尽管微服务架构不断深化,但现实情况是,许多开发人员仍然更喜欢不断向单个提供者添加Dubbo 服务接口,而不是更细粒度的Dubbo 提供者组织。
3、使用场景
为了避免单点故障,主流招生中心提供高可用性解决方案。其中包括Zookeeper使用的Zab协议、Consul使用的Raft协议等一致性协议,解决集群环境下的数据同步问题。不管怎样,如果Dubbo URL数量频繁变化,也会考验网络和CPU负载。注册中心与Zookeeper等客户端长时间连接会增加注册中心的网络负载。
3.1 超大规模 Dubbo 服务治理场景
假设某个Dubbo提供者注册了N个Dubbo服务接口。如果扩容或收缩M个实例(节点),随着N的增加,注册中心将注册或删除至少M * N个Dubbo URL。同时,大多数注册中心实现都支持注册变更通知,例如Zookeeper节点变更通知。如果一个Dubbo Consumer订阅一个Provider的Dubbo服务接口数量为X,则X的值越大,发送的通知越多。事实上,X-1 通知是多余的,对于来自同一提供商的一组服务接口没有任何价值。
如果Dubbo注册实体是Dubbo提供者节点而不是服务URL,则上述情况中注册中心的负担将大大减轻。 (负载仅为之前的1/N或者更少。)但是,Dubbo如何在应用粒度注册是一个新的挑战。
3.2 微服务架构和元原生应用
Dubbo也有应用的概念,但传统的使用场景并不是核心要素,仅用于Dubbo Monitor或Dubbo Admin场景中的标识。随着微服务架构和云原生技术的兴起,按应用注册模型已成为流行趋势,例如Spring Cloud 和Kubernetes 服务注册和发现模型。注册中心管理的对象通常与业务无关,甚至不具有RPC 语义。从术语上来说,微服务架构中的“服务”和云原生中的“应用”属于同一个概念和逻辑名称,其成员都用服务实例(service Instances)来表示,服务实例的数量为1:N。
由于一个服务实例代表一个服务进程,而多个Dubbo服务URL可以属于一个Dubbo Provider进程,因此Dubbo URL与服务实例的数量关系为N : 1。假设Dubbo提供者进程只提供一个Dubbo服务(接口)(即如果N=1)。基于Dubbo传统的注册中心SPI可以实现按应用程序的服务注册和发现,但对于现有的Dubbo应用程序来说,应用程序的微服务化是一项巨大的工作。
3.3 Dubbo 元数据架构的基石
Spring Cloud是VMware(原Pivotal)推出的云原生解决方案,采用Spring作为技术栈,在Java微服务领域具有独特优势,在全球拥有大量用户。 Spring Cloud官方支持三种注册中心实现,包括Eureka、Zookeeper、Consul。 Spring Cloud阿里巴巴扩展了Nacos注册中心的实现。 Zookeeper、Consul、Nacos也都是Apache Dubbo官方支持的,但服务注册和发现机制不同。
如果Dubbo支持Spring Cloud服务注册和发现模型,则Dubbo必须基于Dubbo注册中心SPI来实现。如果不这样做,就会面临根本性变化和兼容性的风险。
4、传统架构
Kubernetes 源于Google 15 年运营和维护生产环境的经验,是一个可移植、可扩展的开源平台,用于管理容器化工作负载和服务。 Kubernetes原生服务发现方式主要包括DNS和API服务器。 DNS服务发现是服务地址的常见解决方案,但对于相对复杂的Dubbo元数据,这种服务发现机制可能无法直接由Dubbo注册中心SPI适配。另一方面,API Server支持相对更方便。毕竟Spring Cloud Kubernetes也是在此基础上实现的,并且已经在生产中经过了测试。这意味着,只要Dubbo支持Spring Cloud服务注册和发现模型,也可以实现基于Kubernetes API Server的支持。
5、现实挑战
Dubbo所谓兼容传统的服务注册和发现模型有两层含义:
基于DubboRegistrySPI,同时支持SpringCloud和Kubernetes服务注册和发现模型,旧版Dubbo服务注册和发现模型和新版Dubbo服务注册和发现模型可以互相发现。
5.1 如何解决或缓解注册中心压力过载
Dubbo从2.7.0开始增加了简化URL元数据的能力,“简化”的数据存储在元数据中心。这是因为Dubbo传统的服务注册和发现模型并没有减少Dubbo服务URL注册的数量。因此,简化URL并不能显着减轻招生中心的负担。同时,Dubbo URL元数据简化模式也有一定的局限性。这意味着所有Dubbo Provider节点必须是无状态的,并且每个节点必须具有一致的URL元信息。在实际中,这个要求是很难保证的,尤其是在同样的情况下。提供者节点有不同的版本或配置。综上所述,Dubbo URL元数据需要进一步精简,或者至少避免给注册中心带来压力。
5.1.1 注册中心内存压力
从架构上来说,Dubbo Service Introspection不仅需要解决上述挑战,而且由于现实场景更加复杂,我们也会一步步讲解架构细节。整体架构由以下子架构组成:
服务注册和发现架构元数据服务架构事件驱动架构
5.1.2 注册中心网络压力
Dubbo服务自省的主要需求是减轻招生中心的负载。同时,每个应用程序的服务注册和发现模型使我们能够最大限度地减少Dubbo服务的元信息注册数量。通过支持Spring Cloud 和Kubernetes 环境,可以一石二鸟。架构图如下。
(图2)
5.1.3 注册中心通知压力
如图所示,提供者和消费者在注册中心注册的实体是服务实例(service Instances),而不是Dubbo URL。一个服务实例代表一个提供者或消费者的Dubbo应用进程。服务实例属性包括:
服务名称:此名称在您的注册中心内必须是全局唯一的。
注:姓名规则的结构没有限制,但每个招生中心的规则不同。
主机地址(Host/IP):可解析的主机名或TCP IP地址服务端口(Port):应用进程暴露的Dubbo协议端口(例如Dubbo默认端口20880)
注意:如果应用程序进程暴露了多个Dubbo协议端口,例如dubbo和rest,服务端口是随机选择的,架构不会强制检查端口是否可用。
元数据:有关服务实例的附加信息。用于存储Dubbo元信息,以及通信协议头或附件。激活状态(启用):用于标记当前实例是否对外提供服务。
对上述服务实例模型的支持取决于您的注册中心实施。换句话说,并非所有注册表实现都满足服务自省架构的要求。
5.2 如何支持以应用为粒度的服务注册与发现
注册中心除了满足服务实例模型的要求外,还必须具备以下特点:
服务实例变更通知(Notification):如上面第4步所示,当消费者订阅的提供者的服务实例发生变化时,注册中心可以实时通知消费者。心跳检测(Heartbeat):可以检测注册中心。优雅地删除失败的服务实例
符合上述要求的业界主流招生中心有:
Apache ZookeeperHashiCorp ConsulNetflix EurekaAlibaba NacosKubernetes API 服务器
这意味着Spring Cloud和Kubernetes注册中心都满足注册中心对服务自省的要求。然而,在Dubbo传统的RPC使用场景中,提供者和消费者关注的是Dubbo服务接口,而不是服务或服务实例。假设现有的Dubbo应用需要迁移到服务自省架构,对于提供者和消费者来说进行大量的代码调整是不现实的。理想情况下,两端的实现代码都不需要改动,只需要少量的配置改动就可以达到过渡效果。那么Dubbo服务接口是如何映射到服务的呢?
5.2.1 支持 Spring Cloud 服务注册与发现模型
前面提到,单个Dubbo服务可以暴露多个Dubbo服务,所以Dubbo服务和Services之间是N:1的关系。但Dubbo Service与Dubbo Service之间并没有强绑定关系。这意味着一个Dubbo Service也可以部署到多个Dubbo Service。因此,Dubbo Service 与Service 数量的关系是N 到M(N,M)。=1),如下图所示:
(图3)
上图中,服务P1 到P3 是Dubbo 服务,com.acme.Interface1 到com.acme.InterfaceN 是Dubbo 服务接口的完全限定名称(QFN)。需要注意的是,由于Dubbo服务的Java接口允许不同的版本或组,仅靠Java接口无法唯一标识Dubbo服务,必须更新映射关系,如下:
(图4)
Dubbo服务ID的字符表示模式为:${protocol}:${interface}:${version}:${group}。这里,版本或组是可选的。当Dubbo Consumer订阅Dubbo服务时,它会创建一个对应的ID,并使用该ID来查询Dubbo Provider的服务名称列表。
Dubbo服务与Services之间的映射关系取决于业务场景,无法在架构层面进行预测。因此,这个映射关系只有在Dubbo服务发布时(运行时)才能确定。否则,如果Dubbo服务被多个消费者应用订阅,消费者会因为找不到提供者服务名而无法完成服务发现。同时,映射关系数据通常以配置的形式存储。服务自检提供两种配置实现:集中式映射配置和本地化映射配置。
5.2.2 支持 Kubernetes 服务注册与发现模型
显然,招生中心扮演动态映射配置的角色是不合适的。否则,Dubbo服务和映射的关系在注册中心内部就会处于同一级别,这无论是在理解上还是在设计上都令人困惑。结合Dubbo现有基础设施的分析,这个存储设施是由Dubbo配置中心来执行的。
其中,Dubbo 2.7.5 动态配置API(DynamicConfiguration)支持组和键的第二种结构。其中,group中存储了Dubbo服务ID,key与对应的Dubbo服务名称关联。 4”是:
(图5)
这样设计的原因是:
获取销钉服务兼容服务
DynamicConfiguration#getConfigKeys(String group) 方法可以让你轻松检索一个Dubbo服务ID暴露的所有Dubbo服务,可以结合服务发现接口检索该服务部署的服务实例集合,并最终添加到URL 列表。
防止Dubbo服务配置互相覆盖
例如,以Dubbo服务ID dubbo:com.acme.Interface1:default为例。其提供的Dubbo服务有P1和P2服务。假设配置的组为“default”(任意名称均可),key为“dubbo:com.acme.Interface1:default”,内容为Dubbo服务的名称。如果P1和P2服务同时启动,无论哪个服务最终完成Dubbo服务的发布,也无论配置中心是否支持原子操作,两者的配置内容都必须是其中之一。即使配置中心支持添加内容的能力,由于两个服务实例流程的不确定性,配置内容可能会重复,如“P1服务、P2服务、P1服务”。
获取Dubbo服务发布的时间戳
5.2.3 兼容 Dubbo 传统服务注册与发现模型
假设P1服务有5个服务实例。当Dubbo 服务dubbo:com.acme.Interface1:default (ID) 发布时,配置关联的key 为当前的Dubbo 服务名称,即(P1 Service),内容如下:最后发布的Dubbo服务的时间戳。服务实例越多,对配置中心和网络传输的写入压力越大。当然,从架构设计的角度来看,Service Introspection也希望避免在DynamicConfiguration API中添加类似于publishConfigIfAbsent等方法。然而,当前大多数配置中心产品(Nacos、Consul 等)都没有。因此,未来需要进行服务自省。该架构提供了针对性的支持(例如Zookeeper)。
5.3 如何精简 Dubbo URL 元数据
服务自省架构必须依赖于注册中心,动态映射配置也依赖于配置中心,增加了应用架构复杂度和维护成本。不过Apache Dubbo支持的一些注册中心也可以作为配置中心。将按以下方式显示:
基础知识软件注册中心配置中心Apache ZookeeperHashiCorp ConsulAlibaba NacosNetflix EurekaKubernetes API Server
其中Zookeeper、Consul、Nacos是目前业界比较流行的注册中心,这对于大多数选择开源产品的应用来说绝对是一个福音。
6、架构设计
如果开发者认为引入配置中心会增加架构复杂度,静态映射配置可能是一个解决方案。
该功能在最新的Dubbo 2.7.6中并未完全发布,但在Dubbo Spring Cloud中发布了部分功能
6.1 服务注册与发现架构
Dubbo 的传统编程模型经常使用Java 注解@Reference 或XML 元素“来订阅目标Dubbo 服务。基于此,服务自省架构增加了如下的服务属性来映射一个或多个Dubbo服务名称。
1 参考服务=\’P1 服务、P2 服务\’ 接口=\’com.acme.Interface1\’ /
或者
1 2 @Reference(services=\’P1 服务,P2 服务\’) private com.acme.Interface1 接口1;
通过此配置,Dubbo 服务com.acme.Interface1 将订阅p1-service 和p2-service。如果开发人员认为外部化配置方法侵入了代码,服务内省还提供该方法的配置映射。
6.1.1 注册实体
服务自省架构通过声明“Dubbo 服务和服务映射”来支持外部配置。配置格式是一个属性。以图4为例,内容如下。
1 2 3 dubbo\\:com.acme.Interface1\\:default=P1服务,P2服务thirft\\:com.acme.InterfaceX=P1服务,P3服务rest\\:com.acme.interf
aceN = P1 Service
6.1.3.2.3 应用级别映射配置
除此之外,Dubbo Spring Cloud 提供应用级别的 Dubbo 服务映射配置,即 dubbo.cloud.subscribed-services ,例如:
1 2 3 dubbo: cloud: subscribed-services: P1 Service,P3 Service
总之,无论是映射配置的方式是中心化还是本地化,服务 Consumer 依赖这些数据来定位 Dubbo Provider Services,再通过服务发现 API 结合 Service 名称(列表)获取服务实例集合,为合成 Dubbo URL 做准备:
(图 6)
不过,映射关系并非是一种强约束,Dubbo Provider 的服务是否可用的检验方法是探测目标 Dubbo Service 是否存在,并需确认订阅的 Dubbo 服务在目标 Services 是否真实暴露,因此,服务自省引入了 Dubbo 元数据服务架构,来完成 Dubbo 服务 URL 的存储。
6.2 元数据服务架构
Dubbo 元数据服务是一个常规的 Dubbo 服务,为服务订阅端提供 Dubbo 元数据的服务目录,类似于 WebServices 中的 WDSL 或 REST 中的 HATEOAS,帮助 Dubbo Consumer 获取订阅的 Dubbo 服务的 URL 列表。元数据服务架构无法独立于服务注册与发现架构而存在,下面通过“整体架构”的讨论,了解两者之间的关系。
6.2.1 整体架构
架构上,无论 Dubbo Service 属于 Provider 还是 Consumer,甚至是两者的混合,每个 Dubbo (Service)服务实例有且仅有一个 Dubbo 元数据服务。换言之,Dubbo Service 不存在纯粹的 Consumer,即使它不暴露任何业务服务,那么它也可能是 Dubbo 运维平台(如 Dubbo Admin)的 Provider。不过出于行文的习惯,Consumer 仍旧被定义为 Dubbo 服务消费者(应用)。由于每个 Dubbo Service 均发布自身的 Dubbo 元数据服务,那么,架构不会为不同的 Dubbo Service 设计独立的元数据服务接口(Java)。换言之,所有的 Dubbo Service 元数据服务接口是统一的,命名为 MetadataService 。
6.2.1.1 微观架构
从 Dubbo 服务(URL)注册与发现的视角, MetadataService 扮演着传统 Dubbo 注册中心的角色。综合服务注册与发现架构(Dubbo Service 级别),微观架构如下图所示:
(图 7)
对于 Provider(服务提供者)而言,Dubbo 应用服务暴露与传统方式无异,而 MetadataService 的暴露时机必须在它们完成后,同时, MetadataService 需要收集这些 Dubbo 服务的 URL(存储细节将在“元数据服务存储模式“ 小节讨论)。假设某个 Provider 的 Dubbo 应用服务暴露数量为 N,那么,它所有的 Dubbo 服务暴露数量为 N + 1。
对于 Consumer(服务消费者)而言,获 Dubbo 应用服务订阅 URL 列表后,Dubbo 服务调用的方式与传统方式是相同的。不过在此之前,Consumer 需要通过 MetadataService 合成订阅 Dubbo 服务的 URL。该过程之所以称之为“合成”,而非“获取,是因为一次 MetadataService 服务调用仅在其 Provider 中的一台服务实例上执行,而该 Provider 可能部署了 N 个服务实例。具体“合成”的细节需要结合“宏观架构”来说明。
6.2.1.2 宏观架构
元数据服务的宏观架构依赖于服务注册与发现架构,如下图所示:
(图 8)
图 8 中 p 和 c 分别代表 Provider 和 Consumer 的执行动作,后面紧跟的数字表示动作的次序,从 0 开始计数。执行动作是串行的,并属于 Fast-Fail 设计,如果前阶段执行失败,后续动作将不会发生。之所以如此安排是为了确保 MetadataService 能够暴露和消费。首先从 Provider 执行流程开始说明。
6.2.1.2.1 Provider 执行流程
p0:发布所有的 Dubbo 应用服务,声明和定义方式与传统方式完全相同。p1:暴露 MetadataService ,该步骤完全由框架自行处理,无论是否 p0 是否暴露 Dubbo 服务p2:在服务实例注册之前, 框架将触发并处理事件(Event),将 MetadataService 的元数据先同步到服务实例(Service Instance)的元数据。随后,执行服务实例注册p3:建立所有的 Dubbo 应用服务与当前 Dubbo Service 名称的映射,并同步到配置源(抽象)
6.2.1.2.2 Consumer 执行流程
c0:注册当前 Dubbo Service 的服务实例,可选步骤,架构允许 Consumer 不进行服务注册c1:通过订阅 Dubbo 服务元信息查找配置源,获取对应 Dubbo Services 名称(列表)c2:利用已有 Dubbo Service 名称(可能存在多个),通过服务发现 API 获取 Provider 服务实例集合。假设 Service 名称 P,服务实例数量为 Nc3:
随机选择 Provider 一台服务实例 Px,从中获取 MetadataService 的元数据将元数据组装 MetadataService Dubbo 调用客户端(代理)发起 MetadataService Dubbo 调用,获取该服务实例 Px 所暴露的 Dubbo 应用服务 URL 列表从 Dubbo 应用服务 URL 列表过滤出当前订阅 Dubbo 应用服务的 URL理论上,步骤 c 和 d 还需要执行 N-1 次才能获取 P 所有服务实例的 Dubbo URL 列表。为了减少调用次数,步骤 d 的结果作为模板,克隆其他 N-1 台服务实例 URL 列表将所有订阅 Dubbo 应用服务的 URL 同步到 Dubbo 客户端(与传统方式是相同的)
c4:发起 Dubbo 应用服务调用(与传统方式是相同的)
不难看出,上述架构以及流程结合了“服务注册与发现”与“元数据服务”双架构,步骤之间会触发相关 Dubbo 事件,如“服务实例注册前事件”等。换言之,三种架构综合体也就是服务自省架构。
至此,关于 Dubbo 服务自省架构设计方面,还存在一些细节亟待说明,比如:
不同的 Dubbo Service 的 MetadataService 怎样体现差异呢?MetadataService 作为一个常规的 Dubbo 服务,它的注册元信息存放在何处?MetadataService 作为服务目录,它管理的 Dubbo 应用服务 URL 是如何存储的?在 Consumer 执行流程的 c3.e 中,克隆 N – 1 条 URL 的前提是该 Provider 的所有服务实例均部署了相同 Dubbo 应用服务。如果 Provider 处于升级的部署过程,同一 Dubbo 应用服务接口在不同的服务实例上存在差异,那么该服务的 URL 应该如何获取?除了 Dubbo 服务 URL 发现之外,元数据服务还支持哪些元数据类型呢?
6.2.2 元数据服务 Metadata
元数据服务 Metadata,称之为“元数据服务的元数据”,主要包括:
inteface:Dubbo 元数据服务所暴露的接口,即 MetadataServiceserviceName : 当前 MetadataService 所部署的 Dubbo Service 名称,作为 MetadataService 分组信息group:当前 MetadataService 分组,数据使用 serviceNameversion:当前 MetadataService 的版本,版本号通常在接口层面声明,不同的 Dubbo 发行版本 version 可能相同,比如 Dubbo 2.7.5 和 2.7.6 中的 version 均为 1.0.0。理论上,version 版本越高,支持元信息类型更丰富protocol: MetadataService 所暴露协议,为了确保 Provider 和 Consumer 通讯兼容性,默认协议为:“dubbo”,也可以支持其他协议。port:协议所使用的网络端口host:当前 MetadataService 所在的服务实例主机或 IPparams:当前 MetadataService 暴露后 URL 中的参数信息
不难得出,凭借以上元数据服务的 Metadata,可将元数据服务的 Dubbo 服务 ID 确定,辅助 Provider 服务暴露和 Consumer 服务订阅 MetadataService 。不过对于 Provider,这些元信息都是已知的,而对 Consumer 而言,它们直接能获取的元信息仅有:
serviceName:通过“Dubbo 接口与 Service 映射”关系,可得到 Provider Service 名称interface:即 MetadataService ,因为 Provider 和 Consumer 公用 MetadataService 接口group:即 serviceName
不过 Consumer 合成 MetadataService Dubbo URL 还需获取 version、host、port、protocol 以及 params:
version:尽管 MetadataService 接口是统一接口,然而 Provider 和 Consumer 可能引入的 Dubbo 版本不同,从而它们使用的 MetadataService version 也会不同,所以这个信息需要 Provider 在暴露MetadataService 时,同步到服务实例的 Metadata 中,方便 Consumer 从 Metadata 中获取host:由于 Consumer 已得到 serviceName,可通过服务发现 API 获取服务实例对象,该对象包含 host 属性,直接被 Consumer 获取即可。port:与 version 类似,从 Provider 服务实例中的 Metadata 中获取params:同上
通过元数据服务 Metadata 的描述,解释了不同 Dubbo Services 是怎样体现差异性的,并且说明了 MetadataService 元信息的存储介质,这也就是服务自省架构为什么强依赖支持 Metadata 的注册中心的原因。下个小节将讨论 MetadataService 所存储 Dubbo 应用服务 URL 存放在何处。
6.2.3 元数据服务存储模式
Dubbo 2.7.5 在引入 MetadataService 的同时,也为其设计了两种存储方式,适用于不同的场景,即“本地存储模式”和“远程存储模式”。其中,本地存储模式是默认选项。
6.2.3.2元数据服务本地存储模式
本地存储模式又称之为内存存储模式(In-Memory),如 Dubbo 应用服务发现和注册场景中,暴露和订阅的 URL 直接存储在内存中。架构上,本地存储模式的 MetadataService 相当于去中心化的 Dubbo 应用服务的注册中心。
6.2.3.1元数据服务远程存储模式
远程存储模式,与去中心化的本地存储模式相反,采用 Dubbo 元数据中心来管理 Dubbo 元信息,又称之为元中心化存储模式(Metadata Center)。
6.2.3.1选择存储模式
为了减少负载压力和维护成本,服务自省中的元数据服务推荐使用**“本地存储模式”**。
回顾前文“Consumer 执行流程”中的步骤 c3.e,为了减少 MetadataService 调用次数,服务自省将第一次的调用结果作为模板,再结合其他 N-1 服务实例的元信息,合成完整的 N 台服务实例的 Dubbo 元信息。假设,Dubbo Service 服务实例中部署的 Dubbo 服务数量和内容不同,那么,c3.e 的执行步骤是存在问题的。因此,服务自省引入“Dubbo 服务修订版本”的机制来解决不对等部署的问题。
尽管“Dubbo 服务修订版本”机制能够介绍 MetadataService 整体消费次数,然而当新修订版本的服务实例过少,并且 Consumer 过多时,如新的版本 Provider 应用分批部署,每批的服务实例为 1 台,而其 Consumer 服务实例成千上万。为了确保这类场景的稳定性,Provider 和 Consumer 的 MetadataService 可选择“远程存储模式”,避免消费热点的发生。
6.2.4 Dubbo 服务修订版本
当业务出现变化时,Dubbo Service 的 Dubbo 服务也会随之升级。通常,Provider 先行升级,Consumer 随后跟进。
考虑以下场景,Provider “P1” 线上已发布 interface 为 com.acme.Interface1,group 为 group , version 为 v1 ,即 Dubbo 服务 ID 为:dubbo:com.acme.Interface1:v1:default 。P1 可能出现升级的情况有:
Dubbo 服务 interface 升级
由于 Dubbo 基于 Java 接口来暴露服务,同时 Java 接口通常在 Dubbo 微服务中又是唯一的。如果 interface 的全类名调整的话,那么,相当于 com.acme.Interface1 做下线处理,Consumer 将无法消费到该 Dubbo 服务,这种情况不予考虑。如果是 Provider 新增服务接口的话,那么 com.acme.Interface1 则并没有变化,也无需考虑。所以,有且仅有一种情况考虑,即“Dubbo interface 方法声明升级”,包括:
增加服务方法删除服务方法修改方法签名
Dubbo 服务 group、version 和 protocol 升级
假设 P1 在升级过程中,新的服务实例部署仅存在调整 group 后的 Dubbo 服务,如 dubbo:com.acme.Interface1:v1:test ,那么这种升级就是不兼容升级,在新老交替过程中,Consumer 仅能消费到老版本的 Dubbo 服务。当新版本完全部署完成后,Consumer 将无法正常服务调用。如果,新版本中 P1 同时部署了 dubbo:com.acme.Interface1:v1:default
和 dubbo:com.acme.Interface1:v1:test 的话,相当于 group 并无变化。同理,version 和 protocol 变化,相当于 Dubbo 服务 ID 变化,这类情况无需处理。
Dubbo 服务元数据升级
这是一种比较特殊的升级方法,即 Provider 所有服务实例 Dubbo 服务 ID 相同,然而 Dubbo 服务的参数在不同版本服务实例存在差异,假设 Dubbo Service P1 部署 5 台服务,其中 3 台服务实例设置 timeout 为 1000 ms,其余 2 台 timeout 为 3000 ms。换言之,P1 拥有两个版本(状态)的 MetadataService 。
综上所述,无论是 Dubbo interface 方法声明升级,还是 Dubbo 服务元数据升级,均可认为是 Dubbo 服务升级的因子,这些因子所计算出来的数值称之为“Dubbo 服务修订版本”,服务自省架构将其命名为“revision”。架构设设计上,当 Dubbo Service 增加或删除服务方法、修改方法签名以及调整 Dubbo 服务元数据,revision 也会随之变化,revision 数据将存放在其 Dubbo 服务实例的 metadata 中。当 Consumer 订阅 Provider Dubbo 服务元信息时,MetadataService 远程调用的次数取决于服务实例列表中出现 revision 的个数,整体执行流程如下图所示:
(图 9)
Consumer 通过服务发现 API 向注册中心获取 Provider 服务实例列表注册中心返回 6 台服务实例,其中 revision 为 1 的服务实例为 Instance 1 到 3, revision 为 2 的服务实例是 Instance 4 和 Instance 5,revision 为 3 的服务实例仅有 Instance 6Consumer 在这 6 台服务实例中随机选择一台,如图中 Instance 3Consumer 向 Instance 3 发起 MetadataService 的远程调用,获得 Dubbo URL 列表,并建立 revision 为 1 的 URL 列表缓存,用 cache = { 1:urls(r1) } 表示(重复步骤 4)Consumer 再从剩余的 5 台服务实例中随机选择一台,如图中的 Instance 5,由于 Instance 5 与 Instance 3 的 revision 分为为 2 和 1,此时缓存 cache = { 1:urls(r1) } 未命中,所以 Consumer 将再次发起远程调用,获取新的 Dubbo URL 列表,并更新缓存,即 cache = { 1:urls(r1) , 2:urls(r2) }(重复步骤 4)Consumer 再从剩余的 4 台服务实例中随机选择一台,假设服务实例是 Instance 6,由于此时 revision 为3,所以缓存 cache = { 1:urls(r1) , 2:urls(r2) } 再次未命中,再次发起远程调用,并更新缓存 cache = { 1:urls(r1) , 2:urls(r2) , 3:urls(r3) }(重复步骤 4)由于缓存 cache = { 1:urls(r1) , 2:urls(r2) , 3:urls(r3) } 已覆盖三个 revision 场景,如果该步骤选择服务实例落在 revision 为 1 的子集中,只需克隆 urls(r1),并根据具体服务实例替换部分 host 和 port 等少量元信息即可,组成成新的 Dubbo URL 列表,依次类推,计算直到剩余服务实例为 0。
大多数情况,revision 的数量不会超过 2,换言之,Consumer 发起 MetadataService 的远程调用不会超过 2次。无论 revision 数量的大小,架构能够保证获取 Dubbo 元信息的正确性。
当然 MetadataService 并非仅支持 Dubbo URL 元数据,还有其他类型的支持。
6.2.5 元数据类型
架构上,元数据服务(MetadataService)未来将逐步替代 Dubbo 2.7.0 元数据中心,并随着 Dubbo 版本的更迭,所支持的元数据类型也将有所变化,比如 Dubbo 2.7.5 元数据服务支持的类型包括:
Dubbo 暴露的服务 URL 列表Dubbo 订阅的服务 URL 列表Dubbo 服务定义
6.2.5.1 Dubbo 暴露的服务 URL 列表
当前 Dubbo Service 暴露或发布 Dubbo 服务 URL 集合,如:[ dubbo://192.168.1.2:20880/com.acme.Interface1?group=default&version=v1 , thirft://192.168.1.2:20881/com.acme.InterfaceX , rest://192.168.1.2:20882/com.acme.interfaceN ]
6.2.5.2 Dubbo 订阅的服务 URL 列表
当前 Dubbo Service 所有订阅的 Dubbo 服务 URL 集合,该元数据主要被 Dubbo 运维平台来收集。
6.2.5.3 Dubbo 服务定义
Dubbo 服务提供方(Provider)在服务暴露的过程中,将元信息以 JSON 的格式同步到注册中心,包括服务配置的全部参数,以及服务的方法信息(方法名,入参出参的格式)。在服务自省引入之前,该元数据被 Dubbo 2.7.0 元数据中心 存储,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 { \”parameters\”: { \”side\”: \”provider\”, \”methods\”: \”sayHello\”, \”dubbo\”: \”2.0.2\”, \”threads\”: \”100\”, \”interface\”: \”org.apache.dubbo.samples.metadatareport.configcenter.api.AnnotationService\”, \”threadpool\”: \”fixed\”, \”version\”: \”1.1.1\”, \”generic\”: \”false\”, \”revision\”: \”1.1.1\”, \”valid\”: \”true\”, \”application\”: \”metadatareport-configcenter-provider\”, \”default.timeout\”: \”5000\”, \”group\”: \”d-test\”, \”anyhost\”: \”true\” }, \”canonicalName\”: \”org.apache.dubbo.samples.metadatareport.configcenter.api.AnnotationService\”, \”codeSource\”: \”file:/../dubbo-samples/dubbo-samples-metadata-report/dubbo-samples-metadata-report-configcenter/target/classes/\”, \”methods\”: [{ \”name\”: \”sayHello\”, \”parameterTypes\”: [\”java.lang.String\”], \”returnType\”: \”java.lang.String\” }], \”types\”: [{ \”type\”: \”java.lang.String\”, \”properties\”: { \”value\”: { \”type\”: \”char[]\” }, \”hash\”: { \”type\”: \”int\” } } }, { \”type\”: \”int\” }, { \”type\”: \”char\” }] }
6.2.5.4 更多元数据类型支持
在架构上,元数据服务(MetadataService)所支持元数据类型是不限制的,如下图所示:
(图 10)
除上文曾讨论的三种元数据类型,还包括“Dubbo 服务 REST 元信息” 和 “其他元信息”。其中,Dubbo 服务 REST 元信息包含 Dubbo 服务 与 REST 映射信息,可用于 Dubbo 服务网关,而其他元信息可能包括 Dubbo 服务 JavaDoc 元信息,可用于 Dubbo API 文档。
6.2.6 元数据服务升级
考虑到 Dubbo Provider 和 Consumer 可能依赖不同发行版本的 MetadataService ,因此,Provider 提供的和 Consumer 所需要的元数据类型并不对等,如 Provider 使用 Dubbo 版本为 2.7.5,该发行版本仅支持“Dubbo 暴露的服务 URL 列表”,“Dubbo 订阅的服务 URL 列表”和“Dubbo 服务定义”,这三种元数据分别来源于接口的三个方法。当 Consumer 使用了更高的 Dubbo 版本,并需要获取“Dubbo 服务 REST 元信息”时,自然无法从 Provider 端获取。假设 MetadataService 为其新增一个方法,那么,当 Consumer 发起调用时,那么这个调用自然会失败。即使两端使用的版本相同,那么 Provider 仍有可能选择性支持特定的元数据类型。为了确保元数据接口的兼容性,MetadataService 应具备元数据类型支持的判断。如此设计,MetadataService 在元数据类型上支持更具有弹性。
6.3 事件驱动架构
相较于传统的 Dubbo 架构,服务自省架构的执行流程更为复杂,执行动作之间的关联非常紧密,如 Dubbo Service 服务实例注册前需要完成 Dubbo 服务 revision 的计算,并将其添加至服务实例的 metadata 中。又如当 Dubbo Service 服务实例出现变化时,Consumer 元数据需要重新计算。这些动作被 “事件”(Event)驱动,驱动者被定义为“事件分发器”( EventDispatcher ),而动作的处理则由“事件监听器”(EventListener)执行,三者均为 “Dubbo 事件“的核心组件,同样由 Dubbo 2.7.5 引入。不过,Dubbo 事件是相对独立的架构,不过被服务自省中的“服务注册与发现架构”和“元数据服务架构”依赖。
6.3.1 Dubbo 内建事件
Dubbo 内建事件可归纳为以下类型:
Dubbo 服务类型事件Dubbo Service 类型事件Dubbo 服务实例类型事件Dubbo 服务注册和发现类型事件
6.3.1.1 Dubbo 服务类型事件
事件类型事件触发时机ServiceConfigExportedEvent当 Dubbo 服务暴露完成时ServiceConfigUnexportedEvent当 Dubbo 服务下线后ReferenceConfigInitializedEvent当 Dubbo 服务引用初始化后ReferenceConfigDestroyedEvent当 Dubbo 服务引用销毁后
6.3.1.2 Dubbo Service 类型事件
事件类型事件触发时机DubboShutdownHookRegisteredEvent当 Dubbo ShutdownHook 注册后DubboShutdownHookUnregisteredEvent当 Dubbo ShutdownHook 注销后DubboServiceDestroyedEvent当 Dubbo 进程销毁后
6.3.1.3 Dubbo 服务实例类型事件
事件类型事件触发时机ServiceInstancePreRegisteredEvent当 Dubbo 服务实例注册前ServiceInstanceRegisteredEvent当 Dubbo 服务实例注册后ServiceInstancePreUnregisteredEvent当 Dubbo 服务实例注销前ServiceInstanceUnregisteredEvent当 Dubbo 服务实例注销后ServiceInstancesChangedEvent当 某个 Dubbo Service 下的服务实例列表变更时
6.3.1.4 Dubbo 服务注册和发现类型事件
事件类型事件触发时机ServiceDiscoveryInitializingEvent当 Dubbo 服务注册与发现组件初始化中ServiceDiscoveryInitializedEvent当 Dubbo 服务注册与发现组件初始化后ServiceDiscoveryExceptionEvent当 Dubbo 服务注册与发现组件异常发生时ServiceDiscoveryDestroyingEvent当 Dubbo 服务注册与发现组件销毁中ServiceDiscoveryDestroyedEvent当 Dubbo 服务注册与发现组件销毁后
你的点赞与关注 是 肖哥弹架构持续的动力。
历史热点文章
🔥Serverless 微服务优雅关机实践
🔥这种算法都看不懂!9张图是如何展示出来的
🔥SpringBoot精通-自定义Condition注解(系列一)
🔥Java是如何将美女的衣服脱掉的
🔥高性能网关原来是这样设计出来的
🔥REST FUL 看了还不懂,你弹我!
🔥Serverless 对程序员的影响到底有多大
🔥分布式事务XID是如何将所有微服务串联起来的
🔥微服务分布式事务TCC核心实现篇
🔥亿级流量网站性能优化的方法论步骤
🔥微服务Nacos通过CMDB实现就近访问提升性能
🔥微服务架构DNS服务注册与发现实现原理
…更多
#以上关于Dubbo 服务自省架构设计的相关内容来源网络仅供参考,相关信息请以官方公告为准!
原创文章,作者:CSDN,如若转载,请注明出处:https://www.sudun.com/ask/92525.html