【GO应用】编译时插桩,Go应用监控的最佳选择

news/2024/12/24 11:25:28/文章来源:https://www.cnblogs.com/o-O-oO/p/18627011

阿里妹导读

本文讲解了阿里云编译器团队和可观测团队为了实现Go应用监控选择编译时插桩的原因,同时还介绍了其他的监控方案以及它们的优缺点。
可观测性是以系统的指标、日志、链路追踪、持续剖析四大数据支柱为基础,从宏观到微观,通过不同数据之间互相关联,衍生出如数据监控、问题分析、系统诊断等一系列的能力。

Java[1]可以通过字节码增强的技术实现无侵入的应用监控(开源社区有非常多的无侵入Agent实现方案,技术非常成熟),可以轻松获取到关键监控数据,相比Java,Go因为语言的特点,应用运行的时候已经被编译成一个二进制文件,无法再做类似Java字节码增强的方式进行动态插桩,在应用监控领域的生态并不完善,可观测的四大数据支柱无法通过无侵入的方式来实现,使得用户的接入成本变高,当前针对Go应用的可观测能力,有3种解决方案:

SDK方案eBPF方案
编译期自动注入方案

以下分别来介绍这几个方案以及对应的开源实现:

一、SDK方案

在可观测领域,随着OpenTracing 被OTel 收编,目前被广泛使用的SDK就是OTel Go SDK[2],通过在业务代码的每个需要的地方进行手动增加埋点,如下所示:

package mainimport ("context""fmt""go.opentelemetry.io/otel""go.opentelemetry.io/otel/attribute""go.opentelemetry.io/otel/sdk/trace""io""net/http"
)func init() {tp := trace.NewTracerProvider()otel.SetTracerProvider(tp)
}func main() {for {tracer := otel.GetTracerProvider().Tracer("")ctx, span := tracer.Start(context.Background(), "Client/User defined span")otel.GetTextMapPropagator()req, err := http.NewRequestWithContext(ctx, "GET", "http://otel-server:9000/http-service1", nil)if err != nil {fmt.Println(err.Error())continue}client := &http.Client{}resp, err := client.Do(req)if err != nil {fmt.Println(err.Error())continue}defer resp.Body.Close()b, err := io.ReadAll(resp.Body)if err != nil {fmt.Println(err.Error())continue}fmt.Println(string(b))span.SetAttributes(attribute.String("client", "client-with-ot"))span.SetAttributes(attribute.Bool("user.defined", true))span.End()}
}

先定义好一个TraceProvider,然后在发起请求的地方获取tracer,使用tracer.Start创建一个span,然后发起请求,在请求结束后使用span.End()。
这是一个简单的http的请求,如果是复杂的业务应用,会涉及多个调用,比如调用redis、mysql、mq、es等中间件,需要在每个调用的地方都进行埋点,同时还需要处理好span Context的传递、baggage的传递,以及及时调用span End。
OTel 的spanContext都是通过context进行传递,如下所示:

func testContext() {tracer := otel.Tracer("app-tracer")opts := append([]trace.SpanStartOption{}, trace.WithSpanKind(trace.SpanKindServer))rootCtx, rootSpan := tracer.Start(context.Background(), getRandomSpanName(), opts...)if !rootSpan.SpanContext().IsValid() {panic("invalid root span")}go func() {opts1 := append([]trace.SpanStartOption{}, trace.WithSpanKind(trace.SpanKindInternal))_, subSpan1 := tracer.Start(rootCtx, getRandomSpanName(), opts1...)defer func() {subSpan1.End()}()}()go func() {opts2 := append([]trace.SpanStartOption{}, trace.WithSpanKind(trace.SpanKindInternal))_, subSpan2 := tracer.Start(rootCtx, getRandomSpanName(), opts2...)defer func() {subSpan2.End()}()}()rootSpan.End()
}

上述的2个新创建的协程里面使用了rootCtx,这样2个协程里面创建的span会是rootSpan的子span,在业务代码中也需要类似的方式进行传递,如果不正确传递context会导致调用链路无法串联在一起,也可能会造成链路错乱。

同时OpenTelemetry Go SDK 目前保持着2周到4周会发布一个版本
https://github.com/open-telemetry/opentelemetry-go/releases,更新速度非常快,经常会有前后不兼容的情况,业务升级OTel Go SDK会导致代码也需要进行修改,成本非常高。

二、eBPF方案

eBPF(扩展的伯克利数据包过滤器)作为Linux内核中的一个高效且灵活的虚拟机,允许开发者自定义运行程序,并通过特定接口将这些程序加载到内核空间执行。这一特性使得eBPF成为了构建各类系统监控解决方案的理想选择之一。

近年来,基于eBPF技术开发的各种开源项目如雨后春笋般涌现出来,其中包括

pixie(https://github.com/pixie-io/pixie)beyla(https://github.com/grafana/beyla)opentelemetry-go-instrumentation(https://github.com/open-telemetry/opentelemetry-go-instrumentation)
deepflow(https://github.com/deepflowio/deepflow)

等知名项目。它们共同致力于利用eBPF的强大能力来实现诸如性能分析(Profiling)、网络流量监测(Network Monitoring)、度量指标收集(Metric Collection)及分布式追踪(Distributed Tracing)等功能。
eBPF可以通过在不同位置的挂载点完成对数据流的抓取,比如tracepoint、kprobe等,也可以使用uprobe针对用户态函数进行hook,以协议解析为例,随着业务复杂度的提升以及不同使用场景的要求,用户态的协议非常多,有RPC类型的http、https、grpc、dubbo等,还有中间件的mysql、redis、es、mq、ck等,要通过eBPF抓取的数据完成数据解析并实现指标的统计难度非常大。
以使用eBPF监控Go应用为例,因其独特的并发模型而广泛采用异步处理机制,若想精确地进行跨协程上下文传递或深入到应用程序内部进行细粒度的跟踪,则通常还需要额外引入SDK来进行辅助支持,完成不同协程之间的上下文传递。
尽管上述项目在功能上存在一定程度的相似性,但由于eBPF自身的一些限制因素,比如eBPF 通常仅限于具有提升权限的 Linux 环境,同时针对内核的版本有要求,对于某些应用场景尤其是涉及到复杂应用层逻辑追踪时,单独依靠eBPF往往难以达到理想效果。
就性能开销而言,eBPF相对于进程内的Agent稍显落后,因为 uprobe 的触发需要在用户空间和内核之间进行上下文切换,这对于访问量特别大的一些接口难以承受。

三、编译时插桩方案

在这个方案前我们在eBPF方案做了非常多的探索,希望使用eBPF一劳永逸的解决非Java语言的各种监控问题,特别是Go应用(在当前除了Java外使用最广泛的语言),经过长时间的探索,发现无法达成如Java一样实现完全无死角的监控能力,这也正让我们开始思考通过其他方式解决这个问题,基于Go toolexec能力,编译时插桩实现Go的应用监控变得可行。
Go应用的编译流程如下:

使用简单的go build 即可获得最终可以执行的二进制文件,go build 的过程通过以下的:

在经过词法分析、语法分析后生成一些.a的中间态文件,最终通过Link的方式将.a文件生成为二进制文件。通过这个步骤可以看出我们可以在编译前端到编译后端中间进行hook的操作,因此我们将对应的编译流程改成如下方式:

通过AST语法树分析,查找到监控的埋点,根据提前定义好的埋点规则,在编译前插入需要的监控代码,然后经过完成的Go编译流程将代码注入到最终的二进制中,这个方案与程序员手写代码完全没有区别,由于经过了完整的编译流程,不会产生一些不可预料的错误。
使用阿里云可观测Go Agent能力,只需要下载一个编译工具instgo,然后修改一下编译语句即可快速接入,如下所示:

当前的编译语句:

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go 

使用Aliyun Go Agent:

wget "http://arms-apm-cn-hangzhou.oss-cn-hangzhou.aliyuncs.com/instgo/instgo-linux-amd64" -O instgo
chmod +x instgo
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 ./instgo go build main.go

通过wget下载instgo编译工具,只需要简单修改在go build前添加instgo即可完成监控能力注入。
我们可以在插入的代码中实现跟Java应用监控完全一样的监控能力,如链路追踪、指标统计、持续剖析、动态配置、代码热点、日志Trace关联等等,在插件丰富度上我们支持了40+的常见插件[4],包含了RPC框架、DB、Cache、MQ、Log等,在性能上,5%的消耗即可支持1000 qps[5],通过动态开关控制、Agent版本灰度等实现生产的可用性和风险控制能力。

总结

本文讲解了阿里云编译器团队和可观测团队为了实现Go应用监控为什么选择编译时插桩的原因,同时还介绍了其他的监控方案,以及它们的优缺点。我们相信阿里云Go Agent(Instgo)是一个非常强大的工具,可以帮助我们实现针对Go应用更好的APM能力,同时还能保持应用程序的安全性和可靠性。

参考链接:

[1]https://github.com/open-telemetry/opentelemetry-java[2]https://github.com/open-telemetry/opentelemetry-go[3] 监控Golang应用:https://help.aliyun.com/zh/arms/application-monitoring/user-guide/monitoring-the-golang-applications/[4] ARMS应用监控支持的Golang组件和框架:https://help.aliyun.com/zh/arms/application-monitoring/developer-reference/go-components-and-frameworks-supported-by-arms-application-monitoring[5] Golang探针性能压测报告:https://help.aliyun.com/zh/arms/application-monitoring/developer-reference/golang-probe-performance-pressure-test-report[6]为Go应用无侵入地添加任意代码
[7]https://github.com/alibaba/opentelemetry-go-auto-instrumentation

原创 古琦 阿里云开发者

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/858041.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

USACO计算机竞赛2024-2025即将开考 报名方式、考点内容全解析

USACO计算机竞赛2024-2025即将开考 报名方式、考点内容全解析USACO竞赛已经有30多年举办历史,吸引了全球众多计算机编程爱好者参赛,且比赛门槛低,中小学都可以参赛!如果学生有足够的算法能力,那么很有可能在USACO竞赛中拿到名次,助力名校申请。 查看以往MIT录取学生简历,…

elasticsearch修改Ik分词器源码实现基于MySQL更新分词

本文主要记录如何修改Ik分词器源码来实现基于MySQL数据库更新分词,所有步骤均为本人实际操作验证。如果你也刚好刷到这篇文章,希望对你有所帮助。 使用过Ik分词器的应该都知道,它提供了三种配置热词词库的方式:Ik内置词库 Ik外置静态词库 Ik远程词库 具体可以去看Ik的配置文…

从家电的“大成”到汽车的“认怂”,苏峻的工业设计追梦之路

孕育三年八个月,iCAR V23是苏峻进入汽车产业的第一个“孩子”,12月16日带着明星气质呱呱落地。 很巧,苏峻的上一个创业项目——米家空气净化器,落地正好十周年。和净化器一经面试即大成相比,iCAR V23上市后,苏峻的第一次发声,却是认错的内容——V23上市后,第一时间听取…

Vue2环境中AntDeisgn1.x的树形下拉选择组件

示例相关代码 <a-tree-selectv-model="type"show-searchstyle="width: 100%":dropdown-style="{ maxHeight: 400px, overflow: auto }"placeholder="请选择"allow-clear:tree-data="treeData":filter-tree-node="fi…

Linux 虚拟机重启找不到IP解决方案

@目录前言简介Linux 操作系统查看不到IP地址问题描述:第一步 :修改配置第二步 :查看ip第三步 :查看网卡第四步 :重启网络‌Linux 网络服务重启失败解决办法问题描述:第一步:查看NetworkManager的状态第二步:停止NetworkManager第三步:重启Network第四步:禁用NetworkM…

浏览器连续循环数据打印分页时分割模块问题

css设置@media print识别打印容器,给独立块元素(不想被分割的class添加 page-break-inside: avoid; 避免在元素内部进行分页 ),如下: -- 这样遇到一页纸不够放的时候不会分割 print-wrap 这个元素,自动换行到下一页~ @media print { .printChild { width: 100%;…

BERT的继任者ModernBERT:融合长序列处理、代码理解与高效计算的新一代双向编码器

BERT 发布于 2018 年(从人工智能发展速度来看已是遥远的过去),但它至今仍在广泛使用:实际上它目前是 HuggingFace hub 上下载量第二高的模型,月下载量超过 6800 万次,仅次于另一个针对检索任务优化的编码器模型。这源于其编码器架构在处理日常实际问题方面表现出色,例如…

OpenHarmony怎么修改DPI密度值?触觉智能RK3566鸿蒙开发板演示

开源鸿蒙OpenHarmony系统下,修改DPI密度值的方法,触觉智能Purple Pi OH鸿蒙开发板演示,搭载了瑞芯微RK3566四核处理器,Laval鸿蒙社区推荐开发板,已适配全新开源鸿蒙OpenHarmony5.0 Release系统,适合鸿蒙开发入门学习本文介绍在开源鸿蒙OpenHarmony系统下,修改DPI密度值的…

医疗行业的项目管理革新:提升工作效率与患者体验

一、医疗行业的痛点 医疗行业在日常运营中面临一系列管理和运营上的挑战,以下是一些主要痛点: 1.任务管理与进度跟踪困难 在医院或医疗机构中,医疗任务和工作往往涉及多个科室、医护人员和患者。传统的管理方式通常依赖纸质记录或简单的电子表格,这种方法在任务繁多、协作复…

实战案例:基于 Java Web 技术的医药信息管理系统设计与构建

1. 引言 1.1系统目标 本医药信息管理系统旨在实现药品信息、进货信息、销售信息、库存信息、处方信息以及员工信息等的全面管理,提高医药企业运营效率,优化业务流程,确保信息准确、及时、安全,为企业决策提供有力支持,同时提升服务质量,增强企业竞争力。 1.2适用范围 本系…

CentOS安装GitLab社区版

官方文档给出的下载链接,貌似下不动。 因此选择使用清华大学的镜像站来下载社区版的GitLab https://mirror.tuna.tsinghua.edu.cn/help/gitlab-ce/ 于/etc/gitlab/gitlab.rb配置文件处,修改你的GitLab访问地址 默认GitLab访问地址是:external_url http://gitlab.example.c…