传统架构
Apache Dubbo 是一款面向接口代理的高性能 RPC 框架,提供服务注册与发现的特性,其基础架构如下图所示:
图 1
Provider 为服务提供方,提供 Java 服务接口的实现,并将其元信息注册到 Dubbo 注册中心(过程 1.register 所示);
Consumer 为服务消费端,从 Dubbo 注册中心检索订阅的 Java 服务接口的元信息(过程 2.subscribe 所示),通过框架处理后,生成代理程序执行远程方法调用(过程 4.invoke 所示);
Registry 为注册中心,属于注册元信息中心化基础设施(如 Apache Zookeeper 或 Alibaba Nacos),为 Provider 提供注册通道,为 Cosumer 提供订阅渠道。同时,注册中心支持注册元信息变更通知,通知 Consumer 上游 Provider 节点的变化(如扩容或缩容)。而注册元信息均以 Dubbo URL 的形式存储;
Monitor 为服务治理平台,提供开发和运维人员服务查询、路由规则、服务 Mock 和测试等治理能力。
综上所述,Dubbo 注册与发现的对象是 Dubbo 服务(Java 接口),而其载体为注册元信息,即 Dubbo URL,如:
dubbo://192.168.1.2:20880/com.foo.BarService?version=1.0.0&group=default
通常包含必须信息,如服务提供方 IP 和端口、Java 接口,可选包含版本(Version)和分组(Group)等。服务 URL 所包含的信息能够唯一界别服务提供方的进程。
现实挑战
为了更好地符合 Java 开发人员的编程习惯,Dubbo 以 Java 服务接口作为注册对象,所面临的现实挑战主要有:
如何解决或缓解注册中心压力过载;
如何支持以应用为粒度的服务注册与发现;
如何精简 Dubbo URL 元数据。
▐ 如何解决或缓解注册中心压力过载?
注册中心内存压力
Dubbo 注册中心是中心化的基础设施,大多数注册中心的实现为内存型存储,比如 Zookeeper、Nacos 或 Consul、Eureka。注册中心的内存消耗与 Dubbo 服务注册的数量成正比,任一 Dubbo Provider 允许注册 N 个 Dubbo 服务接口,当 N 越大,注册中心的负载越重。
根据不完全统计,Dubbo 核心 Provider 用通常会暴露 20 ~ 50 个服务接口。注册中心是中心化的基础设施,其稳定性面临严峻考验。尽管微服务架构不断地深化,然而现实情况是,更多开发者仍旧愿意在单一 Provider 上不断地增加 Dubbo 服务接口,而非更细粒度的 Dubbo Provider 组织。
注册中心网络压力
为了避免单点故障,主流的注册中心均提供高可用方案。为解决集群环境数据同步的难题,内建一致性协议,如 Zookeeper 使用的 Zab 协议,Consul 采用的 Raft 协议。无论哪种方式,当 Dubbo URL 数量变化频繁时,网络和 CPU 压力也会面临考验。如果注册中心与客户端之间维持长连接状态的话,如 Zookeeper,注册中心的网络负担会更大。
注册中心通知压力
假设某个 Dubbo Provider 注册了 N 个 Dubbo 服务接口,当它扩容或缩容 M 个实例(节点)时,N 数量越大,注册中心至少有 M * N 个 Dubbo URL 注册或移除。同时,大多数注册中心实现支持注册变化通知,如 Zookeeper 节点变化通知。
当 Dubbo Consumer 订阅该 Provider 的 Dubbo 服务接口数为 X 时,X 数值越大,通知的次数也就越多。实际上,对于来自同一 Provider 的服务接口集合而言,X-1 次通知是重复和无价值的。
如果 Dubbo 注册实体不再是服务 URL,而是 Dubbo Provider 节点的话,那么上述情况所描述的注册中心压力将得到很大程度的缓解。(负载只有过去的 1/N 甚至更少),然而 Dubbo 如何以应用为粒度来注册又是一个新的挑战。
▐ 如何支持以应用为粒度的服务注册与发现?
尽管 Dubbo 也存在应用(Application)的概念,不过传统的使用场景并非核心要素,仅在 Dubbo Monitor 或 Dubbo Admin 场景下做辨识之用。随着微服务架构和云原生技术的兴起,以应用为粒度的注册模型已是大势所趋,如 Spring Cloud 和 Kubernetes 服务注册与发现模型。注册中心所管理的对象通常与业务无关,甚至不具备 RPC 的语义。
在术语上,微服务架构中的“服务”(Services)与云原生中“应用”(Applications)是相同的概念,属于逻辑名称,而它们的成员则以服务实例(Service Instances)体现,服务和服务实例的数量关系为 1:N。
单个服务实例代表一个服务进程,而多个 Dubbo 服务 URL 可隶属一个 Dubbo Provider 进程,因此,Dubbo URL 与服务实例的数量关系是 N : 1。假设一个 Dubbo Provider 进程仅提供一个 Dubbo 服务(接口)的话,即 N = 1 的情况,虽然以应用为粒度的服务注册与发现能够基于 Dubbo 传统的 Registry SPI 实现,不过对于现有 Dubbo 应用而言,将存在巨大的应用微服务化工作。
支持 Spring Cloud 服务注册与发现模型
Spring Cloud 是 VMware 公司(前为 Pivotal)推出的,一套以 Spring 为技术栈的云原生(Cloud-Native)解决方案,在 Java 微服务领域具备得天独厚的优势,拥有超大规模的全球用户。
Spring Cloud 官方支持三种注册中心实现,包括:Eureka、Zookeeper 和 Consul,Spring Cloud Alibaba 扩展了 Nacos 注册中心实现。尽管 Zookeeper、Consul 和 Nacos 也被 Apache Dubbo 官方支持,然而两者的服务注册与发现的机制不尽相同。若要 Dubbo 支持 Spring Cloud 服务注册与发现模型,Dubbo 则需基于 Dubbo Registry SPI 实现,否则底层的变化和兼容性存在风险。
支持 Kubernetes 服务注册与发现模型
Kubernetes 源自 Google 15 年生产环境的运维经验,是一个可移植的、可扩展的开源平台,用于管理容器化的工作负载和服务。Kubernetes 原生服务发现手段主要包括:DNS 和 API Server。
DNS 服务发现是一种服务地址的通用方案,不过对于相对复杂 Dubbo 元数据而言,这种服务发现机制或许无法直接被 Dubbo Registry SPI 适配。相反,API Server 所支持相对更便利,毕竟 Spring Cloud Kubernetes 同样基于此机制实现,并已在生产环境得到验证。换言之,只要 Dubbo 支持 Spring Cloud 服务注册与发现模型,那么基于 Kubernetes API Server 的支持也能实现。
兼容 Dubbo 传统服务注册与发现模型
所谓兼容 Dubbo 传统服务注册与发现模型,包含两层含义:
基于 Dubbo Registry SPI 同时支持 Spring Cloud 和 Kubernetes 服务注册与发现模型;
传统和新的 Dubbo 服务注册与发现模型之间能够相互发现。
▐ 如何精简 Dubbo URL 元数据?
Dubbo 从 2.7.0 开始增加了简化 URL 元数据的特性,被“简化”的数据存放至元数据中心。由于 Dubbo 传统服务注册与发现模型并未减少 Dubbo 服务 URL 注册数量。因此,精简后的 URL 并未明显地减少注册中心所承受的压力。
同时,Dubbo URL 元数据精简模式存在一定的限制,即所有的 Dubbo Provider 节点必须是无状态的,每个节点中的 URL 元信息均是一致的,现实中,这个要求非常难以保证,尤其在同一 Provider 节点存在不同的版本或配置的情况下。综上所述,Dubbo URL 元数据需要进一步精简,至少压力应该避免聚集在注册中心之上。
架构设计
架构上,Dubbo 服务自省不仅要解决上述挑战,而且实际场景则更为复杂,因此,架构细节也将循序渐进地展开讨论,整体架构可由以下子架构组成:
服务注册与发现架构
元数据服务架构
事件驱动架构
▐ 服务注册与发现架构
Dubbo 服务自省首要需求是减轻注册中心的承载的压力,同时,以应用为粒度的服务注册与发现模型不但能够最大化的减少 Dubbo 服务元信息注册数量。而且还能支持 Spring Cloud 和 Kubernetes 环境,可谓是一举两得,架构图如下所示: