Go 语言体系下的微服务框架选型: Dubbo-go

社区总结和展望

原文链接:《Go 语言体系下的微服务框架选型: Dubbo-go 》

导读:本文介绍了Go 微服务体系发展与选型,过去一年优雅上下线以及对未来的展望。

作者简介: 牛学蔚(GitHub: @justxuewei):Apache Dubbo PMC,对云原生、中间件、容器等领域有浓厚兴趣,活跃在 Dubbo 和 Kata containers 两个开源项目中。

Go 微服务体系发展与选型

随着微服务技术的快速发展,其在各个领域都形成了一系列事实标准,在 Kubernetes 和容器技术加持下,云原生微服务已经成为了主流解决方案。而 Go 语言作为云原生领域最受欢迎的开发语言,正被越来越多的企业作为微服务开发的首选语言,其中比较流行的包括 Go-micro、Go-zero、Dubbo-go 等。作为 Dubbo 微服务体系中多语言实现的一员,在 2022 年 Dubbo-go 以微服务领跑者的角色积极拥抱云原生标准,探索了 Proxyless Mesh 形态,配合适配 Pixiu 云原生网关,形成了完善的 Dubbo-go 微服务生态矩阵。

以 Dubbo-go 为中心的微服务体系在多个知名企业中成功落地和实践,框架的稳定性在实际场景下经受住了考验。截止今年已有 60+ 家企业在我们的用户列表中登记,其中较为典型案例请参考文章《小米电商 Apache Dubbo-go 微服务实践》。小米电商选用了 Dubbo-go + Nacos + sidecar + etcd + mirpc 为核心的微服务体系,除了看中了 Dubbo-go 的互联互通和服务治理能力外,也认可 Dubbo-go 在微服务方向的沉淀和积累。

Dubbo-go 简介

什么是 Dubbo-go

Apache Dubbo 是一款易用、高性能的 WEB 和 RPC 框架,同时为构建企业级微服务提供服务发现、流量治理、可观测、认证鉴权等能力、工具与最佳实践。Dubbo3 从设计上不绑定编程语言,社区目前提供了 Java、Go、Rust、Node.js 等多语言实现,在未来,我们计划为所有主流语言提供对等的微服务开发体验。 Dubbo 框架作为国内最具影响力的开源微服务开发框架之一,拥有非常高的关注度和活跃度,在 GitHub 上拥有 3.8 万+ stars。Dubbo 项目于 2017 年捐赠给 Apache 基金会,在经历了短短 15 个月孵化后顺利毕业,在 Apache 基金会管理的全部项目中关注度排名第三(前两名分别是 echarts 和 superset),Dubbo-go 作为 Dubbo 多语言生态的重要一员,很好的兼容 Dubbo 生态的同时提供面向 Go 语言体系的微服务开发体验。

Dubbo-go(项目地址 github.com/apache/dubbo-go)作为 Dubbo 多语言生态的重要组成部分,目前完全兑现了 Dubbo3 架构的核心能力,并且在云原生时代,凭借 Go 语言无需重量级虚拟机、静态编译以及垃圾回收的特性,获得了广泛关注,其应用规模也逐渐扩大。从特性上来说,Dubbo-go 目前支持 HTTP/2、TCP、gRPC 协议通信、服务发现、流量管控、配置管理、全链路追、可视化观测等诸多新特性,Dubbo3 已是众多用户生产环境首选的微服务框架(用户列表);在生态建设方面,Dubbo-go 适配了包括 Zookeeper、Nacos、Sentinel、Zipkin、Kubernetes、Prometheus、云原生 API 网关项目 Dubbo-pixiu、异步网络库 Dubbo-getty、Hessian2 等生态项目。

2022 年 Dubbo-go 社区以生态互联、开发者体验、稳定性为切入点,不断优化系统架构,社区荣获多个开源奖项:

  • Dubbo 生态被评为 2021 年中国 20 大最活跃社区之一
  • Dubbo-go 入围 2021 年“科创中国”榜单。
  • Dubbo-go 开源社区被 OSCHINA 评为“2022 年度 OSCHINA 优秀开源技术团队”。

重要特性

