Golang Context 值传递的生产案例(01): 链路追踪

时间:2021-10-13 01:01:08

Golang Context 值传递的生产案例(01): 链路追踪

如果在 公众号 文章发现状态为 已更新, 建议点击 查看原文 查看最新内容。

状态: 未更新

原文链接:​https://typonotes.com/posts/2023/03/15/golang-context-in-action-trace/​

看完本文

  1. 了解 链路追踪 和 OpenTelemetry 相关知识
  2. 了解 Context 值传递是如何在 链路追踪 的发展历程上登台亮相的。

之前在 ​​Golang 上下文 Context 源码解析(1): 值传递​​ 文章中举了一个例子说明讲解 Context 的值传递, 其中说到了 刘备-关羽-张飞 之间使用 Context 传递 曹操军队人数,

有朋友反馈说这个值应该是 业务参数 放在函数中作为 形参 传递, 难以理解为什么会放在 Context 中。

func Guanyu(n int) {
// statement
}
func Zhangfei(n int) {
// statement
}

因此, 这次我们通过生产实际应用, 来说一下 Context 的值传递。

链路追踪

想必大家也知道, 微服务治理是一个头疼的问题。 由于服务众多且部署分散, 因此通过 调用链路 排查问题就非常重要了。 实现这个的方案就叫 链路追踪 , 在微服务中非常重要。

可以说, 开源项目 OpenTelemetry 在链路追踪层面, 基本上已经是事实标准了。

Github 地址: ​​https://github.com/open-telemetry​

Golang Context 值传递的生产案例(01): 链路追踪

这里有一个官方一步步实现链路追踪的案例, 可以自己实现一下: ​​https://github.com/open-telemetry/opentelemetry-go/tree/main/example/fib​

Context 在 链路追踪 中的应用

现在假设有 6 个服务, SvcA 1-3 , SvcB 1-3, 这 6 个服务可能分布在不同机器上面。

第一阶段

我们知道他们之间存在调用关系, 但是任何证据支撑。 所以从外部看来, 他们之间就是完全独立的的 6 个服务。

Golang Context 值传递的生产案例(01): 链路追踪

由于时间紧, 任务重, 业务就这样上线了。

第二阶段

后来业务出了问题, 除了靠记忆, 就只能现场看代码找调用链路。 排查过程异常艰难。

于是有人提议说, 不如我们在每个请求中都带上 UUID, 并且打印到日志里面, 就知道哪些服务在某个请求下, 是有关系的。

于是就有了 ​​TraceID​

Golang Context 值传递的生产案例(01): 链路追踪

第三阶段

业务还是除了问题, 这次排查过程相比上次就轻松多了, 但是依然发现了一些 困惑 的问题。 虽然能从日志时间看到服务的调用顺序, 但是并不知道他们之间的调用关系, 没办法有效的把他们组织起来。

于是就有人说, 要不在日志中增加两个字段, 表明他们调用的父子关系吧

于是就有了 ​​ParentSpanID​​ 和 ​​SpanID​

Golang Context 值传递的生产案例(01): 链路追踪

使用 Context 携带字段

现在好了我们能通过 ​​TraceID, SpanID, ParentSpanID​​ 搞清楚服务之间的调用关系了。 但是要怎么传递他们呢?

这些字段是 肯定不能 直接放在参数里面,

  1. 这些字段不是业务参数, 放进去会 污染 函数或方法。
  2. 即使现在放进去了, 以后要维护 增加或减少 怎么办? 不可能每个地方服务都去改一次吧, 服务里面还有那么多函数。 从理论上来说, 这个就是不合理的设计。
func Guanyu(n int, TraceID string, SpanID string, ParentSpanID string) {
// statement
}
func Zhangfei(n int, TraceID string, SpanID string, ParentSpanID string) {
// statement
}

第四阶段

于是又有人说了, 要不我们统一放在 ​​Context​​ 里面吧,

  1. Context 本身就是上下文, 就具有传递性。
  2. 而且其设计理论上还可以存储无限多的数据。

这样我们就只需要在在每个函数或者方法中多添加一个 Context 参数就行了, 维护也方便。

func Guanyu(ctx context.Context, n int) {
// statement
}
func Zhangfei(ctx context.Context, n int) {
// statement
}

这样看起来, 就清爽很多了。 而且这种结构 是不是很熟悉

OpenTelemetry 补充说明

这里以 OpenTelemtry 距离, 是为了说明 Context 的值传递的其中一个使用案例。

OpenTelemetry 在链路追踪上的实现,更完善, 更强大。

配合 Jaeger 这类可视化工具后, 服务的一切净收眼底。

Golang Context 值传递的生产案例(01): 链路追踪