术语约定

  • 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 从 2.7.5 版本开始,新引入了一种基于应用粒度的服务发现机制

什么是服务自省(introspect)

1

以 Dubbo 当前的地址发现数据格式为例,它是“RPC 服务粒度”的,它是以 RPC 服务作为 key,以实例列表作为 value 来组织数据的:

1
2
3
4
5
6
7
“RPC Service1”: [
{“name”:”instance1″, “ip”:”127.0.0.1″, “metadata”:{“timeout”:1000}},
{“name”:”instance2″, “ip”:”127.0.0.1″, “metadata”:{“timeout”:2000}},
{“name”:”instance3″, “ip”:”127.0.0.1″, “metadata”:{“timeout”:3000}},
]
“RPC Service2”: [Instance list of RPC Service2],
“RPC ServiceN”: [Instance list of RPC ServiceN]

而新引入的“应用粒度的服务发现”,它以应用名(Application)作为 key,以这个应用部署的一组实例(Instance)列表作为 value。这带来两点不同:

  1. 数据映射关系变了,从 RPC Service -> Instance 变为 Application -> Instance
  2. 数据变少了,注册中心没有了 RPC Service 及其相关配置信息
1
2
3
4
5
“application1”: [
{“name”:”instance1″, “ip”:”127.0.0.1″, “metadata”:{}},
{“name”:”instance2″, “ip”:”127.0.0.1″, “metadata”:{}},
{“name”:”instanceN”, “ip”:”127.0.0.1″, “metadata”:{}}
]

要进一步理解新模型带来的变化,我们看一下应用与 RPC 服务间的关系,显而易见的,1 个应用内可能会定义 n 个 RPC Service。因此 Dubbo 之前的服务发现粒度更细,在注册中心产生的数据条目也会更多(与 RPC 服务成正比),同时也存在一定的数据冗余。

简单理解了应用级服务发现的基本机制,接着解释它为什么会被叫做“服务自省”?其实这还是得从它的工作原理说起,上面我们提到,应用粒度服务发现的数据模型有几个以下明显变化:数据中心的数据量少了,RPC 服务相关的数据在注册中心没有了,现在只有 application – instance 这两个层级的数据。为了保证这部分缺少的 RPC 服务数据仍然能被 Consumer 端正确的感知,我们在 Consumer 和 Provider 间建立了一条单独的通信通道:Consumer 和 Provider 两两之间通过特定端口交换信息,我们把这种 Provider 自己主动暴露自身信息的行为认为是一种内省机制,因此从这个角度出发,我们把整个机制命名为:服务自省。

使用场景

服务自省是 Dubbo 应用在运行时处理和分析 Dubbo 服务元信息(Metadata)的过程,如当前应用暴露 的 Dubbo 服务以及各自的通讯协议等。期间会伴随着事件的广播和处理,如服务暴露事件。Dubbo 服务自省架构是其传统架的一种补充,更是未来 Dubbo 架构,它更适合以下使用场景:

超大规模 Dubbo 服务治理场景如果 Dubbo 集群规模超过一千以上,或者集群扩缩容已无法自如地执行,如 Zookeeper 管理数万 Dubbo 服务,服务自省可极大化减轻注册中心的压力,尤其在内存足迹、网络传输以及变更通知上体现。

微服务架构和元原生应用如果想要 Dubbo 应用更好地微服务化,或者更接近于云原生应用,那么服务自省是一种不错的选择。它能够提供已应用为粒度的服务注册与发现模型,全面地支持最流行的 Spring Cloud 和 Kubernetes 注册中心,并且能与 Spring Cloud 或 Spring Boot 应用交互。

Dubbo 元数据架构的基石Dubbo 元数据架构是围绕 Dubbo DevOps 而引入,包括 Dubbo 配置元数据(如:属性配置、路由规则等)和结构元数据(如:Java 注解、接口和文档等)。服务自省作为 Dubbo 元数据的基础设施,不仅支持所有元数据的存储和平滑升级,而且不会对注册中心、配置中心和元数据中心产生额外的负担。