通信协议:遵循 Dubbo 核心架构设计,Dubbo-go 在实现上不绑定通信协议,目前支持 HTTP/2、TCP (Dubbo2)、JSONRPC、gRPC、HTTP 等多种通信协议,开发者可以根据使用场景灵活的选择通信协议。

服务注册:支持 Client-based 服务发现机制,支持注册中心适配如 Nacos、Consul、Zookeeper 等。Dubbo3 的服务发现机制诞生于阿里巴巴超大规模微服务电商集群实践场景,其在性能、可伸缩性、易用性等方面的表现大幅领先于业界大多数主流开源产品。

配置中心:Dubbo 配置中心可实现应用配置的远程托管,支持配置变更的实时感知,目前支持 Nacos、Apollo(携程开源)、ZooKeeper 等作为配置中心。

负载均衡:Dubbo 提供了多种负载均衡策略,如随机负载均衡策略、一致性哈希负载、基于权重的轮询、最小活跃度优先、自适应负载均衡 P2C 等。

流量控制:Dubbo 的流量管控规则可以基于应用、服务、方法、参数等粒度精准的控制流量走向,基于此可灵活的实现超时时间调整、开启访问日志、金丝雀发布、参数路由、同区域优先、按比例流量分发等。除此之外,通过接入 Hystrix、Sentinel 等,Dubbo-go 还支持自适应限流、限流熔断等。

分布式事务:支持 Seata-golang 分布式事务框架,实现了 AT 模式和 TCC 模式分布式事务的调用,AT 模式相较 TCC 模式对代码的入侵性更小、需要开发的接口更少,但 AT 模式对事务操作的数据持有全局锁,TCC 模型性能更好。

链路追踪:支持基于 Jaeger、ZipKin 的链路追踪能力。

指标可视化:支持使用 Prometheus 收集框架指标和用户指标。

可扩展性:Dubbo-go 提供了灵活的 extension 扩展机制,用户可随时根据自己的需求灵活扩展服务发现、负载均衡、配置中心、流量管控规则、全链路追踪等中间件。

过去一年我们做了什么

优雅上下线

在微服务场景下,业务是以容器的形式对外提供服务,k8s 能够方便的对 Pod 进行滚动升级,在旧版本被替换的时候应该达到无损下线的效果,即容器不能被销毁直到没有正在处理的请求。如果其不能被正确实现,对于承载高流量的在线服务来说,在更新期间可能会导致大量的请求报错,甚至可能触发报警,其影响是巨大的。优雅上下线功能是 Dubbo-go 3.0 正式版本发布后的第一个重大增强,王晓伟同学(GitHub: @XiaoWeiKIN)贡献了全流程的优雅上下线能力。

Dubbo 经典的调用流程如上图所示,这里面包含了服务提供者(Provider)、服务消费者(Consumer)以及注册中心(Registry)三个关键组件,一个服务能够被调用,首先需要提供者准备服务并对外暴露端口(步骤 0),然后提供者需要将调用信息注册到注册中心中(步骤 1),消费者则会通过异步订阅的方式获取最新的提供者数据(步骤 2),注册中心在有新数据后会主动推送给消费者(步骤 3),此时消费者已经有本次调用的全部信息了,最后消费者发送调用请求(步骤 4),这样就完成了整个调用链路。

在单体应用中,上述逻辑非常清晰和简单,但是在大规模微服务集群中,这个逻辑的每一个细节都需要被仔细推敲后,才能保证上下线的过程中调用不出错。

优雅上线的目标是解决服务上线调用报错的问题,主要针对微服务场景下的调用依赖问题。在 Dubbo 生态中,Service 表示一个服务,能够被暴露并被其他服务调用,Reference 表示引用,可以简单的理解为下游服务。一个典型调用结构如上图所示,该服务对外暴露一个接口,同时引用了下游的 N 个服务。该服务在上线的时候应该严格遵循以下流程,首先保证下游服务的引用被成功初始化,之后再初始化 Service 对外暴露服务,最后再向注册中心注册服务。优雅上线相对来说逻辑比较简单,只需要严格遵循初始化过程的依赖关系就能保证上线过程中服务能够被正常调用。

优雅下线是优雅上下线的难点,涉及到了信号监听、反注册、等待已有请求完成调用等逻辑。在需要销毁容器的时候,kubelet 会向容器发送 SIGTERM 信号,Dubbo-go 会进入优雅下线流程,此时容器并不会立刻被销毁。即将下线的提供者首先会执行反注册,即向注册中心中删除自己的信息,消费者可以通过订阅获得这个信息,这个过程需要一定的时间。换句话说,在消费者获得这个删除信息之前,流量还是有可能会流向该提供者,此时提供者应该拒绝这部分请求。当然除了下线期间的新请求外,还有残留的来自上游的请求以及自己调用下游的请求,我们分别为这两种情况设置一个计数器,当两个计数器都被清零时,可以认为该提供者是“干净”的。Dubbo-go 的策略是拒绝新请求,等待已放行的旧请求。最后,销毁协议并关闭监听,该容器就能够被安全的摘除。

在启动优雅上下线后,集群内无错误请求,成功率保持在100%。

新一代柔性服务

在去年发布 Dubbo-go 3.0 版本的时候,柔性服务首次作为一个重要特性被提出。时隔一年,我们带来了全新升级的新一代柔性服务,在新版本中我们将爬山算法替换为峰值干预算法,在经过多次测试后新算法行为可控性更高、性能更优异,这部分工作由来自北京邮电大学的张业鹏同学(GitHub: @CoolIceV)贡献。

柔性服务是一种更智能的负载均衡算法。传统负载均衡算法大多是基于消费者视角,它们共同的局限性是无法根据服务提供者的当前状态动态调整分流策略,如 RR、hash 等算法。这些算法总是以尽可能公平的概率分配流量,但在实践中公平不等于负载均衡。

爬山算法是一种容量预估的算法,服务提供者需要将一些关键信息回传给消费者,比如时延、请求排队数量、预估容量等,消费者使用 P2C 算法选择一个负载最低的作为本次请求的提供者。这些数据实效性要求非常高,如果这些数据是被动传递的,那么很难保证实效性,如果这些数据是被主动探测的,那么在一个大型集群下感知成本非常高。基于上述问题,我们选择了更可控的峰值干预算法。

消费者部分中,我们使用了改良版的 P2C 算法,采集的指标包括请求数(requests)、成功数(accepts)、请求时延(rtt)。与原实现方案不同的是,该版本采用了更合理的滑动窗口(SlidingWindowCounter)和指数移动平均(EMA)两种带有时序性的模块进行采集。 SlidingWindowCounter 会保存时长为统计周期 T 的数据,整个周期内的数据被分割为若干个 Bucket,每个Bucket 保存计数时长内的数据,当前所处的 Bucket 会随着时间前进而向后移动。

EMA 利用指数移动平均算法进行平滑、减小抖动,适用于统计时延型的指标,计算公式:

以下为一个客户端请求 3 个服务端的测试结果,3 个服务端配置不同,分别为 1 核 1GB、2 核 2GB、3 核 3GB。兰青色虚线代表开始使用上述负载均衡算法,可以看到开启前每个服务端接收到的请求数几乎相同,开启之后流量会根据提供者的规格进行智能分流。

提供者基于一个 AutoConcurrencyLimiter 组件限流,在请求到达时会判断已接受的请求是否超过最大处理量,如果超过了就会直接返回失败,限流导致的失败会影响负载均衡时的成功率,进而影响该实例被请求的可能性。与常规限流组件不同的是,该组件会根据采样情况自动调整服务的最大处理量,不需要手动配置,而且增加了 CPU 负载作为启动开关,可以减少被错误限流的数量。

该组件主要关注 QPS、无负载时延(NoLoadLatency)和最大并发量(maxConcurrency),同时有一个用户指定的超参数 exploreRatio,表示探索最大并发量的程度。更新规则是

  • 使用总请求数(包含未被采样的数据)计算 QPS,新 QPS 更大时直接替换,更小则使用指数移动平均进行平滑处理。
  • 使用采样数据计算平均处理时延来估算 NoLoadLatency,平均时延变小才会更新,并使用指数移动平均进行平滑处理。
  • 增加探索因子启发式计算 maxConcurrency, 在采样时延或 QPS 在探索允许的范围之内时会逐步增大 exploreRatio,否则用指数移动平均的方式进行平滑处理

