图 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:https://zookeeper.apache.org/
HashiCorp Consul:https://www.consul.io/
Netflix Eureka:https://github.com/netflix/Eureka
Alibaba Nacos:https://nacos.io/
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 列表。
(2)避免 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”。
(3)获取 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 所支持的部分注册中心也可作为配置中心使用,情况如下所示:
基础软件 | 注册中心 | 配置中心 |
---|---|---|
Apache Zookeeper | ✅ | ✅ |
HashiCorp Consul | ✅ | ✅ |
Alibaba Nacos | ✅ | ✅ |
Netflix Eureka | ✅ | ❌ |
Kubernetes API Server | ✅ | ❌ |
其中,Zookeeper、Consul 和 Nacos 是目前业界流行的注册中心,这对于大多数选择开源产品的应用无疑是一个福音。
本地化映射配置
如果开发人员认为配置中心的引入增加了架构的复杂性,那么,静态映射配置或许是一种解决方案。
注:该特性并未在最新 Dubbo 2.7.6 全面发布,部分特性已在 Dubbo Spring Cloud[2]中发布。
(1)接口映射配置
在 Dubbo 传统的编程模型中, 常以 Java 注解 @Reference 或 XML 元素 <reference> 订阅目标 Dubbo 服务。服务自省架构在此基础上增加 service 属性的映射一个或多个 Dubbo Service 名称,如:
<reference services="P1 Service,P2 Service" interface="com.acme.Interface1" />
或
@Reference(services="P1 Service,P2 Service")
private com.acme.Interface1 interface1;
如此配置后,Dubbo 服务 com.acme.Interface1 将向 p1-service 和 p2-service 订阅服务。如果开发人员认为这种方式会侵入到代码,服务自省还提供外部化配置方式配置映射。
(2)外部化映射配置
服务自省架构支持外部化配置的方式声明“Dubbo 服务与 Service 映射”,配置格式为 Properties ,以图 4 为例,内容如下:
dubbo\:com.acme.Interface1\:default = P1 Service,P2 Service
thirft\:com.acme.InterfaceX = P1 Service,P3 Service
rest\:com.acme.interfaceN = P1 Service
(3)应用级别映射配置
除此之外,Dubbo Spring Cloud 提供应用级别的 Dubbo 服务映射配置,即 dubbo.cloud.subscribed-services ,例如:
dubbo:
cloud:
subscribed-services: P1 Service,P3 Service
总之,无论是映射配置的方式是中心化还是本地化,服务 Consumer 依赖这些数据来定位 Dubbo Provider Services,再通过服务发现 API 结合 Service 名称(列表)获取服务实例集合,为合成 Dubbo URL 做准备: