服务发现之元数据结构
在使用微服务构建复杂的分布式系统时,如何感知 backend 服务实例的动态上下线,也是微服务框架最需要关心并解决的问题之一。业界将这个问题称之为 - 微服务的地址发现(Service Discovery)
1.dubbo元数据如何组织?
2.dubbo2.x
2.1URL统一模型
对于 dubbo 中的 URL,有人理解为配置总线,有人理解为统一配置模型,说法虽然不同,但都是在表达一个意思,这样的 URL 在 dubbo 中被当做是公共契约,所有扩展点参数都包含 URL 参数,URL 作为上下文信息贯穿整个扩展点设计体系。
dubbo://10.2.17.218:7093/com.test.es.core.service.ShopSearchService?anyhost=true&application=es-service&bean.name=com.test.es.core.service.ShopSearchService&bind.ip=10.2.17.218&bind.port=7093&default.layer=10.2.17.218&default.retries=0&default.threads=200&default.warmup=20000&dispatcher=message&dubbo=2.0.2&generic=false&interface=com.test.es.core.service.ShopSearchService&logger=slf4j&methods=searchOrderList,selectOrderInfoBySort,count,selectOrderInfo,selectShopOrder&pid=9676®ister=false&side=provider&threads=200&timeout=1000×tamp=1701933846936
2.dubbo2.x变更流程
流程:
-
用户新增/删除DemoService的某个具体Provider实例(常见于扩容缩容、网络波动等原因)
-
ZooKeeper将DemoService下所有实例推送给Consumer端
-
Consumer端根据Zookeeper推送的数据重新全量生成URL 根据该方案可以看出在Provider实例数量较小时,Consumer端的影响比较小,但当某个接口有大量Provider实例时,便会有大量不必要的URL创建过程。
3.两面性
一个事物总是有其两面性,Dubbo2 地址模型带来易用性和强大功能的同时,也给整个架构的水平可扩展性带来了一些限制。这个问题在普通规模的微服务集群下是完全感知不到的,而随着集群规模的增长,当整个集群内应用、机器达到一定数量时,整个集群内的各个组件才开始遇到规模瓶颈。在总结包括阿里巴巴、工商银行等多个典型的用户在生产环境特点后,我们总结出以下两点突出问题(如图中红色所示):
• 首先,注册中心集群容量达到上限阈值。由于所有的 URL 地址数据都被发送到注册中心,注册中心的存储容量达到上限,推送效率也随之下降。
• 而在消费端这一侧,Dubbo2 框架常驻内存已超 40%,每次地址推送带来的 cpu 等资源消耗率也非常高,影响正常的业务调用。
为什么会出现这个问题?我们以一个具体 provider 示例进行展开,来尝试说明为何应用在接口级地址模型下容易遇到容量问题。 青蓝色部分,假设这里有一个普通的 Dubbo Provider 应用,该应用内部定义有 10 个 RPC Service,应用被部署在 100 个机器实例上。这个应用在集群中产生的数据量将会是 “Service 数 * 机器实例数”,也就是 10 * 100 = 1000 条。数据被从两个维度放大:
• 从地址角度。100 条唯一的实例地址,被放大 10 倍
• 从服务角度。10 条唯一的服务元数据,被放大 100 倍
一个典型的极端场景即是 Dubbo 体系中的网关型应用,有些网关应用消费(订阅)达 100+ 应用,而消费(订阅)的服务有 1000+ ,平均有 10 个接口来自同一个应用,如果我们把地址推送和计算的粒度改为应用,则地址推送量从原来的 n * 1000 变为 n * 100,地址数量降低可达近 90%。
4.dubbo3.x
-
注册中心。协调 Consumer 与 Provider 之间的地址注册与发现
-
配置中心。
-
存储 Dubbo 启动阶段的全局配置,保证配置的跨环境共享与全局一致性
-
负责服务治理规则(路由规则、动态配置等)的存储与推送。
-
-
元数据中心。
-
接收 Provider 上报的服务接口元数据,为 Admin 等控制台提供运维能力(如服务测试、接口文档等)
-
作为服务发现机制的补充,提供额外的接口/方法级别配置信息的同步能力,相当于注册中心的额外扩展
-
对于一个原先采用老版本Dubbo搭建的应用服务,在迁移到Dubbo 3时,Dubbo 3 会需要一个元数据中心来维护RPC服务与应用的映射关系(即接口与应用的映射关系),因为如果采用了应用级别的服务发现和服务注册,在注册中心中将采用“应用 —— 实例列表”结构的数据组织形式,不再是以往的“接口 —— 实例列表”结构的数据组织形式,而以往用接口级别的服务注册和服务发现的应用服务在迁移到应用级别时,得不到接口与应用之间的对应关系,从而无法从注册中心得到实例列表信息,所以Dubbo为了兼容这种场景,在Provider端启动时,会往元数据中心存储接口与应用的映射关系。
-
为了让注册中心更加聚焦与地址的发现和推送能力,减轻注册中心的负担,元数据中心承载了所有的服务元数据、大量接口/方法级别配置信息等,无论是接口粒度还是应用粒度的服务发现和注册,元数据中心都起到了重要的作用。
-
4.1目的
-
元数据中心来维护RPC服务与应用的映射关系(即接口与应用的映射关系)来兼容接口与应用之间的对应关系
-
让注册中心更加聚焦与地址的发现和推送能力
5.dubbo3服务自省机制
在注册中心不再同步 RPC 服务信息后,服务自省在服务消费端和提供端之间建立了一条内置的 RPC 服务信息协商机制,这也是“服务自省”这个名字的由来。服务端实例会暴露一个预定义的 MetadataService RPC 服务,消费端通过调用 MetadataService 获取每个实例 RPC 方法相关的配置信息。
服务自省机制机制将以前注册中心传递的 URL 一拆为二:
-
一部分和实例相关的数据继续保留在注册中心,如 ip、port、机器标识等。
-
另一部分和 RPC 方法相关的数据从注册中心移除,转而通过 MetadataService 暴露给消费端。
-
服务提供者启动,首先解析应用定义的“普通服务”并依次注册为 RPC 服务,紧接着注册内建的 MetadataService 服务,最后打开 TCP 监听端口。
-
启动完成后,将实例信息注册到注册中心(仅限 ip、port 等实例相关数据),提供者启动完成。
-
服务消费者启动,首先依据其要“消费的 provider 应用名”到注册中心查询地址列表,并完成订阅(以实现后续地址变更自动通知)。
-
消费端拿到地址列表后,紧接着对 MetadataService 发起调用,返回结果中包含了所有应用定义的“普通服务”及其相关配置信息
-
至此,消费者可以接收外部流量,并对提供者发起 Dubbo RPC 调用
5.1元数据同步机制提供两种方案本地和远程。
-
内建 MetadataService。
-
独立的元数据中心,通过中心化的元数据集群协调数据。
5.2轻量化注册中心
引入 MetadataService 元数据服务服务的好处
-
由中心化推送转向点对点拉取(Consumer - Proroder)
-
易于扩展更多的参数
-
更多的数据量
-
对外暴露更多的治理数据
5.3应用-服务接口映射关系
老的 Consumer 开发与配置示例:
<!-- 框架直接通过 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 开发与配置示例:
<!-- 框架需要通过额外的 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 应用名的转换。
6.延迟通知-服务上、下线感知
在该方案中,当 Consumer 接收 Zookeeper 的变更通知后会主动休眠一段时间,而这段时间内的变更在休眠结束后只会保留最后一次变更,Consumer 便会使用最后一次变更来进行监听实例的更新,以此方法来减少大量 URL 的创建开销。