最后可以提供给用户一个可以通过 cgroup v1 进行 CPU 限制,当当前提供者 CPU 负载过高的时候,会无条件拒绝一切新请求。CPU 限制是一种最坏情况下的兜底策略。

以下为 1 个客户端请求一个服务端的测试结果,该测试随着时间推移,QPS 会逐步增大,如蓝线所示。可以看到当CPU 负载(橙线)过高时,有请求被限流(黄线),随后即使 QPS 再增大,CPU 负载、请求成功数均已相对稳定。

Dubbo Mesh

今年 Dubbo Go 社区发布了 Dubbo Mesh [1] 架构的完整实现,能够以 Proxyless Mesh 的形式加入 Istio 服务网格,开启了 Go 语言体系下的微服务新形态。

Istio 在架构层面分为控制平面(control plane)和数据平面(data plane),其中控制平面是一个名为 istiod 的进程,网络代理是 envoy 。Istiod 简体 Kubernetes 资源(resources)获取服务信息,比如 Service、Endpoint 等,将这些信息通过 xDS 协议发送给位于数据平面的 envoy。Envoy 作为一个独立代理进程以边车(sidecar)形式运行,该进程与业务进程共同加入同一个网络,劫持业务流量并转发到正确的位置。

服务网格能够屏蔽复杂的服务治理细节,让开发者能够专注于业务实现。Istio 通过边车的形式实现了业务逻辑的无侵入性,降低了系统之间的耦合性,带来开发便利的同时也引入了转发时延、额外资源消耗的问题。但是 Istio 作为云原生时代的标杆产品,其架构模式和思路就有非常大的借鉴意义,针对上述提到的 Proxy Mesh 的弊端,我们提出了一套基于 Dubbo-go 的 Proxyless Mesh 微服务治理模式。

Proxyless Mesh 是无代理服务网格,由 Google 提出后,多个开源产品在这个方向进行了探索和实践。其核心思路是用 SDK 代替独立代理进程,SDK 作为数据平面接收来自控制平面的控制信息,负责服务之间的通信和治理工作。

Dubbo-go 为了融入 Istio 体系,将扩展出来的注册发现流程进行了特殊改造。除了复用 Istio 提供的 EDS、CDS 主机发现的能力之外,增加了接口名到主机名的映射,作为源数据注册在了控制平面上。客户端在发起调用前持有接口名,通过查询 istiod 上的元数据信息,拿到接口名到主机名到映射,转换为主机名;再通过 EDS、CDS 和路由,完成主机名到下游端点实例的转换。完成服务发现流程。

互联互通的新典范:Polaris 和 Dubbo-go 全面对接

Dubbo-go 从发布伊始,一直非常重视与各个开源产品之间的互联互通,今年我们完成了与 Polaris 全面对接,这部分工作由社区邓正威同学(GitHub: @jasondeng1997)和春少同学(GitHub: @chuntaojun)贡献。

Polaris 是一款开源的服务治理平台,致力于解决分布式和微服务架构中的服务管理、流量管理、配置管理、故障容错和可观测性问题,针对不同的技术栈和环境提供服务治理的标准方案和最佳实践。在经典 Dubbo-go 使用场景下,用户需要自行部署注册中心、可观测服务、流量管理等组件,而 Polaris 内置了服务管理、流量管理、故障容错、配置管理和可观测性五大功能,能够帮助用户快速降低微服务开发门槛。

Polaris 有统一的控制平面,需要为 Dubbo-go 框架适配相应的数据平面 SDK,用户只需要在 Dubbo-go 中开启 Polaris,框架将自动通过 extension 机制进行注入,开发者无需其他额外的开发工作。从用户数据流的维度,当用户在 dubbogo 中启用 Polaris 的服务治理能力后,业务流量实际处理流程如下:

当前 Polaris 已实现了 Dubbo-go 原生的服务注册扩展点,因此原本服务注册逻辑不需要进行任何调整,只需要在 dubbogo.yaml 配置文件中新增 polaris 协议的注册中心配置即可,如下所示。

