提高 GoLang 服务的可观测性
福洛链 Flow Official
2023-02-15 07:00
订阅此专栏
收藏此文章

原文链接:https://flow.com/engineering-blogs/golang-services-improving-observability

本文由 Flow 大使 Caos 翻译整理。

这篇博客文章针对的是 GoLang 开发人员,他们希望改善他们的服务的可观察性。它跳过了基础知识,直接跳到高级主题,如异步结构化日志记录、使用样本的指标、使用 TraceQL 进行跟踪、聚合 Pprof 和连续分析、使用 Benchstat 进行微基准测试和基本统计、黑盒性能测试,以及用于确定系统最大负载的基本 PID 控制器。我们还将简要介绍可观测性领域的最新研究,包括主动偶然剖析和被动临界截面检测。



可观测性的三大支柱: 日志、指标、追溯

如果你正在读这篇文章,你可能不需要复习一下可观察性的基本知识。让我们深入研究一些不那么明显的东西,把重点放在尽可能容易地在三个主要的可观测表面之间。我们还将讨论如何将跟踪添加到组合中,以便将 pprof 数据链接到跟踪并返回。


如果您正在寻找有关监视基础知识的简短而直接的介绍,以及将基本可观测性快速引入服务的方法,Cindy Sridharan 的“分布式系统可观测性”是一个很好的起点。



结构化日志

如果不使用零分配日志库,日志记录可能成为一个瓶颈。如果你还没有,可以考虑使用 zap 或者 zerolog ——两者都是很好的选择。

Golang 还有一个正在进行的引入结构化日志记录的提议:slog 一定要查看它,并提供对提案的反馈!

可以在以下网址找到最新的基准测试Logbench(http://hackemist.com/logbench/)结构化日志记录对于从日志中提取数据至关重要。采用 json 或 logfmt 格式可以简化特定的故障排除,并允许在处理适当的度量时快速显示粗糙的图表 / 警报。大多数日志库还为 gRPC/HTTP 客户机和服务器以及通用数据库客户机提供了即时使用的钩子,这极大地简化了对现有代码库的介绍。


如果您发现基于文本的格式效率低下,您可以在很大程度上优化您的日志记录。例如,zerolog 支持二进制 CBOR 格式,Envoy 为其结构化访问日志提供了 protobufs。


在某些情况下,日志本身可能成为性能瓶颈。您不希望您的服务被卡住,因为当您启用 DEBUG 日志时,Docker 无法足够快地从 stderr 管道中提取日志。

一种解决方案是对日志进行取样:

sampled := log.Sample(zerolog.LevelSampler{    DebugSampler: &zerolog.BurstSampler{        Burst: 5,        Period: 1*time.Second,        NextSampler: &zerolog.BasicSampler{N: 100},    },})

或者,你可以让它们的触发完全异步,这样它们就不会堵塞:

wr := diode.NewWriter(os.Stdout, 1000, 10*time.Millisecond, func(missed int) {    fmt.Printf("Logger Dropped %d messages", missed)  })log := zerolog.New(wr)

对于使用 Grafana 和 Loki 的用户,还有一点需要注意: 您可能希望设置派生字段。通过这种方式,您可以从日志中提取字段并将其放入任意 URL 中。

一个在日志、跟踪和第三方资源之间一键导航的示例。


如果上下文指示应启用跟踪,请考虑在每条日志消息中包含跟踪 ID。你以后会为此感谢自己的。



指标

假设您已经在服务中使用了普罗米修斯风格的度量标准。但是,当您看到图表上的一个峰值,并且需要找出导致减速的原因 ( 扰流器: 可能是数据库 ) 时,您该怎么办?直接从指标跳转到缓慢请求的跟踪不是很棒吗?如果是这样,ExemparAdder 和 ExemparObserver 就是为您准备的:


ctx := r.Context()traceID := trace.SpanContextFromContext(ctx).TraceID.String()requestDurations.(prometheus.ExemplarObserver).ObserveWithExemplar(        time.Since(now).Seconds(), prometheus.Labels{"traceID": traceID}})

注意,您不仅可以将 trace_id 放入标签中,还可以将任意键值数据放入标签中,这对于可以包含 user_id team_id 的多租户环境特别有用。这可能是解决高基数度量问题的一个具有成本效益的解决方案。



追踪

在当今世界,跟踪对于性能分析至关重要,因此大多数服务都启用了跟踪。从开放跟踪到开放人口普查再到现在的开放遥测,这个行业的跟踪之路一直坎坷不平。我们使用一个通用的设置,OTEL 库用于跟踪事件,Grafana Tempo 作为后端。


Flow 区块链跟踪事件不同于典型的 web 后端: 我们不依赖于在信任边界之间传递的跟踪上下文,所以我们不传播 TraceID,而是基于正在处理的对象确定性地构建它们——块或事务的哈希。


