vivo 微服务架构实践之 Dubbo 性能优化
在Java技术栈场景vivo主要基于 Apache Dubbo 框架来作为微服务之间的通信桥梁在内部业务的大规模实践过程中我们碰到了质量、性能和容量等方面的挑战通过一系列的扩展与优化较好的解决了相关问题助力业务更好保障质量节省算力成本提升研发效率。1分钟看图掌握核心观点图1 VS 图2您更倾向于哪张图来辅助理解全文呢欢迎在评论区留言。一、Dubbo 在 vivo 的演进历程1.1 vivo 微服务现状vivo自2015年通过微服务架构升级以赋能业务增长通过全网治理于2018年完成了全网Java技术栈RPC框架统一为Dubbo。 目前该架构高效支撑了5亿用户、覆盖60地区的业务体量实现了万级微服务在十万级机器上的稳定运行日均RPC调用量高达8000亿次。1.2 Dubbo在vivo的演进历史Dubbo 是一款 RPC 服务开发框架主要用于解决微服务架构中的通信与服务治理问题。它提供了服务定义服务发现、负载均衡、流量管控等丰富能力。vivo在2015年引入开源社区Dubbo作为Java技术栈RPC框架。而随业务规模发展业务侧浮现框架版本碎片化现象产生治理困难维护成本高等问题。 在19年vivo引入开源社区2.7.* 版本发布作为第一个基线版本对业务侧进行了版本收敛。随后发布两个大基线版本分别为建设三中心分离能力和应用级注册发现能力。1.3 Dubbo执行核心链路概要我们先简要介绍一下Dubbo的整体流程。 流程可分为上下两部分。上半部分呈现了由提供方、消费方、注册中心和元数据中心协同完成的服务注册与引入。 下半部分为调用流程。Dubbo采用微内核与插件化设计内部多个抽象层次。总体而言一次RPC流程可分为两类一是启动即就绪的静态过程如代理生成、服务列表缓存二是每次调用均需动态计算的部分如路由、负载均衡、序列化编解码这些常是性能热点。二、Dubbo 路由扩展及优化2.1 Dubbo路由简介Dubbo路由是一套基于规则的精细化流量治理组件其工作流程由服务治理侧向Dubbo下发路由策略从而确保RPC请求能够被精准的路由至预期的服务实例列表。该机制是支撑灰度发布、机房容灾、环境隔离等流量治理能力的技术基石。 开源版本的Dubbo提供了应用级标签路由、条件路由和脚本路由等核心路由能力。我们在其基础上扩展实现了接口级标签路由与就近路由两种增强机制。2.2 就近路由2.2.1 就近路由背景说明一般情况下同机房内部的网络调用平均时延在0.1ms左右而同城多机房间的平均时延在1ms-5ms跨地域机房之间的网络时延则更大。 假设内部服务存在大量跨机房调用尤其针对rt敏感业务可能因为请求延时的增加影响服务质量用户体验。 因此Dubbo就近路由应运而生其可实现RPC过程优先使用同机房进行调用。 可以看到上图提供方在注册会上报机房信息消费方调用经过就近路由只匹配同机房的提供方节点列表。2.2.2 就近路由场景分析我们的理想方案如上方所示是多机房共享注册中心流量在就近路由的干涉下在同机房内流转。 但此方案面临下方两个问题 存在部分业务单机房部署现象若强制进行同机房调用会造成消费方无可用提供者。 同时存在多机房非均匀部署现象若机房间部署规模差异较大同机房调用可能造成小规模部署机房的业务集群雪崩。2.2.3 就近路由实践为解决刚才的问题我们最终实现如下中间件联合CI/CD侧在提供方服务部署时上报机房信息。消费方调用经过就近路由时会遍历提供方列表优先筛选同机房提供方实例。新增阈值判断当同机房提供方机器规模低于阈值时路由会自动降级进行全量访问。这样可以有效避免单机房部署的无提供者问题 以及降低非均匀部署时的集群雪崩风险。用上边的三个请求举例在就近路由阶段请求1 发现同机房提供者部署规模超过阈值属于安全调用直接过滤出01机房节点。请求2 发现同机房无可用提供者则直接触发降级规则返回全量节点。请求3 在就近路由阶段发现同机房虽有可用提供者部署但规模低于阈值也直接触发降级规则返回全量节点。综上就近路由通过简单的元数据标记和灵活的阈值规则实现了流量的自动优化与隔离。其改造过程对业务代码无侵入并带来延迟降低、网络带宽成本下降、稳定性提升的巨大收益。2.3 标签路由能力说明接下来是标签路由标签路由是一种在微服务架构中用于实现流量精细控制的服务治理策略。其核心思想是通过控制面为服务实例打上自定义标签标签路由根据消费方调用时指定的标签将请求流量路由到匹配这些标签的提供方实例。 在Dubbo语义中Dubbo标签分为动态标签与静态标签如图所示我们用通过配置中心下发动态标签标记gray1包含a节点标记gray2包含c节点用于标识两个灰度环境。 而提供方部署时可以自带静态标签静态标签随Dubbo注册发现流程被消费方在内存缓存。以三个请求举例请求1 指定了gray1标签路由会遍历提供方列表与gray1对应的列表进行交集计算最后过滤a节点。请求2 指定gray3标签路由发现无可用节点则请求会降级到无标签的机器最后过滤b,d,e节点。请求3 未指定标签说明是基线环境调用标签路由会筛选未打标签的机器最后过滤b,d,e节点。2.4 我们发现的性能问题在vivo大规模 Dubbo 提供方集群场景下高峰期该业务消费方侧应用的整体 CPU 利用率约为60%而其中负载均衡模块及路由模块的 CPU 占用率竟超过了30% 通过火焰图分析可以观察到这些问题存在共性 相关方法均涉及遍历操作其时间复杂度与提供方节点数量成正相关。在大规模集群部署环境下路由与负载均衡模块因遍历计算产生了明显的资源消耗 。2.4.1 路由优化实践--减少遍历运算优化思路降低消费方侧遍历次数我们发现部分业务是完全不使用应用级标签路由的而为了支持静态标签场景应用级路由对于不带标签的请求还是需要全量遍历以筛选无静态标签的节点。这部分无效遍历会造成算力空转浪费。因此我们第一个优化是对此类业务关闭了应用级路由。根据火焰图我们了解到在负载均衡中负载均衡器需要全量遍历节点以获取权重。那么这时我们可以试图降低参与负载均衡计算的节点数在负载均衡前我们新增了虚拟分组。当路由筛选后的实例规模超过阈值后虚拟分组模块会将实例列表拆分成多个小规模分组通过对分组随机选择倍数级降低了进入负载均衡的节点数降低了负载均衡遍历次数。2.4.2 路由优化实践1.引入位图缓存由火焰图现象发现无论是就近路由还是标签路由筛选流程以及交集计算流程依然存在大量遍历操作带来算力损耗。首先引入缓存减少遍历。对于标签路由可以对提供者节点做如下分类 带动态标签的节点带静态标签的节点未打标签的节点我们可以提前在建立路由元数据的时候对不同种类节点进行缓存。我们在标签路由内设置了缓存单元对上述三类节点进行了分类缓存。 类似的在就近路由内对不同机房的提供者列表直接进行缓存。 同时我们以位图形式组织了缓存。以图中请求为例全量节点为a,b,c至j10个节点。 在应用级标签路由中共维护四份缓存有gray1gray2静态标签位图无标签位图。类似的接口级维护两份分别为grayA标签位图与无标签位图。最后是就近路由维护机房级别的位图缓存。请求一从loc1机房发起携带应用级标签gray1接口级标签garyA。经历应用标签路由与运算可用列表为a,b,g经过接口级路由与运算依然a,b,g。经过就近路由与运算后只保留ab。由此我们完成了路由执行复杂度从O(n) - O(1的挑战。2.缓存一致性