编译时插桩,Go 应用监控最佳选择
可观测性是以系统的指标、日志、链路追踪、持续剖析四大数据支柱为基础,从宏观到微观,通过不同数据之间互相关联,衍生出如数据监控、问题分析、系统诊断等一系列的能力。
Java[1]可以通过字节码增强的技术实现无侵入的应用监控(开源社区有非常多的无侵入Agent实现方案,技术非常成熟),可以轻松获取到关键监控数据,相比Java,Go因为语言的特点,应用运行的时候已经被编译成一个二进制文件,无法再做类似Java字节码增强的方式进行动态插桩,在应用监控领域的生态并不完善,可观测的四大数据支柱无法通过无侵入的方式来实现,使得用户的接入成本变高,当前针对Go应用的可观测能力,有3种解决方案:
- SDK方案
- eBPF方案
- 编译期自动注入方案
以下分别来介绍这几个方案以及对应的开源实现:
SDK 方案
在可观测领域,随着OpenTracing 被OTel 收编,目前被广泛使用的SDK就是OTel Go SDK[2],通过在业务代码的每个需要的地方进行手动增加埋点,如下所示:
package main import ( "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能力,同时还能保持应用程序的安全性和可靠性。
为了推广编译时注入的方案,同时为Go开发者提供更多的选择,提升效率,我们的Agent进行了开源[7],欢迎大家加入我们的钉钉群(开源群:102565007776,商业化群:35568145),共同提升编译时插桩在Go应用监控的能力。
参考链接:
[1]https://github.com/open-telemetry/opentelemetry-java
[2]https://github.com/open-telemetry/opentelemetry-go
[3] 监控Golang应用:
[4] ARMS应用监控支持的Golang组件和框架:
[5] Golang探针性能压测报告:
[7]https://github.com/alibaba/opentelemetry-go-auto-instrumentation

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
日志服务 SQL 引擎全新升级
作者:戴志勇、顾汉杰(执少) SQL 作为 SLS 基础功能,每天承载了用户大量日志数据的分析请求,既有小数据量的快速查询(如告警、即席查询等);也有上万亿数据规模的报表级分析。SLS 作为 Serverless 服务,除了要满足不同用户的各类需求,还要兼顾性能、隔离性、稳定性等要求。过去一年多的时间,SLS SQL 团队做了大量的工作,对 SQL 引擎进行了全新升级,SQL 的执行性能、隔离性等方面都有了大幅的提升。 SQL 引擎重磅升级 计算引擎切换为 C++ 版本,充分利用 CPU 的 SIMD 指令集加速能力。 计算存储融合,将计算和存储(只读)并入一个进程,减少数据转换和拷贝开销。 Pipeline 计算模型支持细粒度并行,充分释放单机多核 CPU 的计算能力。 调度模型升级,使任务调度更均衡和稳定,减少数据倾斜,并充分利用历史亲和力和多级缓存。 更优的分布式执行计划,优化了多 count distinct、高基数聚合等场景。 增量计算,对于相同的 SQL,复用历史局部查询结果,只计算最新的数据。 数据缓存,引入阿里自研的缓存组件,自适应缓存列存数据,减少直接 IO 开销。 ...
- 下一篇
网络分析与监控:阿里云拨测方案解密
作者:俞嵩(榆松) 随着互联网的蓬勃发展,网络和服务的稳定性已成为社会秩序中不可或缺的一部分。一旦网络和服务发生故障,其带来的后果将波及整个社会、企业和民众的生活质量,造成难以估量的损失。 2020 年 12 月: Akamai 的 DNS 服务中断,导致包括银行和航空公司在内的众多美国企业官网无法访问。 2021 年 6 月: Fastly CDN 故障,影响了《纽约时报》、亚马逊、Twitch 和 Reddit 等网站的正常运行。 2021 年 10 月: Facebook 遭遇大规模宕机,旗下 Facebook、Instagram 和 WhatsApp 三大社交平台全球范围内停摆长达六小时。 2022 年 12 月: 阿里云香港可用区 C 服务中断,故障持续超过十二小时,严重影响了大量企业的日常运营。 在这样的背景下,拨测(Sythetic Monitoring)作为可观测在网络性能和用户体验监控场景下的核心功能,借助全球覆盖的网络探测节点,模拟用户请求对目标服务/域名/IP 等进行主动网络拨测,监控目标地址在不同地域、运营商网络环境下访问的可用性、性能和用户体验。使用拨测功能,...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- 2048小游戏-低调大师作品
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Hadoop3单机部署,实现最简伪集群
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果