跟踪的问题不在于发送数据,而在于定位“有趣”数据的能力。默认的 Grafana 的搜索能力相当有限,有时需要几分钟才能找到所需的跟踪。

标准跟踪搜索界面👆🏻


TraceQL 通过引入一种方便的跟踪搜索方法解决了这个问题。找到特定的踪迹现在很容易。以下是参考文献中的一些例子:

{.network = "mainnet20" && span.transaction.ContractFunctionCall = "e467b9dd11fa00df.FlowStorageFees.calculateAccountsCapacity" && duration > 1ms }

如果您正在使用 Grafana,不要忘记将您的日志数据源链接到您的跟踪。这将使得在跟踪和日志之间导航变得容易:

将跟踪链接回日志 ( 包括可能指标 )👆🏻


优化 TraceQL 查询可以通过在“资源”中具有通用的每个进程属性来实现。这比搜索要快得多,而且可以产生巨大的影响。例如,在上面的截图中,我们有一个 bug,其中 env 和 network 应该作为资源而不是属性发出。



剖析

Go 运行时提供了出色的分析能力。如果你想在 net/http/pprof 之外测试你的代码,我们强烈推荐阅读 DataDog 的忙碌的开发者的分析、跟踪和可观察性指南。它详细介绍了所有分析器类型 (CPU、内存、块等 ) ,并涵盖了底层实现细节,如堆栈跟踪和 pprof 格式。


许多分析特性需要一个新的 Go 运行时,以便快速和准确地进行操作。如果您打算在生产中依赖分析数据,特别是如果您计划使用连续分析,请升级到 Go 1.19。


一旦您有了原始的 pprof 文件,您将需要对其进行分析。然而,go 工具 pprof -http 5000 有其局限性。理想的解决方案是将概要文件存储在支持基本查询和筛选的数据库中。我们使用 Google 的 Cloud Profiler,而不是依赖于他们有限的客户端库,我们利用了他们的“离线 API”,它允许我们发布现有.pprof 文件到谷歌:


profileBytes, _ := os.ReadFile(filename)client.CreateOfflineProfile(ctx, &pb.CreateOfflineProfileRequest{  Parent: projectId,  Profile: &pb.Profile{    ProfileType:  profileType,    Deployment:   deployment,    ProfileBytes: profileBytes,  },})

将配置文件发送到远程存储比仅仅在本地存储它们的主要好处是能够将多个配置文件聚合到单个视图中。


聚合概要文件查看器界面此外,它还使我们能够随着时间的推移查看配置文件趋势。

历史趋势 N 图的一个例子遗憾的是,谷歌的“离线”API 的速度和大小受到严重限制,似乎一般不支持,所以我们正在积极探索替代方案。因为我们已经在使用 Grafana 栈,所以我们会密切关注Phlare:https://grafana.com/oss/phlare/ 的开发; 



分析与跟踪结合

我们在性能分析的用户体验方面投入了大量精力,确保了日志、跟踪和指标之间的平稳过渡。然而,分析数据目前是一个独特的挑战。为了解决这个问题,我们正在探索 pprof.Do ( 或者 pprof.SetGoroutineLabels)。这将使我们能够在分析和跟踪之间创建当前缺失的连接。

pprof.Do(    ctx,    pprof.Labels(        "span", fmt.Sprintf("%s", span)    ),    func(ctx context.Context) {        doWork(ctx)    },)

标签有一些缺点: 它们不受所有配置文件类型的支持,并且会增加配置文件的大小,所以要注意标签的基数。


您可以在这里添加任意标签,这在多租户环境中特别有用。例如,您可以使用租户的 UserID 和 TeamID 对教授进行注释。即使对于单租户设置,使用 EndpointPath 对概要文件进行注释也可以提供对 CPU 使用情况的额外了解。



fgprof

