1. Go 应用微服务治理简介
Go 语言具有简洁、高效、并发性强等特性,已经被广泛认为是构建微服务的理想选择之一。Go 语言作为构建 Kubernetes、Docker 的主要编程语言,目前不仅在云原生基础组件领域中被广泛使用,也逐渐被越来越多的开发者应用于各类业务场景中,基于微服务架构构建业务应用。
微服务架构通过模块化体系结构,提高了系统的灵活性、敏捷性和扩展性,缩短了团队开发周期、增加了资源利用效率,这也是越来越多的公司选择采用微服务架构的主要原因之一。然而,随着业务不断发展与持续迭代,微服务带来的系统复杂性也使得运维管理难度逐渐增加,从而影响开发效率和系统稳定性。
为了保证系统稳定性,我们在享受微服务带来的优势的同时,也不得不持续解决微服务带来的问题和风险。例如:
- 在开发测试态,敏捷的迭代需要提供与线上完全隔离的环境用于研发和调试、业务发展带来的服务增长需要有统一的面板来管理和观测。
- 在变更态,为了防止新引入的 bug 给业务带来的损失,需要具备灰度发布、快速回滚的能力。
- 在运行态,为了避免不确定流量、不稳定调用和基础设施给业务带来的稳定性问题,需要具备限流、熔断、降级等能力,来规避运行时风险。
目前,在 Go 语言生态中,主流的微服务框架主要专注于解决如何快速构建微服务应用、以及微服务间通信问题,在微服务治理能力上仍有欠缺;主流的微服务治理组件,如 Sentinel-Golang、OpenTelemetry 等,虽然较好的解决了流量防护、应用可观测等方面的微服务治理问题,但需要开发手动在业务代码中通过 SDK 进行埋点,无法专注于业务逻辑的实现,这无疑在一定程度上降低了开发效率。
为了更好的进行 Go 应用微服务治理,提高研发效率和系统稳定性,本文将介绍 MSE 微服务治理方案,无需修改业务代码,即可实现上述治理能力。
2. 原理说明
不同于 JAVA,Golang 作为静态编译型语言,它在编译时直接将源代码转换为机器码,不依赖于虚拟机,也不具有字节码。这种方式虽然无法屏蔽底层操作系统,但是由于可以直接在硬件上运行,会具有更高的性能。
我们通过 Go Build 工具原生提供的 -toolexec 机制,在编译期进行代码劫持,对特定 Go SDK 进行埋点注入来实现代码增强(如框架 SDK、Go 内置 runtime、net/http 包等),从而使微服务应用具备了治理能力。
- Compile Front:在编译过程中,我们通过 dry run 机制和抽象语法树解析每个待编译 .go 文件,通过 module + path + receive type name + function name 可以唯一识别一个方法,从而能够判断出该方法是否需要进行代码增强和埋点注入。
- Code Inject:判断出当前方法需要进行注入时,会在对该文件进行编译之前,通过修改语法树,插入预定义好的代码,随后编译生成 .a 文件。
- Compile Backend:编译器继续执行原有编译流程,最终生成二进制可执行文件,里面包含了服务治理逻辑。
编译时注入框架现已开源,欢迎参与社区讨论和贡献,详情请点击 opentelemetry-go-auto-instrumentation[1]。
3. 接入并治理 Go 应用
为了更直观的展示如何进行 Go 应用的微服务治理,接下来我们将通过一个 Demo,演示 Go 应用从接入到使用微服务治理并生效的全过程。
3.1 Demo 服务说明
Demo 服务部署在阿里云 ACK 集群中,调用顺序为网关->A->B->C。其中网关采用阿里云 MSE 云原生网关,应用间调用方式为 http、服务发现方式采用 K8s 标准的 CoreDNS,应用 A、C 各部署了一个灰度版本,用于某需求迭代发布过程中的灰度验证。
Demo 服务的实现源代码请详见 mse-go-demo/multiframe[2]。
应用名称 | 语言及版本 | 微服务框架及版本 | Client 调用方式 | 服务发现方式 |
A | go 1.20 | gin 1.8.1 | http | CoreDNS |
B | go 1.19 | kratos 2.7.1 | http | CoreDNS |
C | go 1.19 | go-zero 1.5.1 | / | CoreDNS |
3.2 接入 MSE 服务治理中心
一个 Go 应用接入 MSE 服务治理中心,只需执行以下四步即可,其中步骤 1、2 只需首次接入时执行,后续无需再操作。
1. 在 MSE 服务治理中心中,为 ACK 集群一键安装 ACK-Onepilot 组件
2. 在 MSE 服务治理中心中,为 ACK 集群一键开启高阶治理能力
3. 下载并使用我们提供的 Instgo 工具编译 Go 应用,来代替 go build 命令,生成二进制可执行文件
# 生成当前操作系统可执行文件 ./instgo build --mse --licenseKey="{licenseKey}" # 交叉编译,例如在MacOS中生成linux操作系统可执行文件 #amd CGO_ENABLED=0 GOOS=linux GOARCH=amd64 ./instgo build --mse --licenseKey="{licenseKey}" #arm CGO_ENABLED=0 GOOS=linux GOARCH=arm64 ./instgo build --mse --licenseKey="{licenseKey}"
4. 将应用打包成镜像之后,部署到集群之前,在相应的 Deployment YAML 文件中 spec.template.metadata.labels 中添加以下标签后,部署应用即可完成接入
spec: template: metadata: labels: # required msePilotAutoEnable: "on" #标识开启MSE微服务治理 mseNamespace: your-namespace #标识应用所属微服务治理空间 msePilotCreateAppName: "your-app-name" #标识应用名称 aliyun.com/app-language: golang #标识为Golang应用 # optional alicloud.service.tag: pod-tag #在全链路灰度中,用于标识灰度节点,如gray、blue...
可以看到,整个接入过程中不会涉及到业务代码的修改,相比自主魔改框架、手动引入 SDK 埋点等方式,更加清爽和简洁。
关于上述各步骤更详细的指引和说明请详见 ACK 微服务应用接入 MSE 治理中心(Golang 版)[3]。
3.3 使用服务治理能力
按照 3.2 所述步骤完成应用接入后,即可在 MSE 治理中心控制台中看到具体的应用及服务信息,并且进行相应的服务治理规则配置。本节将从应用观测与管理、流量治理、全链路灰度三个常见治理场景,演示操作过程与实际效果。
应用观测与管理
将 3.1 中所述 Demo 的 A、B、C 三个 Go 应用接入到 MSE 治理中心,并设置:
- mseNamespace=mse-go-agent-demo
- 应用 A 的 msePilotCreateAppName=go-gin-demo-a
- 应用 B 的 msePilotCreateAppName=go-kratos-demo-b
- 应用 C 的 msePilotCreateAppName=go-zero-demo-c
接入完成后,可以在 MSE 治理中心 mse-go-agent-demo namespace 下看到对应的应用详情。
服务信息查看
进入 MSE 服务治理中心,点击服务查询,可以查看服务信息,包括应用内创建的 http 或 rpc 服务,以及对应服务的元信息,如接口元信息、服务元信息等。
在左上角分别选择 Gin、Kratos 和 Go-zero 框架,可以分别看到 A、B、C 应用的服务信息。由于我们在编码实现时,分别为应用 B 和 C 各创建了一个 Http Server、一个 Grpc Server,用于接收不同类型的请求,因此可以在控制台中看到两个服务。
点击相应的服务,能够看到服务的详细元信息,以应用 B 为例,以下为 B 应用的 Http 服务和 Grpc 服务信息。
应用信息查看
进入 MSE 服务治理中心,点击应用治理,能够从应用、接口、节点等不同维度查看应用的运行数据以及系统数据。
在应用列表处,可以看到不同应用的节点数、标签数、请求数、QPS 等数据。
点击对应应用卡片,即可从不同维度查看应用更详细的信息,包括应用整体数据、接口数据、节点数据等。
1)应用概览数据
2)接口数据
3)节点数据
流量治理
目前,我们提供了以下流量治理能力,支持用户自主配置并应对不同的场景,同时支持通过控制台配置规则快速启停:
- 接口流控:设置单接口最大 QPS,超过阈值的请求将会被拒绝或进入等待队列。
- 并发隔离:设置单接口的最大并发协程数,超过阈值的请求将会被拒绝。
- 熔断:设置接口的熔断防护规则,阈值可以设置为慢调用比例或者失败率,达到阈值后会触发熔断,在熔断时长内,该接口的请求都会快速失败,熔断状态结束后通过单次探测或渐进式策略恢复。
- 热点参数防护:相比接口流控,防护的规则精细到参数级别,例如可以设置某接口第 N 个参数占用最大并发资源数不超过 10,如果超过阈值则对应的请求会快速失败。
- 行为降级:触发接口流控后,可以自定义防护行为,例如返回指定状态码和内容、重定向等。
- 自适应过载保护:以 CPU 使用率作为衡量实例负载的依据,自适应地调整对入口流量的防护策略,避免因 CPU 资源打满导致服务崩溃。
规则配置
以应用 A 为例,假设我们希望为接口 /greet1a 设置单机 QPS 阈值为 1 的接口流控规则,如果达到阈值之后立即失败,并且返回 429 作为 Http 状态码,返回内容为"Too Many Request! The Server A will not process!"。
为了达到以上效果,可以直接在 MSE 治理中心配置如下:
1. 点击应用治理,点击 go-gin-demo-a 应用卡片,点击流量治理
2. 点击流量防护-行为管理,点击新增行为,按下图所示配置行为后,点击新建
3. 点击流量防护-接口流控,按下图所示步骤配置流控防护规则,然后点击新增
4. 在规则列表处,点击规则状态置为开启后,规则即生效
结果观测
持续一段时间后,可以看到应用 A 单节点通过的 QPS 稳定在 1,超过阈值的请求已经被拒绝;应用 B 的 QPS 稳定为 2,这是因为应用 A 一共有 2 个节点,最多允许通过的 QPS 为 2。
1)应用 A 单节点 QPS 数据
2)应用 B 整体 QPS 数据
使用云原生网关对应用 A/greet1a 发起调用,可以观察到由于触发了流控,接口返回了我们刚刚定义的错误状态码和内容。
全链路灰度
规则配置
假设在某次需求迭代中,我们变更了应用 A 和应用 C 的部分逻辑,并且希望通过灰度发布的方式测试并验证功能的正确性,希望 Header 内容中 x-user-uuid=123456789 的请求路由到灰度节点,其他请求仍然路由到常规节点。
为了达到以上效果,我们可以按照以下步骤进行操作和配置:
- 为应用 A 和应用 C 部署灰度节点,通过在 YAML 文件中 spec.template.metatada.labels 增加 alicloud.servicetag=gray 标识节点类型。
- 进入 MSE 治理中心,点击全链路灰度,创建泳道组,泳道组主要用于业务模块隔离,例如,同部门下业务小组 A 可以创建并使用泳道组 a,业务小组 B 可以创建并使用泳道组 b,两者相互隔离,互不干扰。
- 在泳道组内创建标签为 gray 的灰度泳道,如下图所示填写泳道信息、配置基线路由和灰度规则。
泳道组及泳道创建完成后,全链路灰度的配置就已经完成,如下图所示,泳道组的流量入口为 MSE 云原生网关,接下来我们通过对应的网关发起请求,Demo 应用中的业务返回结果打印了请求的调用链以及每一跳所路由到的节点标签和 IP,用于方便观察调用链路。
结果观测
1)设置请求 Header 中无 x-user-uuid,请求均路由到基线节点
2)设置请求 Header x-user-uuid=1,请求均路由到基线节点
3)设置请求 Header x-user-uuid=123456789,请求路由到 A、C 的灰度节点,由于应用 B 不存在灰度节点,因此路由到基线节点
由此,我们验证了全链路灰度规则配置已经生效。
本文链接:
[1] opentelemetry-go-auto-instrumentation
[3] ACK 微服务应用接入 MSE 治理中心(Golang 版)
相关链接:
[1] Go 应用治理接入指引
阿里云 MSE Go 应用服务治理现已开启公测,公测期间不收取费用,欢迎接入使用!
作者:赵源筱