工作原理

设计原则

  • 新的服务发现模型要实现对原有 Dubbo 消费端开发者的无感知迁移,即 Dubbo 继续面向 RPC 服务编程、面向 RPC 服务治理,做到对用户侧完全无感知。
  • 建立 Consumer 与 Provider 间的自动化 RPC 服务元数据协调机制,解决传统微服务模型无法同步 RPC 级接口配置的缺点。

基本原理详解

应用级服务发现作为一种新的服务发现机制,和以前 Dubbo 基于 RPC 服务粒度的服务发现在核心流程上基本上是一致的:即服务提供者往注册中心注册地址信息,服务消费者从注册中心拉取&订阅地址信息。

这里主要的不同有以下两点:

注册中心数据以“应用 – 实例列表”格式组织,不再包含 RPC 服务信息

以下是每个 Instance metadata 的示例数据,总的原则是 metadata 只包含当前 instance 节点相关的信息,不涉及 RPC 服务粒度的信息。

总体信息概括如下:实例地址、实例各种环境标、metadata service 元数据、其他少量必要属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
“name”: “provider-app-name”,
“id”: “192.168.0.102:20880”,
“address”: “192.168.0.102”,
“port”: 20880,
“sslPort”: null,
“payload”: {
“id”: null,
“name”: “provider-app-name”,
“metadata”: {
“metadataService”: “{\”dubbo\”:{\”version\”:\”1.0.0\”,\”dubbo\”:\”2.0.2\”,\”release\”:\”2.7.5\”,\”port\”:\”20881\”}}”,
“endpoints”: “[{\”port\”:20880,\”protocol\”:\”dubbo\”}]”,
“storage-type”: “local”,
“revision”: “6785535733750099598”,
}
},
“registrationTimeUTC”: 1583461240877,
“serviceType”: “DYNAMIC”,
“uriSpec”: null
}

Client – Server 自行协商 RPC 方法信息

在注册中心不再同步 RPC 服务信息后,服务自省在服务消费端和提供端之间建立了一条内置的 RPC 服务信息协商机制,这也是“服务自省”这个名字的由来。服务端实例会暴露一个预定义的 MetadataService RPC 服务,消费端通过调用 MetadataService 获取每个实例 RPC 方法相关的配置信息。

当前 MetadataService 返回的数据格式如下,

1
2
3
4
5
[
“dubbo://192.168.0.102:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=9585&release=2.7.5&side=provider&timestamp=1583469714314”,
“dubbo://192.168.0.102:20880/org.apache.dubbo.demo.HelloService?anyhost=true&application=demo-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=9585&release=2.7.5&side=provider&timestamp=1583469714314”,
“dubbo://192.168.0.102:20880/org.apache.dubbo.demo.WorldService?anyhost=true&application=demo-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=9585&release=2.7.5&side=provider&timestamp=1583469714314”
]

熟悉 Dubbo 基于 RPC 服务粒度的服务发现模型的开发者应该能看出来,服务自省机制机制将以前注册中心传递的 URL 一拆为二:

  • 一部分和实例相关的数据继续保留在注册中心,如 ip、port、机器标识等。
  • 另一部分和 RPC 方法相关的数据从注册中心移除,转而通过 MetadataService 暴露给消费端。

理想情况下是能达到数据按照实例、RPC 服务严格区分开来,但明显可以看到以上实现版本还存在一些数据冗余,有些也数据还未合理划分。尤其是 MetadataService 部分,其返回的数据还只是简单的 URL 列表组装,这些 URL其实是包含了全量的数据。

