2. Golang Context 值传递的生产案例(01) - 链路追踪
如果在 公众号 文章发现状态为 已更新, 建议点击 查看原文 查看最新内容。
状态: 未更新
原文链接: https://typonotes.com/posts/2023/03/15/golang-context-in-action-trace/
看完本文
- 了解 链路追踪 和 OpenTelemetry 相关知识
- 了解 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
这里有一个官方一步步实现链路追踪的案例, 可以自己实现一下: https://github.com/open-telemetry/opentelemetry-go/tree/main/example/fib
现在假设有 6 个服务, SvcA 1-3 , SvcB 1-3, 这 6 个服务可能分布在不同机器上面。
第一阶段
我们知道他们之间存在调用关系, 但是任何证据支撑。 所以从外部看来, 他们之间就是完全独立的的 6 个服务。
由于时间紧, 任务重, 业务就这样上线了。
第二阶段
后来业务出了问题, 除了靠记忆, 就只能现场看代码找调用链路。 排查过程异常艰难。
于是有人提议说, 不如我们在每个请求中都带上 UUID, 并且打印到日志里面, 就知道哪些服务在某个请求下, 是有关系的。
于是就有了 TraceID
第三阶段
业务还是除了问题, 这次排查过程相比上次就轻松多了, 但是依然发现了一些 困惑 的问题。 虽然能从日志时间看到服务的调用顺序, 但是并不知道他们之间的调用关系, 没办法有效的把他们组织起来。
于是就有人说, 要不在日志中增加两个字段, 表明他们调用的父子关系吧
于是就有了 ParentSpanID
和 SpanID
现在好了我们能通过 TraceID, SpanID, ParentSpanID
搞清楚服务之间的调用关系了。 但是要怎么传递他们呢?
这些字段是 肯定不能 直接放在参数里面,
- 这些字段不是业务参数, 放进去会 污染 函数或方法。
- 即使现在放进去了, 以后要维护 增加或减少 怎么办? 不可能每个地方服务都去改一次吧, 服务里面还有那么多函数。 从理论上来说, 这个就是不合理的设计。
func Guanyu(n int, TraceID string, SpanID string, ParentSpanID string) {
// statement
}
func Zhangfei(n int, TraceID string, SpanID string, ParentSpanID string) {
// statement
}
第四阶段
于是又有人说了, 要不我们统一放在 Context
里面吧,
- Context 本身就是上下文, 就具有传递性。
- 而且其设计理论上还可以存储无限多的数据。
这样我们就只需要在在每个函数或者方法中多添加一个 Context 参数就行了, 维护也方便。
func Guanyu(ctx context.Context, n int) {
// statement
}
func Zhangfei(ctx context.Context, n int) {
// statement
}
这样看起来, 就清爽很多了。 而且这种结构 是不是很熟悉 ?