首页 > 其他分享 >【GO应用】编译时插桩,Go应用监控的最佳选择

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

时间:2024-12-24 11:33:16浏览次数:3  
标签:监控 应用 编译 eBPF Go go GO com

阿里妹导读

本文讲解了阿里云编译器团队和可观测团队为了实现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能力,同时还能保持应用程序的安全性和可靠性。

参考链接:

[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

原文

标签:监控,应用,编译,eBPF,Go,go,GO,com
From: https://www.cnblogs.com/o-O-oO/p/18627011

相关文章

  • 魔搭Modahub AI 应用(包括智能体)平台架构
    魔搭为AI应用(包括智能体)开发人员提供了一站式全链路的AI应用搭建能力,包括应用开发、测评、监控和丰富的发布渠道。如下图所示,空间是魔搭平台的最顶层的资源组织方式,通过工作空间对开发资源进行隔离。空间:空间是资源组织的基础单元,不同空间内的资源和数据相互隔离。一个......
  • 模拟 AutoMapper 在单元测试中的应用:_mapperMock.Setup 详解
    模拟AutoMapper在单元测试中的应用:_mapperMock.Setup详解在单元测试中,我们经常需要模拟一些外部依赖的行为,比如数据库操作、网络请求或是映射工具。AutoMapper是.NET中广泛使用的对象映射库,它将一个类型的对象转换为另一个类型的对象。为了在单元测试中有效地验证业务逻辑,......
  • 为何都在谈低代码?快速了解低代码技术在ITSM中的应用
    本文来自腾讯蓝鲸智云社区用户:CanWay还记得早期的Dreamweaver吗?为了提高网页的开发效率,Dreamweaver提供了可视化拖拽的能力来生成网页代码。可见,低代码、无代码的探索和发展其实很早就开始了。近年来,“低代码”这个关键词突然又热了起来,相关创业公司如春笋般涌现。突然爆火......
  • Flutter OHOS fluttertpc_app_installer(打开应用商店和安装APP)
    fluttertpc_app_installer打开应用商店和安装APP用法StringandroidAppId='';StringiOSAppId='';StringohosAppId='';AppInstaller.goStore(androidAppId,iOSAppId,ohosAppId); AppInstaller.installApk('/sdcard/apk/app-debug.......
  • 小迪安全->基础入门-APP应用&微信小程序&原生态开发&H5+Vue技术&WEB封装打包&反编译抓
    知识点:1、基础入门-APP应用-开发架构安全问题2、基础入门-小程序应用-开发架构安全问题通用:1、反编译-得到源码-源码提取资产(泄漏的配置信息)-安全测试2、抓包-资产-安全测试一、演示案例-移动App-开发架构-原生&H5&封装等1、原生开发安卓一般使用java语言开发,当然现在也有......
  • 自然语言处理与知识图谱的融合与应用
    目录前言1.知识图谱与自然语言处理的关系1.1知识图谱的定义与特点1.2自然语言处理的核心任务1.3二者的互补性2.NLP在知识图谱构建中的应用2.1信息抽取2.1.1实体识别2.1.2关系抽取2.1.3属性抽取2.2知识融合2.3知识推理3.NLP与知识图谱融合的实际应用3.1智......
  • ArkUI 的声明式 UI 编程与状态管理:构建高效鸿蒙应用
    ArkUI的声明式UI编程与状态管理:构建高效鸿蒙应用在鸿蒙应用开发领域,ArkUI脱颖而出,其独特的声明式UI编程与高效的状态管理机制,为开发者开辟了一条便捷、高效的开发之路,重塑了移动应用的构建方式。声明式UI编程,摒弃传统命令式繁琐操作,宛如一位精细的画师,用简洁笔触勾勒界......
  • 大语言模型驱动的Agent:定义、工作原理与应用
    文章目录引言什么是大语言模型?Agent的概念LLMAgent的工作原理Dify平台上的AgentLLMAgent的应用场景挑战与展望结论引言随着人工智能(AI)技术的发展,特别是自然语言处理(NLP)领域的进步,大语言模型(LLM,LargeLanguageModels)已经成为AI领域的一颗璀璨明星。这些模......
  • 可视化分析-基于django的网络用户购物行为分析系统
    文章目录程序资料获取一、项目技术二、项目内容和项目介绍三、核心代码四、效果图程序资料获取......
  • ✨ 自动化更新 Docker 应用:Watchtower 魔法
    ✨自动化更新Docker应用:Watchtower魔法......