以下是服务自省的一个完整工作流程图,详细描述了服务注册、服务发现、MetadataService、RPC 调用间的协作流程。

  • 服务提供者启动,首先解析应用定义的“普通服务”并依次注册为 RPC 服务,紧接着注册内建的 MetadataService 服务,最后打开 TCP 监听端口。
  • 启动完成后,将实例信息注册到注册中心(仅限 ip、port 等实例相关数据),提供者启动完成。
  • 服务消费者启动,首先依据其要“消费的 provider 应用名”到注册中心查询地址列表,并完成订阅(以实现后续地址变更自动通知)。
  • 消费端拿到地址列表后,紧接着对 MetadataService 发起调用,返回结果中包含了所有应用定义的“普通服务”及其相关配置信息。
  • 至此,消费者可以接收外部流量,并对提供者发起 Dubbo RPC 调用

在以上流程中,我们只考虑了一切顺利的情况,但在更详细的设计或编码实现中,我们还需要严格约定一些异常场景下的框架行为。比如,如果消费者 MetadataService 调用失败,则在重试知道成功之前,消费者将不可以接收外部流量。

服务自省中的关键机制

元数据同步机制

Client 与 Server 间在收到地址推送后的配置同步是服务自省的关键环节,目前针对元数据同步有两种具体的可选方案,分别是:

  • 内建 MetadataService。
  • 独立的元数据中心,通过中细化的元数据集群协调数据。

1. 内建 MetadataService
MetadataService 通过标准的 Dubbo 协议暴露,根据查询条件,会将内存中符合条件的“普通服务”配置返回给消费者。这一步发生在消费端选址和调用前。

元数据中心
复用 2.7 版本中引入的元数据中心,provider 实例启动后,会尝试将内部的 RPC 服务组织成元数据的格式到元数据中心,而 consumer 则在每次收到注册中心推送更新后,主动查询元数据中心。

注意 consumer 端查询元数据中心的时机,是等到注册中心的地址更新通知之后。也就是通过注册中心下发的数据,我们能明确的知道何时某个实例的元数据被更新了,此时才需要去查元数据中心。

RPC 服务 < – > 应用映射关系

回顾上文讲到的注册中心关于“应用 – 实例列表”结构的数据组织形式,这个变动目前对开发者并不是完全透明的,业务开发侧会感知到查询/订阅地址列表的机制的变化。具体来说,相比以往我们基于 RPC 服务来检索地址,现在 consumer 需要通过指定 provider 应用名才能实现地址查询或订阅。

老的 Consumer 开发与配置示例:

1
2
3
4
5
<!– 框架直接通过 RPC Service 1/2/N 去注册中心查询或订阅地址列表 –>
<dubbo:registry address=”zookeeper://127.0.0.1:2181″/>
<dubbo:reference interface=”RPC Service 1″ />
<dubbo:reference interface=”RPC Service 2″ />
<dubbo:reference interface=”RPC Service N” />

新的 Consumer 开发与配置示例:

1
2
3
4
5
<!– 框架需要通过额外的 provided-by=”provider-app-x” 才能在注册中心查询或订阅到地址列表 –>
<dubbo:registry address=”zookeeper://127.0.0.1:2181?registry-type=service”/>
<dubbo:reference interface=”RPC Service 1″ provided-by=”provider-app-x”/>
<dubbo:reference interface=”RPC Service 2″ provided-by=”provider-app-x” />
<dubbo:reference interface=”RPC Service N” provided-by=”provider-app-y” />

以上指定 provider 应用名的方式是 Spring Cloud 当前的做法,需要 consumer 端的开发者显示指定其要消费的 provider 应用。

以上问题的根源在于注册中心不知道任何 RPC 服务相关的信息,因此只能通过应用名来查询。

为了使整个开发流程对老的 Dubbo 用户更透明,同时避免指定 provider 对可扩展性带来的影响(参见下方说明),我们设计了一套 RPC 服务到应用名的映射关系,以尝试在 consumer 自动完成 RPC 服务到 provider 应用名的转换。

