饿了么 Dubbo 实践分享
——刘军
阿里云-云原生应用平台,Apache Dubbo PMC
饿了么从 2021年 11 月启动 Dubbo3 升级工作。在此之前,饿了么使用 HSF2 作为服务框架。升级过程历史半年,目前已基本完成,共有将近 2000 个应用和 10 万个节点运行在 Dubbo3 之上。
一、Dubbo 3 简介
Dubbo3 是下一代云原生微服务框架。设计原则上, Dubbo3 面向云原生、支持百万集群实例的可伸缩,强调柔性和智能化流量的调度;策略上,Dubbo3 完全开源;业务价值上,Dubbo3 更强调降低单机资源消耗,提升全链路资源利用率以及云原生时代的服务治理能力。
Dubbo3 从 Dubbo2.0 架构演进而来,继承了开源 Dubbo2 以及阿里内部演进多年的 HSF2 的完整特性和优点,并且保持对 Dubbo2 和 HSF2 完全兼容。因此,老用户几乎实现 Dubbo3 的 0 成本迁移。
Dubbo3 提供的核心特性主要有以下四个:
① 全新服务发现模型。
② 提供了基于 HTTP /2 的Triple 协议。
③ 统一流量治理模型,支持更丰富的流量治理场景。
④ Service Mesh模型:提供了Sidecar Mesh 和 ProxyLess Mesh 两种部署架构。
Dubbo3 基于 Dubbo2 和 HSF2 两款产品诞生,同时以云原生架构作为指导思想,进行了大量重构,并规划了一系列功能特性。在开源 Dubbo3 产品之上,还有基于 Dubbo3 的企业用户实践、生态产品以及公有云厂商云产品,有大家比较关心的 Dubbo3 典型用户阿里巴巴。
在此之前,阿里巴巴一直运行在自研 HSF2 框架之上。虽然 HSF2 和 Dubbo2 有很多相似之处,但如今已经演进成为两个不同框架。实现了 Dubbo3 对 HSF2 和 Dubbo2 的融合后,阿里巴巴完全迁移到Dubbo3。
开源的 Dubbo3,如何满足阿里特有的诉求?答案是:通过 Dubbo3 开源的标准 SPI扩展。因此阿里内部现在还有一套 HSF 3,与以往 HSF 2 完全不同,已经是两个不同的框架。
HSF3是基于标准的 Dubbo3 SPI 生成的扩展库,但也仅仅是一个扩展库,比如注册中心的扩展、路由组件的扩展或者监控组件的扩展,而其他配置的组装服务暴露、服务发现、地址解析等核心流程已经完全跑在 Dubbo3 之上。
在此模式之下,阿里巴巴内部的实现诉求将完全体现在开源的Dubbo3 之上。阿里巴巴内部开发人员同时工作在 Dubbo3 以及内部扩展SPI 库 HSF3之上。
通过 SPI 扩展的模式同样适用于公有云产品或其他厂商实践。
升级到 Dubbo3 后,能够获得以下三方面的收益:
① 性能和资源利用率提升:升级 Dubbo3 后,预期能够实现单机资源接近 50%的资源利用率下降,集群规模越大,效果越明显。同时,Dubbo3 从架构上支持百万实例级别集群的横向扩展。
② 助力业务架构升级:借助于 Dubbo3 提供的新协议,能够让业务架构升级得到更多帮助。比如此前,从前端应用接入到后端,需要经过网关代理,而网关需要进行协议转换或类型映射等工作,因此往往会成为架构的瓶颈。有了 Triple 协议后,过程变得更容易实现,同时还提供了流式通信模型等更丰富的通信场景的支持。
③ 云原生:屏蔽了基础设施层变革,降低 Mesh 升级成本,提供了更丰富的流量治理模型。
Dubbo 从设计之初就内置了服务发现能力。 Provider 注册地址到注册中心,Consumer 通过订阅,实时获取注册中心地址并进行更新。接收到地址列表后,Consumer 基于特定的负载均衡策略发起对Provider 的 RPC 调用,如上图所示。过程中,每个Provider通过特定的 key 向注册中心注册本机可访问地址,而注册中心通过 key 对 Provider 实例进行地址聚合。 Consumer 通过同样 key 从注册中心订阅,以便实时收到聚合后的地址列表。
Provider部署的应用通常有多个 Dubbo Service ,每个 Service 可能都有其独有的配置。Service 服务发布的过程就是基于服务配置生成地址 URL 的过程,生成的地址数据就如上图橙色部分 URL 所示。同样其他服务也会生成地址,即 Dubbo 实例会生成多个 Service URL。
注册中心以 Service 服务名作为数据划分依据,将服务下所有地址的数据都作为子节点进行聚合。子节点内容是实际可访问的 IP 地址,即 Dubbo 中的 URL 。
URL 地址数据的详细格式可划分成四个部分:
① 实例可访问地址:消费端将基于这条数据生成 TCP 链接,作为后续 RPC 数据的传输载体。
② RPC 服务元数据:主要是用于定义和描述一次 RPC请求,表明这条地址数据是与某条具体 RPC 服务有关,比如接口名、版本号分组以及方法定义等。
③ RPC 相关配置数据:有些配置用于控制 RPC 调用行为,有些用于同步 Provider 端进程实例的状态到消费端。典型的配置有超时时间、序列化编码的方式。
④ 业务自定义元数据:主要用于区分以上框架预定义的各项配置,为用户提供更大灵活性。用户可以进行任意扩展,将状态同步到消费端。
结合上面对 Dubbo2 接口及地址模型的分析,可以得出 Dubbo 服务治理易用性的秘密:首先,在接口级服务发现模型,地址发现聚合 Key 等于 RPC 粒度的服务,比如在 Java 中定义 Dubbo 服务接口;其次,注册中心同步的数据不只包含地址,还包含各种元数据以及配置;最后,得益于前面两点,Dubbo 可支持各种不同粒度的服务治理,比如应用粒度、RPC 服务粒度以及方法粒度。
Dubbo 在地址通知过程中同步的数据非常丰富,这也是一直以来 Dubbo2 在易用性、服务治理、功能性、可扩展性方面强于很多其他框架的核心原因。
然而,Dubbo2 地址模型虽然带来了易用性和强大的功能,但在实践过程中也逐渐发现架构在水平可扩展性上出现了限制。这个问题在普通微服务集群下通常感知不到,当集群规模逐渐增长,集群内应用和机器达到一定数量时,集群内的组件就会开始出现规模瓶颈。主要存在以下两个突出问题,如上图橙色所示:
由于所有 URL 地址数据都被发送到注册中心,注册中心存储容量容易达到上限,推送效率也会下降;消费端侧,Dubbo2 框架常驻内存通常已经占用超过业务进程的 40%,每次地址推送带来动态 CPU 资源消耗或资源波动也非常明显,甚至会影响正常业务调用。
通过DubboProviderr(蓝色部分)实例来分析以上问题出现的原因。假设有一个普通的 DubboProvider 应用,应用内部有 10 个 RPC Service ,Dubbo服务应用被部署在 100 个机器实例之上。那么,应用在集群中产生的数据量微10*100,即 1000 条 URL 数据。
数据将从两个维度被放大。从地址角度,有 100 个机器实例,100 条唯一地址数据被放大 10 倍;从服务角度,因为服务元数据在应用内都是唯一元数据,被地址数放大 100 倍。
面对上述问题,在 Dubbo3 架构下,我们不得不深入思考如何在保留应用性和功能性的同时,重新组织 URL 地址数据,避免冗余数据的出现,使 Dubbo3 能够支撑更大规模的水平扩容;其次,还需解决如何在地址发现层面与其他微服务体系打通。
在地址发现链路上,接口级聚合元素Key是 RPC 服务。在应用级发现中,设计思路是将聚合 Key 由服务调整为应用。另外,通过对注册中心数据的大幅精简,只保留最核心IP 和 port,使应用级发现链路传输地址的数据量得到大幅度精简。
上图为升级后应用级服务发现内部的数据结构。与之前接口级地址发现模型的不同主要在于图中橙色部分。Provider 实例侧:相比于此前每个 RPC Service 都注册一条地址数据,升级后的 Provider 部署机器实例只注册一条地址到注册中心。注册中心侧:地址开始以应用名为粒度进行聚合,应用名节点下是精简后的 Provider 及实例,新生成的 URL 已经只包含 IP 和 port。
应用级发现经过上述调整,实现了地址单条数据大小和地址总数量下降,但也带来了新挑战——基本损失了此前 Dubbo2 强调的易用性和功能性,因为元数据传输被精简掉,如何精细地控制单个服务行为变得无法实现。
针对上述问题,Dubbo3 的解法是引入内置元数据服务 MetadataService 。在引入元数据服务后,此前由注册中心从 Provider 同步元数据的行为转变为点对点的拉取行为,消费端在收到纯粹的 IP 和 port 地址通知后,会通过 MetadataService 找到对应的 Provider 进行点对点元数据读取。
此模式下,元数据传输的数据量不再是问题,甚至于可以在元数据服务或元数据内容之上扩展出更多参数,暴露出更多服务治理。
上图为 Dubbo 应用级服务发现的基本工作原理,重点看 Consumer 侧的地址订阅行为。
消费端分两步来完成地址读取以及组装工作。首先,从注册中心收到精简后的 IP 和 port 地址后,通过调用 MetadataService元数据读取到对端的元数据信息。收到这两份数据后,再由消费端完成地址数据聚合,最终在运行态还原出类似Dubbo2 的 URL 地址格式。从运行态的最终结果而言,应用级地址模型同时兼顾了传输层面性能与运行层面的功能性。
三、饿了么 Dubbo3 升级过程
上图为饿了么基本部署架构图。
升级之前,饿了么微服务框架采用HSF2,跨单元 RPC 调用通过中间的Proxy中心化部署代理完成中转。过程中 Proxy 所承载的机器数和流量会有迅速增长。与应用级服务发现的地址发现模型相关,比较突出的一点是 Proxy 需要订阅两侧所有地址数据。因此在存储和数据聚合方面,资源消耗和稳定性都受到了严重挑战。
因此,我们期望通过升级 Dubbo3 结合整个架构升级来解决通过 Proxy 集中式流量调度以及 Proxy 面临的地址存储和推送压力。主要期望实现两个目标:
第一,将地址模型切换到应用级服务发现地址模型,从而大幅度减轻中心化节点和消费端节点的资源消耗压力;
第二,以应用级服务发现架构下的全局共享注册中心取代 Proxy 需要订阅两侧不同的注册中心模型,实现跨单元节点的通信直连。
Dubbo3 对此前的 Dubbo 2 和 HSF2 做了全面 API 兼容,因此,饿了么升级到 Dubbo3 可以实现零改造升级,并且每个应用都可以进行独立升级,不需要关心上下游应用的升级状态。 Dubbo3 升级后,不论从地址发现模型还是协议默认行为,都保持与 2.0 版本兼容,用户可以在任意时间点对任意应用按需切换。
上图右侧模拟展示了饿了么集群在 Dubbo3 升级过程的中间状态,其中灰色代表没有升级的 HSF2 老应用,橙色和绿色代表已经升级到 Dubbo3 的应用,其中橙色表示已完成迁移,绿色代表已升级但未迁移。
上图升级过程可以说明两点:Dubbo3 框架升级在 API 和默认行为方面是完全兼容的;另外,集群内应用以及升级到Dubbo3 以后的地址行为和切换行为是完全独立的。
橙色部分往发现模型迁移时具体操作如何?
首先看 Provider 端行为。Provider 在升级 Dubbo3 以后会默认保持双注册行为,即同时注册接口级地址和应用级地址。一方面是为了保持兼容性,另一方面为未来消费端的迁移做好准备。
双注册行为对于注册中心带来的额外压力,在应用级服务发现模型下并不是问题。每增加一条应用及服务发现的 URL 地址,只会带来 0.1% - 1% 的额外开销。
Consumer 端与 Provider 端类似,要实现平滑迁移,消费端地址模型不可避免地要做双订阅过程。
消费端双订阅行为可以通过规则和开关进行动态调整,控制消费端消费的某个服务或某个应用独立迁移到应用地址模型。除此之外,Dubbo3 还内置了自动决策机制并默认开启,在发现应用级地址可用的情况下,即可自动完成从接口级地址到应用级地址的切换。
Dubbo 的基本工作原理比较简单,其核心是围绕提供者、消费者和注册中心三个节点的数据同步。饿了么在升级 double3 应用级发现模型后,成功实现了去 Proxy 的总体架构目标。
Dubbo3 可以实现透明兼容升级,并且能够按需切换地址模型行为。Dubbo3在饿了么的升级,代表着阿里巴巴内部真正意义上实现了对于 HSF2的替换,并且迁移到新的应用级地址服务发现模型。