Go 的默认分析器有一个缺点,那就是只能查看 On-CPU 或 Off-CPU 时间。Felix Geisendörfer 的分析器 fgprof (https://github.com/felixge/fgprof)通过提供一个同时捕捉两者的视图解决了这个问题。



持续分析

分析现在已经足够便宜,以至于许多公司都提供了用于在生产中连续分析的库,这已经成为一种趋势。值得注意的例子包括 Pyroscope、DataDog 和 Google。


Grafana Phlare 没有将分析器嵌入到代码库中,而是使用了一个代理模型,该模型定期擦除 Go 位于 /debug/pprof/ 的 pprof HTTP 端点:

scrape_configs:  - job_name: 'default'    scrape_interval: 10s    profiling_config:      path_prefix: "/debug/pprof"      pprof_config:        memory:          enabled: true          path: "/allocs"          delta: true# ...



eBPF-based 分析

以上所有分析特性都需要在二进制文件中使用某种检测工具,无论是 HTTP 端点还是连续分析库。最近,有一个趋势是可观测的无仪器分析启用 eBPF。

例如,Parcahttps://github.com/parca-dev/parca)使您能够观察未检测的 C、 C + + 、 Rust、 Go 等等!

Parca UI Demo👆🏻



bpftrace/bcc 剖析工具

Parca 完全依赖 perf_events,这是绝对安全的。同时,如果您尝试在 Go 应用程序上使用带有 bcc 或 bpftrace 的 uprobe / uretprobe 进行简单的分析,那么由于 Go 的 stack copying,您可能会遇到 SIGBUS。



微观基准测试

微基准测试是 Go 语言中一个众所周知的实践,所以没有太多可说的。不过,有几件事值得一提。首先,建议在运行微基准测试时使用 test.benchmem 并运行 ^ $


其次,只有当 -count 大于或等于 10 时,基准结果才应该被认为是有效的:

$ go test -count 10 -bench 'Benchmark.*TokenTransfer' -benchmem -run '^$' ./goos: darwingoarch: arm64pkg: github.com/onflow/cadence/runtimeBenchmarkFungibleTokenTransfer-8 6638  179964 ns/op 104511 B/op 1966 allocs/opBenchmarkFungibleTokenTransfer-8 6458 179890 ns/op 103890 B/op 1966 allocs/opBenchmarkFungibleTokenTransfer-8 6853 180334 ns/op 104513 B/op 1966 allocs/op...

最后,在分析单个基准测试结果时,应始终使用基准测试工具 benchstat( 或类似的工具 ) ,以提高可读性并保证环境中没有噪音:

$ benchstat go1.20rc1name                            time/opFungibleTokenTransfer-8  180µs ± 0%name                            alloc/opFungibleTokenTransfer-8  105kB ± 1%name                            allocs/opFungibleTokenTransfer-8  1.97k ± 0%

在基准测试环境中,噪音可能是一个严重的问题。LLVM 项目有很多关于如何调优 Linux 系统以适应小于 0.1% 的基准测试变化的文档。


此外,在进行比较时,尤其是在声称性能有所改善时,必须显示统计数据:

$ benchstat go1.19 go1.20rc1name                            old time/op    new time/op    deltaFungibleTokenTransfer-8     182µs ± 2%     180µs ± 0%  -1.33%  (p=0.021 n=10+8)name                            old alloc/op   new alloc/op   deltaFungibleTokenTransfer-8     105kB ± 1%     105kB ± 1%    ~     (p=0.363 n=10+10)name                            old allocs/op  new allocs/op  deltaFungibleTokenTransfer-8     1.97k ± 0%     1.97k ± 0%  -0.10%  (p=0.000 n=10+10)

不要羞于向测试中添加自定义度量,以便从关键部分获得对性能报告的更多了解。利用 b.ReportMetric 和 b.ResetTimer 对它们进行优化。


如果有一个开源或 SaaS 工具能够随着时间的推移收集和跟踪 Go 存储库的微基准测试结果就好了; 但是,我们没有找到这样的工具。



黑盒性能测试

虽然有各种各样的工具用于端到端的性能测试 web 应用程序 ( 例如,我们使用 grafana/k6 来测试我们的应用程序 ) ,但是复杂的系统,如编译器或数据库,需要定制的黑盒测试,也称为宏基准测试。下面是大型开源项目中自动化宏基准测试的一些好例子 ( 基准测试框架本身也是开源的 )。

分布式数据库 Vitess 每晚都会进行一次非常全面 ( 尽管有点吵 ) 的性能测试,跟踪 micro 和 macro 基准测试的结果。这个测试框架在 https://github.com/vitessio/arewefastyet 上是开源的。

Rust 是另一个很好的基准测试框架的例子,可以用于在每次提交的基础上进行的各种特定于编译器的测试。该软件的开源 https://github.com/rust-lang/rustc-perf

与这两个相比,我们的端到端设置相对较小,但是仍然有一些有趣的细节。其中之一是检测系统过载点。由于区块链是异步的,并且可以在端到端的宏基准测试中对多个层上的事务进行排队,因此我们需要找到区块链在没有过多延迟的情况下能够处理的最大每秒事务值。我们过去使用 TCP 的和性增长 / 乘性降低 (AIMD) 算法来解决这个问题,但是收敛速度很慢。最近,我们转而使用

PID 控制器:https://github.com/onflow/flow-go/pull/3735

( 确切地说是 PD 控制器 ) ,它有一个很好的特性,可以快速收敛到所需的队列大小,同时也不会超调太多:

我们的 Token 转账基准收敛到稳定状态的一个例子。

如果你有兴趣在你自己的系统中引入 PID 控制器,Philipp K. Janert 的书《计算机系统的反馈控制: 向企业程序员介绍控制理论》是一个很好的起点。如果你是一个更多的视觉学习者,从 理解 PID 控制 YouTube 播放列表的前几个讲座也是一个不错的方式。请记住,这是 MATLAB 通道,所以它节奏很快。我已经提示过你了。



未来的工作

在这里,我们将讨论今年计划添加到我们的性能可观测性工具包中的事情。


■ 通过放慢速度来检测瓶颈

加快速度可能很困难,但放慢速度却相对简单。因此,识别瓶颈的一种方法是尝试将系统组件的速度降低 1 毫秒、5 毫秒、50 毫秒、250 毫秒等,并测量基准测试结果。然后,将函数外推回 -1 毫秒、 -5 毫秒、 -50 毫秒 等。这种方法是近似的,但是对于小值很有效。


■ 临时检测

临时分析器是另一种活跃的方法,是对前一种方法的更精确的概括。它不会减慢感相关组件的速度,而是会减慢它周围的所有其他组件的速度,从而模拟正在测试的组件的加速比。

如果你对临时分析感兴趣,有一个很好的视频介绍在“ Coz: 寻找代码与因果分析计数”(SOSP’15)。


■ 自动关键路径分析

分析大规模分布式系统的关键路径可能是一个挑战。在他们的 OSDI’14 论文《神秘的机器: 大规模互联网服务的端到端性能分析》中,密歇根大学和 Facebook 提出了一种通过观察日志 ( 现在可能更类似于跟踪 ) 被动识别关键路径的方法。这种方法的好处是可以很好地与跟踪和分析基础结构相结合。



附录 A 人工智能驱动的博客文章编辑器

与 Golang 的可观测性无关,但是通常适用于那些写博客文章的人: 英语可能不是你的母语,所以博客文章的初稿对于编辑来说是相当痛苦的。这通常需要好几个小时 ( 甚至可能是一瓶威士忌 ) 来放回被遗忘的文章,重新组织句子,抑制给我发送 Conjunction Junction 视频的冲动,纠正标点符号和习语。现代语言模型可以消除这种痛苦。例如,这篇文章的第一次编辑是由 text-davinci-003 完成的 ( 说的太多会引起不适!!!) 提示语如下:


你是一个技术博客编辑,评论一篇关于“提高 Golang 服务的可观测性”的文章请根据需要重写下面的段落,纠正拼写、习语、标点和单词选择。合并、拆分句子或重写,以便在需要时增加清晰度。别写得太枯燥了,幽默就好。


 关注 Flow 

什么是 Flow 福洛链?


Flow 福洛链是一个快速,去中心化,且对开发者友好的区块链,旨在为新一代游戏、娱乐应用程序提供动力的数字资产的基础。Flow 是唯一一个由始至终为消费者提供出色体验的 Layer-1 区块链团队。其团队创造的 dApp 包括:CryptoKittiesDapper WalletsNBA Top shot


CrytoKitties 于 2017 年推出时便快速成为加密市场最受欢迎的 dApp,因其成功而导致以太坊堵塞。在 Flow 上运营的 NBA Top shot 也已成为增长最快的 dApp,在公开发布后的 6 个月创造了 7 亿美金销量。正因为 Flow 公链的可扩展性和消费者友好的体验,让这一切成为可能。目前有 1000 多个项目正在 Flow 链上筹备中,我们期待看到一个伟大的生态系统蓬勃发展。


关于 Dapper Labs

Dapper Labs 是一家位于加拿大的全球顶尖区块链服务商,在 2017 年年底通过 CryptoKitties 收藏游戏成功进入⽤户视野,并且因为加密猫的爆⽕导致以太坊拥堵,从而推出 Flow 公链以及全新的开发语言—— Cadence,旨在吸引更多的开发者在 Flow 上开发应⽤。 


Flow 的合作伙伴们:

我们欢迎越来越多的小伙伴加入 Flow 星球,为星球增添色彩!


Flow 官网:https://zh.onflow.org/

Flow 论坛: https://forum.onflow.org/

Flow Discord:

https://discord.com/invite/flow

Flow CN Telegram: https://t.me/flow_zh

Flow B 站:https://space.bilibili.com/1002168058

Flow 微博: 

https://weibo.com/7610419699

Flow CSDN:

https://blog.csdn.net/weixin_57551966?spm=1010.2135.3001.5343


扫码添加 Flow 官方账号微信号,加入 Flow 生态群

微信号 : FlowChainOfficial

【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。

福洛链 Flow Official
数据请求中
查看更多

推荐专栏

数据请求中
在 App 打开