Dubbo 之所以选择建立一套“接口-应用”的映射关系,主要是考虑到 service – app 映射关系的不确定性。一个典型的场景即是应用/服务拆分,如上面提到的配置,PC Service 2 是定义于 provider-app-x 中的一个服务,未来它随时可能会被开发者分拆到另外一个新的应用如 provider-app-x-1 中,这个拆分要被所有的 PC Service 2 消费方感知到,并对应用进行修改升级,如改为,这样的升级成本不可否认还是挺高的。
到底是 Dubbo 框架帮助开发者透明的解决这个问题,还是交由开发者自己去解决,当然这只是个策略选择问题,并且 Dubbo 2.7.5+ 版本目前是都提供了的。其实我个人更倾向于交由业务开发者通过组织上的约束来做,这样也可进一步降低 Dubbo 框架的复杂度,提升运行态的稳定性。

整体架构

架构上,无论 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 执行流程开始说明。

(1)Provider 执行流程

p0:发布所有的 Dubbo 应用服务,声明和定义方式与传统方式完全相同。p1:暴露 MetadataService ,该步骤完全由框架自行处理,无论是否 p0 是否暴露 Dubbo 服务p2:在服务实例注册之前, 框架将触发并处理事件(Event),将 MetadataService 的元数据先同步到服务实例(Service Instance)的元数据。随后,执行服务实例注册p3:建立所有的 Dubbo 应用服务与当前 Dubbo Service 名称的映射,并同步到配置源(抽象)(2)Consumer 执行流程

c0:注册当前 Dubbo Service 的服务实例,可选步骤,架构允许 Consumer 不进行服务注册c1:通过订阅 Dubbo 服务元信息查找配置源,获取对应 Dubbo Services 名称(列表)c2:利用已有 Dubbo Service 名称(可能存在多个),通过服务发现 API 获取 Provider 服务实例集合。假设 Service 名称 P,服务实例数量为 Nc3:1. 随机选择 Provider 一台服务实例 Px,从中获取 MetadataService 的元数据;2. 将元数据组装 MetadataService Dubbo 调用客户端(代理);3. 发起 MetadataService Dubbo 调用,获取该服务实例 Px 所暴露的 Dubbo 应用服务 URL 列表;4. 从 Dubbo 应用服务 URL 列表过滤出当前订阅 Dubbo 应用服务的 URL;5. 理论上,步骤 c 和 d 还需要执行 N-1 次才能获取 P 所有服务实例的 Dubbo URL 列表。为了减少调用次数,步骤 d 的结果作为模板,克隆其他 N-1 台服务实例 URL 列表;6. 将所有订阅 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 发现之外,元数据服务还支持哪些元数据类型呢?元数据服务 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 服务修订版本

当业务出现变化时,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 方法声明升级”,包括:

增加服务方法删除服务方法修改方法签名(2)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 变化,这类情况无需处理。

(3)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 6;Consumer 在这 6 台服务实例中随机选择一台,如图中 Instance 3;Consumer 向 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 元数据,还有其他类型的支持。

元数据类型

架构上,元数据服务(MetadataService)未来将逐步替代 Dubbo 2.7.0 元数据中心[5],并随着 Dubbo 版本的更迭,所支持的元数据类型也将有所变化,比如 Dubbo 2.7.5 元数据服务支持的类型包括:

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 ]

Dubbo 订阅的服务 URL 列表当前 Dubbo Service 所有订阅的 Dubbo 服务 URL 集合,该元数据主要被 Dubbo 运维平台来收集。

Dubbo 服务定义Dubbo 服务提供方(Provider)在服务暴露的过程中,将元信息以 JSON 的格式同步到注册中心,包括服务配置的全部参数,以及服务的方法信息(方法名,入参出参的格式)。在服务自省引入之前,该元数据被 Dubbo 2.7.0 元数据中心 存储,如:

{“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” }]}

参考文档

https://www.cnkirito.moe/dubbo-app-pubsub/

http://baijiahao.baidu.com/s?id=1666431720289962616