以下是精彩视频内容整理:
一、服务自省架构背景
第一个议题就是关于服务自省架构的背景,它离不开我们的技术的一个引进,其中包括三个方面。第一个是Cloud-Native的技术的兴起,所谓的云原生的技术兴起,这个技术相信大家已经耳熟能详。
第二方面是关于Java社区里面的一个龙头老大,Spring Cloud的快速崛起。它其实是目前很多中小型企业首选的一个微服务框架,或者说云原生的开发框架。它能够快速的帮我们整合一些相关的服务,并且用统一的API的方式来抹去我们的一些厂商约定的技术。
第三个方面是Dubbo所面临的挑战。第一,如何解决或缓解注册中心压力过载。包括内存压力,网络压力和通知压力。第二,如何支持以应用为粒度的服务注册与发现。包括支持Spring Cloud服务注册与发现模型,支持Kubernetes服务注册与发现模型和兼容Dubbo传统服务注册与发现模型。第三,如何精简Dubbo URL元数据,如下图所示。
二、服务自省架构定义
(一)定义
我们再来看下个大的议题,就是关于服务自省这个架构我们如何来定义。实际上关于自省有很多种讲法,通常我们说自省是自我反省,严格意义上讲,它不应该叫自我反省,应该叫透视的一个功能,相当于x光一样的给你来做一个全面的审视。Dubbo应用在运行时处理和分析Dubbo服务元信息的过程,如当前应用暴露的Dubbo服务以及各自的通讯协议等。期间会伴随着事件的广播和处理,如服务暴露事件。
(二)术语约定
-Service:SOA 或微服务中的“服务”,或称之为“应用”,具有全局唯一的名称
-Service Name: 服务名称,或应用名称
-Servce Instance:服务实例,或称为应用实例(Application Instance),表示单个 Dubbo 应用进程
-Registry:注册中心
-Dubbo 服务:又称之为“Dubbo 业务服务”,包含 Java 接口、通讯协议,版本(version)和分组(group)等元信息
-Dubbo 服务 ID:唯一鉴定 Dubbo 服务的元数据,用于 Dubbo 服务暴露(发布)和订阅
-Provider:Dubbo 服务提供方
-Consumer:Dubbo 服务消费方
-Dubbo 服务暴露:也称之为 Dubbo 服务发布,或英文中的“export”、”exported”
-Dubbo 应用服务:也称之为 Dubbo 业务服务,或业务 Dubbo 服务
三、服务自省架构使用场景
(一)超大规模 Dubbo 服务治理场景
如果 Dubbo 集群规模超过一千以上,或者集群扩缩容已无法自如地执行,如 Zookeeper 管理数万 Dubbo 服务,服务自省可极大化减轻注册中心的压力,尤其在内存足迹、网络传输以及变更通知上体现。
(二)微服务架构和云原生应用
如果想要 Dubbo 应用更好地微服务化,或者更接近于云原生应用,那么服务自省是一种不错的选择,它能够提供已应用为粒度的服务注册与发现模型,全面地支持最流行的 Spring Cloud 和 Kubernetes 注册中心,并且能与 Spring Cloud 或 Spring Boot 应用交互。
(三)Dubbo 元数据架构的基石
Dubbo 元数据架构是围绕 Dubbo DevOps 而引入,包括 Dubbo 配置元数据(如:属性配置、路由规则等)和结构元数据(如:Java 注解、接口和文档等)。服务自省作为 Dubbo 元数据的基础设施,不仅支持所有元数据的存储和平滑升级,而且不会对注册中心、配置中心和元数据中心产生额外的负担。
四、服务自省架构设计
传统架构
Apache Dubbo 是一款面向接口代理的高性能 RPC 框架,提供服务注册与发现的特性,其基础架构如下图所示:
(图 1)
架构上,Dubbo 服务自省不仅要解决上述挑战,而且实际场景则更为复杂,因此,架构细节也将循序渐进地展开讨论,整体架构可由以下子架构组成:
-服务注册与发现架构
-元数据服务架构
-事件驱动架构
服务注册与发现架构
Dubbo 服务自省首要需求是减轻注册中心的承载的压力,同时,以应用为粒度的服务注册与发现模型不但能够最大化的减少 Dubbo 服务元信息注册数量,而且还能支持 Spring Cloud 和 Kubernetes 环境,可谓是一举两得,架构图如下所示:
(图 2)
注册实体
图中所示,从 Provider 和 Consumer 向注册中心注册的实体不再是 Dubbo URL,而是服务实例(Service Instance),一个服务实例代表一个 Provider 或 Consumer Dubbo 应用进程。服务实例属性包括:
-服务名(Service Name):该名称必须在注册中心全局唯一
注:名称规则架构上不做约束,不过不同注册中心的规则存在差异
-主机地址(Host/IP):能够被解析的主机名或者 TCP IP 地址
-服务端口(Port):应用进程所暴露的 Dubbo 协议端口,如 Dubbo 默认端口 20880
注:如果应用进程暴露多个 Dubbo 协议端口,如 dubbo 和 rest,那么,服务端口随机挑选其一,架构上不强制检验端口是否可用
-元数据(Metadata):服务实例的附加信息,用于存储 Dubbo 元信息,类似于通讯协议头或附件
-激活状态(Enabled):用于标记当前实例是否对外提供服务
上述服务实例模型的支持依赖于注册中心的实现。换言之,并非所有注册中心实现满足服务自省架构的要求。
注册中心
除了满足服务实例模型的要求之外,注册中心还得具备以下能力:
-服务实例变化通知(Notification):如上图步骤 4 所示,当 Consumer 订阅的 Provider 的服务实例发生变化时,注册中心能够实时地通知 Consumer
-心跳检测(Heartbeats):注册中心能够检测失效的服务实例,并且合理地移除它们
业界主流的注册中心中满足上述要求的有:
-Apache Zookeeper
-HashiCorp Consul
-Netflix Eureka
-Alibaba Nacos
-Kubernetes API Server
总之,Spring Cloud 与 Kubernetes 注册中心均符合服务自省对注册中心的要求。不过,在 Dubbo 传统 RPC 使用场景中,Provider 和 Consumer 关注的是 Dubbo 服务接口,而非 Service 或服务实例。假设需要将现有的 Dubbo 应用迁移至服务自省架构,Provider 和 Consumer 做大量的代码调整是不现实的。理想的情况下,两端实现代码均无变化,仅修改少量配置,就能达到迁移的效果。那么,Dubbo 服务接口是如何与 Service 进行映射的呢?
Dubbo 服务与 Service 映射
前文曾讨论,单个 Dubbo Service 能够发布多个 Dubbo 服务,所以,Dubbo 服务与 Service 的数量关系是 N 对 1。不过,Dubbo 服务与 Dubbo Service 之间并不存在强绑定关系,换言之,某个 Dubbo 服务也能部署在多个 Dubbo Services 中,因此,Dubbo 服务与 Service 数量关系是 N 对 M(N, M >= 1),如下图所示:
(图 3)
上图中 P1 Service 到 P3 Service 为 Dubbo Service,com.acme.Interface1 到 com.acme.InterfaceN 则为 Dubbo 服务接口全称限定名(QFN)。值得注意的是,Dubbo 服务的 Java 接口(interface)允许不同的版本(version)或分组(group),所以仅凭 Java 接口无法唯一标识某个 Dubbo 服务,还需要增加通讯协议(protocol)方可,映射关系更新如下:
(图 4)
Dubbo 服务 ID 字符表达模式为: ${protocol}:${interface}:${version}:${group} , 其中,版本(version)或分组(group)是可选的。当 Dubbo Consumer 订阅 Dubbo 服务时,构建对应 ID,通过这个 ID 来查询 Dubbo Provider 的 Service 名称列表。
由于 Dubbo 服务与 Service 的映射关系取决于业务场景,架构层面无从预判。因此,这种映射关系只能在 Dubbo 服务暴露时(运行时)才能确定,否则,Dubbo 服务能被多个 Consumer 应用订阅时,Consumer 无法定位 Provider Service 名称,进而无法完成服务发现。同时,映射关系的数据通常采用配置的方式来存储,服务自省提供两种配置实现,即 “中心化映射配置” 和 “本地化映射配置”。
中心化映射配置
明显地,注册中心来扮演动态映射配置的角色并不适合,不然,Dubbo Service 与映射关系在注册中心是平级的,无论在理解上,还是设计上是混乱的。结合 Dubbo 现有基础设施分析,这个存储设施可由 Dubbo 配置中心承担。
其中 Dubbo 2.7.5 动态配置 API(DynamicConfiguration )支持二级结构,即:group 和 key,其中,group 存储 Dubbo 服务 ID,而 key 则关联对应的 Dubbo Service 名称,对应的 “图 4” 的数据结构则是:
(图 5)
如此设计的原因如下:
1.获取 Dubbo 服务对应 Services
利用 DynamicConfiguration#getConfigKeys(String group) 方法,能够轻松地通过 Dubbo 服务 ID 获取其发布的所有 Dubbo Services,结合服务发现接口获取服务所部署的 Service 实例集合,最终转化为 Dubbo URL 列表。
1.避免 Dubbo Services 配置相互覆盖
以 Dubbo 服务 ID dubbo:com.acme.Interface1:default 为例,它的提供者 Dubbo Services 分别:P1 Service 和 P2 Service。假设配置 Group 为 “default”(任意名字均可), Key 为 “dubbo:com.acme.Interface1:default”,而内容则是 Dubbo Service 名称的话。当 P1 Service 和 P2 Service 同时启动时,无论哪个 Services 最后完成 Dubbo 服务暴露,那么,该配置内容必然是二选其一,无论配置中心是否支持原子操作。即使配置中心支持内容追加的特性,由于两个 Service 服务实例过程不确定,配置内容可能会出现重复,如:“P1 Service,P2 Service,P1 Service”。
1.获取 Dubbo 服务发布的 timestamp
配置中心潜在的压力
假设当 P1 Service 存在 5 个服务实例,当 Dubbo 服务 dubbo:com.acme.Interface1:default(ID)发布时,配置所关联的 key 就是当前 Dubbo Service 名称,即(P1 Service),而内容则是最后发布该 Dubbo 服务的时间戳(timestamp)。当服务实例越多时,配置中心和网络传输所承受的写入压力也就越大。当然架构设计上,服务自省也希望避免重复推送配置,比如在 DynamicConfiguration API 增加类似于 publishConfigIfAbsent 这样的方法,不过目前大多数配置中心产品(如:Nacos、Consul)不支持这样的操作,所以未来服务自省架构会有针对性的提供支持(如:Zookeeper)。
注册中心作为配置中心
由于服务自省架构必须依赖注册中心,同时动态映射配置又依赖配置中心的话,应用的架构复杂度和维护成本均有所提升,不过 Apache Dubbo 所支持的部分注册中心也可作为配置中心使用,情况如下所示:
本地化映射配置
如果开发人员认为配置中心的引入增加了架构的复杂性,那么,静态映射配置或许是一种解决方案。
该特性并未在最新 Dubbo 2.7.6 全面发布,部分特性已在 Dubbo Spring Cloud 中发布
接口映射配置
在 Dubbo 传统的编程模型中, 常以 Java 注解 @Reference 或 XML 元素 `` 订阅目标 Dubbo 服务。服务自省架构在此基础上增加 service 属性的映射一个或多个 Dubbo Service 名称,如:
如此配置后,Dubbo 服务 com.acme.Interface1 将向 p1-service 和 p2-service 订阅服务。如果开发人员认为这种方式会侵入到代码,服务自省还提供外部化配置方式配置映射。
外部化映射配置
服务自省架构支持外部化配置的方式声明“Dubbo 服务与 Service 映射”,配置格式为 Properties ,以图 4 为例,内容如下:
(图 6)
不过,映射关系并非是一种强约束,Dubbo Provider 的服务是否可用的检验方法是探测目标 Dubbo Service 是否存在,并需确认订阅的 Dubbo 服务在目标 Services 是否真实暴露,因此,服务自省引入了 Dubbo 元数据服务架构,来完成 Dubbo 服务 URL 的存储。
元数据服务架构
Dubbo 元数据服务是一个常规的 Dubbo 服务,为服务订阅端提供 Dubbo 元数据的服务目录,类似于 WebServices 中的 WDSL 或 REST 中的 HATEOAS,帮助 Dubbo Consumer 获取订阅的 Dubbo 服务的 URL 列表。元数据服务架构无法独立于服务注册与发现架构而存在,下面通过“整体架构”的讨论,了解两者之间的关系。
整体架构
架构上,无论 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 。
微观架构
从 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 个服务实例。具体“合成”的细节需要结合“宏观架构”来说明。
宏观架构
元数据服务的宏观架构依赖于服务注册与发现架构,如下图所示:
(图 8)
图 8 中 p 和 c 分别代表 Provider 和 Consumer 的执行动作,后面紧跟的数字表示动作的次序,从 0 开始计数。执行动作是串行的,并属于 Fast-Fail 设计,如果前阶段执行失败,后续动作将不会发生。之所以如此安排是为了确保 MetadataService 能够暴露和消费。首先从 Provider 执行流程开始说明。
Provider 执行流程
p0:发布所有的 Dubbo 应用服务,声明和定义方式与传统方式完全相同。
p1:暴露 MetadataService ,该步骤完全由框架自行处理,无论是否 p0 是否暴露 Dubbo 服务
p2:在服务实例注册之前, 框架将触发并处理事件(Event),将 MetadataService 的元数据先同步到服务实例(Service Instance)的元数据。随后,执行服务实例注册
p3:建立所有的 Dubbo 应用服务与当前 Dubbo Service 名称的映射,并同步到配置源(抽象)
Consumer 执行流程
c0:注册当前 Dubbo Service 的服务实例,可选步骤,架构允许 Consumer 不进行服务注册
c1:通过订阅 Dubbo 服务元信息查找配置源,获取对应 Dubbo Services 名称(列表)
c2:利用已有 Dubbo Service 名称(可能存在多个),通过服务发现 API 获取 Provider 服务实例集合。假设 Service 名称 P,服务实例数量为 N
c3:
a.随机选择 Provider 一台服务实例 Px,从中获取 MetadataService 的元数据
b.将元数据组装 MetadataService Dubbo 调用客户端(代理)
c.发起 MetadataService Dubbo 调用,获取该服务实例 Px 所暴露的 Dubbo 应用服务 URL 列表
d.从 Dubbo 应用服务 URL 列表过滤出当前订阅 Dubbo 应用服务的 URL
e.理论上,步骤 c 和 d 还需要执行 N-1 次才能获取 P 所有服务实例的 Dubbo URL 列表。为了减少调用次数,步骤 d 的结果作为模板,克隆其他 N-1 台服务实例 URL 列表
f.将所有订阅 Dubbo 应用服务的 URL 同步到 Dubbo 客户端(与传统方式是相同的)
c4:发起 Dubbo 应用服务调用(与传统方式是相同的)
不难看出,上述架构以及流程结合了“服务注册与发现”与“元数据服务”双架构,步骤之间会触发相关 Dubbo 事件,如“服务实例注册前事件”等。换言之,三种架构综合体也就是服务自省架构。
至此,关于 Dubbo 服务自省架构设计方面,还存在一些细节亟待说明,比如:
1.不同的 Dubbo Service 的 MetadataService 怎样体现差异呢?
2.MetadataService 作为一个常规的 Dubbo 服务,它的注册元信息存放在何处?
3.MetadataService 作为服务目录,它管理的 Dubbo 应用服务 URL 是如何存储的?
4.在 Consumer 执行流程的 c3.e 中,克隆 N - 1 条 URL 的前提是该 Provider 的所有服务实例均部署了相同 Dubbo 应用服务。如果 Provider 处于升级的部署过程,同一 Dubbo 应用服务接口在不同的服务实例上存在差异,那么该服务的 URL 应该如何获取?
5.除了 Dubbo 服务 URL 发现之外,元数据服务还支持哪些元数据类型呢?
元数据服务 Metadata
元数据服务 Metadata,称之为“元数据服务的元数据”,主要包括:
inteface:Dubbo 元数据服务所暴露的接口,即 MetadataService
serviceName : 当前 MetadataService 所部署的 Dubbo Service 名称,作为 MetadataService 分组信息
group:当前 MetadataService 分组,数据使用 serviceName
version:当前 MetadataService 的版本,版本号通常在接口层面声明,不同的 Dubbo 发行版本 version 可能相同,比如 Dubbo 2.7.5 和 2.7.6 中的 version 均为 1.0.0。理论上,version 版本越高,支持元信息类型更丰富
protocol: MetadataService 所暴露协议,为了确保 Provider 和 Consumer 通讯兼容性,默认协议为:“dubbo”,也可以支持其他协议。
port:协议所使用的网络端口
host:当前 MetadataService 所在的服务实例主机或 IP
params:当前 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 存放在何处。
元数据服务存储模式
Dubbo 2.7.5 在引入 MetadataService 的同时,也为其设计了两种存储方式,适用于不同的场景,即“本地存储模式”和“远程存储模式”。其中,本地存储模式是默认选项。
元数据服务本地存储模式
本地存储模式又称之为内存存储模式(In-Memory),如 Dubbo 应用服务发现和注册场景中,暴露和订阅的 URL 直接存储在内存中。架构上,本地存储模式的 MetadataService 相当于去中心化的 Dubbo 应用服务的注册中心。
元数据服务远程存储模式
远程存储模式,与去中心化的本地存储模式相反,采用 Dubbo 元数据中心来管理 Dubbo 元信息,又称之为元中心化存储模式(Metadata Center)。
选择存储模式
为了减少负载压力和维护成本,服务自省中的元数据服务推荐使用“本地存储模式”。
回顾前文“Consumer 执行流程”中的步骤 c3.e,为了减少 MetadataService 调用次数,服务自省将第一次的调用结果作为模板,再结合其他 N-1 服务实例的元信息,合成完整的 N 台服务实例的 Dubbo 元信息。假设,Dubbo Service 服务实例中部署的 Dubbo 服务数量和内容不同,那么,c3.e 的执行步骤是存在问题的。因此,服务自省引入“Dubbo 服务修订版本”的机制来解决不对等部署的问题。
尽管“Dubbo 服务修订版本”机制能够介绍 MetadataService 整体消费次数,然而当新修订版本的服务实例过少,并且 Consumer 过多时,如新的版本 Provider 应用分批部署,每批的服务实例为 1 台,而其 Consumer 服务实例成千上万。为了确保这类场景的稳定性,Provider 和 Consumer 的 MetadataService 可选择“远程存储模式”,避免消费热点的发生。
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 可能出现升级的情况有:
1.Dubbo 服务 interface 升级
由于 Dubbo 基于 Java 接口来暴露服务,同时 Java 接口通常在 Dubbo 微服务中又是唯一的。如果 interface 的全类名调整的话,那么,相当于 com.acme.Interface1 做下线处理,Consumer 将无法消费到该 Dubbo 服务,这种情况不予考虑。如果是 Provider 新增服务接口的话,那么 com.acme.Interface1 则并没有变化,也无需考虑。所以,有且仅有一种情况考虑,即“Dubbo interface 方法声明升级”,包括:
增加服务方法
删除服务方法
修改方法签名
1.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 变化,这类情况无需处理。
1.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)
1.Consumer 通过服务发现 API 向注册中心获取 Provider 服务实例列表
2.注册中心返回 6 台服务实例,其中 revision 为 1 的服务实例为 Instance 1 到 3, revision 为 2 的服务实例是 Instance 4 和 Instance 5,revision 为 3 的服务实例仅有 Instance 6
3.Consumer 在这 6 台服务实例中随机选择一台,如图中 Instance 3
4.Consumer 向 Instance 3 发起 MetadataService 的远程调用,获得 Dubbo URL 列表,并建立 revision 为 1 的 URL 列表缓存,用 cache = { 1:urls(r1) } 表示
5.(重复步骤 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) }
6.(重复步骤 4)Consumer 再从剩余的 4 台服务实例中随机选择一台,假设服务实例是 Instance 6,由于此时 revision 为3,所以缓存 cache = { 1:urls(r1) , 2:urls(r2) } 再次未命中,再次发起远程调用,并更新缓存 cache = { 1:urls(r1) , 2:urls(r2) , 3:urls(r3) }
7.(重复步骤 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 元数据,还有其他类型的支持。
元数据类型
架构上,元数据服务(MetadataService)未来将逐步替代 Dubbo 2.7.0 元数据中心,并随着 Dubbo 版本的更迭,所支持的元数据类型也将有所变化,比如 Dubbo 2.7.5 元数据服务支持的类型包括:
Dubbo 暴露的服务 URL 列表
Dubbo 订阅的服务 URL 列表
Dubbo 服务定义
关键词:服务自省架构,Apache Dubbo,云原生,微服务,Spring Cloud