dubbo:
registries:
demo:
protocol: polaris
address: {polaris-ip}:8091

其中 polaris-ip 表示北极星服务端 IP 地址,此时 Dubbo-go 框架就接入了 Polaris 服务治理框架。Polaris 还提供了流量管理、故障容错等诸多内容,碍于篇幅限制这里就不一一展开了,如果有兴趣请参阅 《互联互通的新典范:Polaris 和 Dubbo-go 全面对接》[2]。

TLS 安全通信支持

在今年我们为 Dubbo 协议、Triple 协议和 gRPC 协议实现了 TLS 安全通信功能,微服务之间能够以可信的方式调用,该部分由社区张立斌同学(GitHub: @ZLBer)贡献。

TLS 的前身是 SSL,被用于通信加密,能够保证传输内容不被其他主机查看和篡改,已经被广泛的应用于 HTTPS 等技术中。在开启 TLS 之前,需要生成所需要的证书和秘钥,我们假设其保存在 x509 目录中。启用 TLS 不需要对业务逻辑进行修改,只需要设置相应的 Dubbo-go 配置文件 dubbogo.yaml 即可。

消费者配置文件:

dubbo:
consumer:
references:
UserProvider:
url: tri://localhost:20000
protocol: tri
serialization: json
interface: com.apache.dubbogo.samples.rpc.extension.UserProvider
tls_config:
ca-cert-file: x509/server_ca_cert.pem
tls-cert-file: x509/client2_cert.pem
tls-key-file: x509/client2_key.pem
tls-server-name: dubbogo.test.example.com

提供者配置文件:

dubbo:
protocols:
triple:
name: tri
port: 20000
provider:
services:
UserProvider:
serialization: json
interface: com.apache.dubbogo.samples.rpc.extension.UserProvider
tls_config:
ca-cert-file: x509/client_ca_cert.pem
tls-cert-file: x509/server2_cert.pem
tls-key-file: x509/server2_key.pem
tls-server-name: dubbogo.test.example.com

在正确开启 TLS 之后,提供者会输出一条 "Server initialized the TLSConfig configuration" 日志,消费者会输出一条 "Client initialized the TLSConfig configuration" 日志。更详细的使用实例请参考 tls 示例 [3]。

展望 2023

2022 年是极不容易的一年,感谢大家的信任,也非常感谢社区中每位同学的贡献。展望 2023 我们将会持续打磨框架,在优先保障稳定性的前提下持续提升易用性,打造一流的 Go 语言微服务框架。

Dubbo 开源整体规划

  • 官网与文档体验全面提升
  • Go、Node.js、Rust 等多语言体系建设
  • 全面提升整体可观测性
  • Dubbo Admin 一站式服务运维管控平台
  • Dubbo Mesh 走向成熟
  • 提升 HTTP 开发体验,补全 Web 互通
  • 打造 gRPC over Dubbo 最佳实践
  • 完善的认证鉴权体系

面向 Go 开发者全面使用体验提升

Dubbo-go 基础功能建设已经较为完善,同时在最近几年中我们持续在 dubbo-go-samples 目录完善示例代码 [4],能够帮助用户更加快速的上手项目。与此同时,Dubbo-go 文档内容还较为匮乏,代码示例只能让框架跑起来,但是这里面的使用和实现细节还需要在文档中进一步被详细阐述,让用户知其然,更知其所以然。除了文档建设工作外,社区对开发者工具的建设工作高度重视,在 2023 年,我们计划在 dubboctl 工具中集成更多命令,为用户提供快速创建微服务、支持了 Hessian2 生成器、新建 Dubbo-go 项目等诸多特性。在 2023 年我们将会进一步提升文档和工具的建设工作,除了强大的功能之外,成为真正面向 Go 开发者的轻量、易用的框架。

参考链接

  1. Dubbo-go-Mesh 开启新一代 Go 微服务形态
  2. https://baijiahao.baidu.com/s?id=1751764790109427167&wfr=spider&for=pc
  3. https://github.com/apache/dubbo-go-samples/tree/master/tls
  4. https://github.com/apache/dubbo-go-samples
All rights reserved
Except where otherwise noted, content on this page is copyrighted.