介绍
现金我是服务器端工程师 hirobe。
Kyash 运行着大约 30 个微服务,微服务之间通过 REST/gRPC 进行同步通信和通过 SQS 进行异步通信。
运行微服务上下文传播我希望你有机会听到这个词。
上下文传播是指一种可以跨微服务链接信息的机制。1
上下文传播可以实现的第一件事是追踪我猜。
通过使用 Datadog 和 New Relic 等工具,可以检查跨微服务的处理时间等,对于微服务的运行来说是必不可少的。
此外,通过上下文传播行李可以传播。 Baggage 是您希望跨微服务传递的键值格式数据。
Kyash 使用 Datadog 已经三年多了,当然 Tracing 对研究很有用,但 Baggage 不可用。
因此,本次的主题是让以 REST/gRPC/SQS 为基础的 Baggage 传播成为可能!
此外,在 SQS 中无法进行跟踪,所以我在这个场合使它成为可能。我想在最后简单地谈一下。
概括,
| 通讯方式 | 追踪 | 行李的传播 |
|---|---|---|
| 休息 | 哦 | X |
| gRPC | 哦 | X |
| SQS | X | X |
是下面的故事。
| 通讯方式 | 追踪 | 行李的传播 |
|---|---|---|
| 休息 | 哦 | 哦 |
| gRPC | 哦 | 哦 |
| SQS | 哦 | 哦 |
我们公司使用 Datadog 作为供应商,Go 作为语言,所以我会在这个前提下写这篇博客,但我认为这对其他供应商和语言会有所帮助。
行李中可能包含的好东西
常见示例示例包括与请求相关的数据,例如 UserID、ProductID 和源 IP 地址。作为警告,您不应包含任何安全信息,以防行李意外传递给外部供应商(如果您不小心可能会发生这种情况)。
或者,如果您使用功能标志,最好在您的行李中包含功能信息。
通过在 BFF 等通信入口处确定它是哪个特性,然后在微服务之间的通信中始终传递该信息,您可以在所有微服务中提供相同的特性。
以前写过一篇博客,叫Guide to Introducing Lightweight Feature Flags,但是当时还没有办法传播Baggage,所以只好用了轻量级的方法。
同理,最近好像比较火通过尽可能共享资源来启动微服务开发环境的功能实现时,Baggage 传播对于掌握每个微服务应该表现的版本至关重要。
什么是 OpenTelemetry
第一的,开放遥测解释组。以下简称OTel。
过去,提供可观察工具(Tracing、Metrics、Logs等)的厂商有不同的实现和接口,也没有统一的数据格式发送给厂商。
每个供应商都有不同的标准是不好的,所以标准化已经成为一种趋势,但是在过去已经建立了两个组,如下图所示。
认识到标准化的必要性,云社区走到了一起,诞生了两个开源项目:开放追踪(一个云原生计算基金会 (CNCF)项目)和公开普查(一个谷歌开源社区项目)。
OpenTracing:提供了一个供应商中立的 API,用于将遥测数据发送到 Observability 后端;但是,它依赖于开发人员实现自己的库来满足规范。
OpenCensus:提供了一组特定于语言的库,开发人员可以使用它们来检测他们的代码并发送到他们支持的任何一个后端。然而,在2019年,它被愉快地合并成为了OTel!
因此,在考虑应该使用什么接口来定义Baggage以及应该如何传递时,应该首先考虑OTel。
最后请注意,OTel 本身并不提供实现 Tracing 等的基础设施。它只是组织界面并提供一个库。此类基础设施需要 Datadog、New Relic 等供应商提供服务。但是,要使用通用界面正常工作供应商插件也实施。
OpenTelemetry 行李规范
让我们来看看 OTel 的 Baggage 规范。
前面说过,数据是key-value的格式,但是有详细的规定。规定可以对key-value格式的数据进行Get/GetAll/Set/Delete。
而且 Baggage 是不可变的。也就是说,在Set/Delete等更新处理的情况下,会返回一个新的Baggage,而不是更新它。再次,语境2有一个术语上下文与上下文传播中的“上下文”相同,它是您要跨微服务传播的内容,包含链接信息所需的信息。 Context 也是不可变的,Baggage 必须能够绑定到 Context、检索它并删除它。
熟悉 Go 的读者可以使用 Go 的
context.Context作为 Context。你是对的!在库中表示为context.Context,context.WithValue持有Baggage and Tracing所需的TraceID和SpanID。标头名称指定为
baggage。
baggage是member,用逗号隔开,member除了key/value 之外还可以给property。
在 key 和 value 之前和之后允许有空格,但它们都被删除和解释。key 允许的字符与一般标题可以使用的字符相同。4, value 中允许的字符是百分比编码的 UTF-8 字符。
property可以在key=value这样的成员之后以key=value的形式出现任意次数。一个属性可以只是一个键(例如,k2),类似地,前导和尾随空格是允许的,但会被删除。未指定property应包含的内容。我认为如果你看一个使用 Go 的特定示例会更容易理解。
在下文中,还假设 Tracing 也在使用 OTel 创建 span。import ( "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel" ) func hoge(ctx context.Context) { // 新規にspanを作成 // 紐づいたcontextが返ってくる ctx, span := otel.Tracer("").Start(ctx, "test") // Baggageを定義していく m1, _ := baggage.NewMember("user_id", "10") // m2はpropertyを2つ持つ prop1, _ := baggage.NewKeyValueProperty("is_primary", "true") prop2, _ := baggage.NewKeyValueProperty("name", "test") m2, _ := baggage.NewMember("product_id", "20", prop1, prop2) // m1とm2の2つメンバを持つ bg, _ := baggage.New(m1, m2) // ctxにBaggageを紐づける ctx = baggage.ContextWithBaggage(ctx, bg) // 外部のマイクロサービスに送る if err := sender.Send(ctx, msg, nil); err != nil { panic(err) }下面的值是用头名
baggage存储的。user_id=10,product_id=20;is_primary=true;name=test我最终使用了 Datadog 库
在上一章中,我们确认了 OTel 中 Baggage 的规范。大多数人在实现 Baggage 传播时应该首先考虑使用 OTel 库。
但是Kyash原来是用Datadog做Tracing的,库也用了Datadog的库,所以经过考虑,我们决定不使用OTel,用Datadog的库来实现Baggage传播。
本章将解释原因!
首先,让我向您展示将 Baggage 与 Datadog 一起使用会是什么样子。
下面,我们还假设 Tracing 也在使用 Datadog 库来创建跨度。package main import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) func hoge(ctx context.Context) { // 新規にspanを作成 // 紐づいたcontextが返ってくる span, ctx := tracer.StartSpanFromContext(ctx, "testing") ... // spanに対してBaggageを定義 span.SetBaggageItem("user_id", "10") span.SetBaggageItem("product_id", "20") if err := sender.Send(ctx, msg, nil); err != nil { panic(err) }在上面的代码中,标题名称
ot-baggage-user_id将存储10,而标题名称ot-baggage-product_id将存储20。在 OTel 的情况下,需要对 Context 分别管理 Tracing 的传播和 Baggage 的传播,但在 Datadog 的情况下,Baggage 被定义为 Tracing Span 的一个属性。Baggage 也会被复制到有父子关系的 span.
另外,header的名字可以自定义,但是默认是
ot-baggage-{baggageのkey},并且值没有特定的结构,所以相对OTel来说还是比较简单的。现在,我想对我考虑的三种模式进行评估,为什么我决定将 Datadog 库用于 Tracing 和 Baggage。
将 OTel 用于 Tracing 和 Baggage 的模式
这种模式一开始就没有被考虑过。在Kyash中,大量Datadog的追踪逻辑已经存在于所有微服务中,重写成本巨大。
所以,如果我从头开始考虑它,甚至没有跟踪,即使在那种情况下,我也可能使用 Datadog 的库。
简而言之,使用 OTel 作为 Datadog 的接口似乎并没有死。
控制如何与幕后供应商交换数据的部分(如Datadog)收藏家虽然叫,但这部分需要针对厂商构建,如果Datadog这里它在
网上关于这部分的资料不多,数据狗文档如图 所示,TraceId 和 SpanId 的格式与 OTel 的格式不同,如下图所示。
OpenTelemetry TraceId 和 SpanId 属性与 Datadog 约定不同。因此有必要将 TraceId 和 SpanId 从它们的 OpenTelemetry 格式(128 位无符号整数和 64 位无符号整数分别表示为 32 位十六进制字符和 16 位十六进制字符的小写字符串)进入他们的 Datadog 格式(一个 64 位无符号整数)。
Datadog Agent 将于 2022 年 5 月能够理解 Otel 协议我觉得我们正在朝着更好的方向前进,但我认为我们需要花足够的时间来思考它。
使用 Datadog 进行追踪和 OTel 进行行李追踪的模式
一开始我没有意识到Baggage可以使用Datadog的库进行传播,所以我什至实现了它,但后来我意识到它并放弃了它。
对于 Tracing 和 Baggage,需要使用 Context 执行链接工作。
另外,Tracing已经实现了将通信时Context中包含的要传播的信息转换成header等的处理(下一章解释),很难写出所有的连接部分
使用 Datadog 进行追踪和行李处理的模式
这非常容易!
如本章开头所述,只需将 Baggage 设置在 span 上即可传播实际图像。在 Kyash 的情况下,Tracing 已经完成,并且已经实现了 REST 和 gRPC,将 Context 中包含的要传播的信息转换为 header 等,所以我能够自然地进行下去。
换句话说,我们能够传播 Baggage,而无需额外实现 REST 和 gRPC,而不是 SQS!
我认为这种模式的警告是 Tracing 和 Baggage 是紧密耦合的。
对于其他两种模式,不执行 Tracing,只能执行 Baggage,但是对于这种模式,由于 Baggage 被指定为 span 属性,因此不可避免地假设执行 Tracing。此外,据我所知,至少在 Go 库中,除非调用
tracer.Start定期向 Datadog 代理发送数据,否则不会传播 Baggage。因此,它基本上假设在 Datadog Agent 实际运行的环境中使用,但作为一种解决方法,模拟追踪器我找到了启动 .如果您致电
mocktracer.Start,除了将数据连接到Datadog 的后端之外,一切似乎都已正确完成,因此Baggage 传播完成没有问题。当然,mocktracer 是为测试而准备的,所以它可能不是一个很好的用途。无论如何,在 Kyash 中,Datadog Agent 运行在所有微服务中并完成了跟踪,所以这不是问题。
如何将 Context 中的信息放入通信中
使用 Datadog 库,我介绍了通过将 Tracing 信息和 Baggage 放在 Context 中来在应用程序中传播跟踪信息和 Baggage 的方法和源代码。
那么我们如何在网络中传播上下文呢?
在客户端,上下文中的信息应该被编码并打包在通信内容的头部等中,而在服务器端,它应该被解码并打包在上下文中!这里的故事不是特定于 Baggage 的,Tracing 也是一样,而且这个博客的主题是 Baggage,所以我老实犹豫要不要写它。似乎它会保持原样,并没有网上资料很多,我就写一下。
我会以 Go 为前提来写,但我认为在其他语言中完全一样。
以下TextMapWriter、TextMapReader应该实施。
Ingect、Extract的carrier参数类型是interface{},这令人困惑,但实际上它们分别是TextMapWriter和TextMapReader。
TextMapWriter和TextMapReader是抽象如何在上下文和通信数据之间转换的接口,用于键和值数据(traceID、spanID 和追踪所需的 Baggage 数据)。使用它,接口
Propagator被传播。在图书馆一侧完成所以你不必担心。具体比如提取 Baggage(从服务器端的通信数据中提取 Baggage 并打包到 Context 中)的过程调用TextMapReader,将前缀为ot-baggage-的key 视为 Baggage 数据,并将前缀替换为 It is设置为跨度上下文,除了。// Propagator implementations should be able to inject and extract // SpanContexts into an implementation specific carrier. type Propagator interface { // Inject takes the SpanContext and injects it into the carrier. Inject(context ddtrace.SpanContext, carrier interface{}) error // Extract returns the SpanContext from the given carrier. Extract(carrier interface{}) (ddtrace.SpanContext, error) } // TextMapWriter allows setting key/value pairs of strings on the underlying // data structure. Carriers implementing TextMapWriter are compatible to be // used with Datadog's TextMapPropagator. type TextMapWriter interface { // Set sets the given key/value pair. Set(key, val string) } // TextMapReader allows iterating over sets of key/value pairs. Carriers implementing // TextMapReader are compatible to be used with Datadog's TextMapPropagator. type TextMapReader interface { // ForeachKey iterates over all keys that exist in the underlying // carrier. It takes a callback function which will be called // using all key/value pairs as arguments. ForeachKey will return // the first error returned by the handler. ForeachKey(handler func(key, val string) error) error }我觉得有没有具体的图片比较容易理解,所以我只引用http载体的实现。 datadog 的库是提供这是一个具有它只是塞在标题中。
// HTTPHeadersCarrier wraps an http.Header as a TextMapWriter and TextMapReader, allowing // it to be used using the provided Propagator implementation. type HTTPHeadersCarrier http.Header var _ TextMapWriter = (*HTTPHeadersCarrier)(nil) var _ TextMapReader = (*HTTPHeadersCarrier)(nil) // Set implements TextMapWriter. func (c HTTPHeadersCarrier) Set(key, val string) { http.Header(c).Set(key, val) } // ForeachKey implements TextMapReader. func (c HTTPHeadersCarrier) ForeachKey(handler func(key, val string) error) error { for k, vals := range c { for _, v := range vals { if err := handler(k, v); err != nil { return err } } } return nil }对于 gRPC,datadog 的贡献库定义在您可以看到它存储在 gRPC 元数据中。 grpc 元数据键不区分大小写,因此 Baggage 键只使用小写字母似乎更安全。
完成此操作后,只需分别在客户端和服务器上调用
Inject和Extract。例如,对于 http 客户端
_ = tracer.Inject(span.Context(), tracer.HTTPHeadersCarrier(req.Header))是。
服务器端一般实现为中间件。例如,杜松子酒由中间件定义并从那里打电话函数内部处理提取被调用。
对于 gRPC,由中间件定义并从那里打电话函数内部处理提取被调用。
就我能找到的 SQS 载体而言,没有特定的库,所以我自己制作了。最后,我想通过介绍 SQS 的载体实现来结束。
import ( "github.com/aws/aws-sdk-go/aws" sqssdk "github.com/aws/aws-sdk-go/service/sqs" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) // SQSMessageAttributeCarrier adapts map of MessageAttribute to satisfy tracer.TextMapWriter/tracer.TextMapReader interface. type SQSMessageAttributeCarrier map[string]*sqssdk.MessageAttributeValue var _ tracer.TextMapWriter = (*SQSMessageAttributeCarrier)(nil) var _ tracer.TextMapReader = (*SQSMessageAttributeCarrier)(nil) // Get returns the value associated with the passed key. func (mc SQSMessageAttributeCarrier) Get(key string) string { if mc == nil { return "" } v := mc[key] if v == nil { return "" } if *v.DataType != "String" { return "" } return *v.StringValue } // Set stores the key-value pair. func (mc SQSMessageAttributeCarrier) Set(key string, value string) { v := &sqssdk.MessageAttributeValue{ DataType: aws.String("String"), StringValue: aws.String(value), } mc[key] = v } // ForeachKey will iterate over all key/value pairs. func (mc SQSMessageAttributeCarrier) ForeachKey(handler func(key, val string) error) error { for k := range mc { if err := handler(k, mc.Get(k)); err != nil { return err } } return nil }概括
虽然我介绍了 OTel Baggage 的规范,但我没有使用 OTel 库,但如果以后每个供应商都提供 OTel 行为作为默认行为就好了。例如,对于 Datadog声明您对 OTel 的贡献我认为这很好,因为每个公司似乎都有合作的态度。
我不认为这是一个面向大众的主题,但是互联网上的信息很少,有必要向公司解释,所以我这次花时间写了很多。
Kyash 是这样组织基金会的。
我认为我们正朝着更好的方向前进。如果您有兴趣,请查看!
准确地说,它不仅包括微服务,还包括无服务器应用程序,但解释是简化的。并且该领域的术语很困难,并且根据上下文,它的使用可能会略有不同。老实说,这个街区的故事很复杂,很难理解。我打算阅读文档和实现,但请指出任何错误。↩
讲解以“A Context是一种传播机制~”开头,但你有没有觉得主题不是Context而是Context Propagation?↩
我担心 OTel 规范和 w3c 之间的差异,但就实现而言,区别似乎是 w3c 允许消息中的重复键,但 OTel 不允许它们并覆盖它们。↩
ASCII 字符的一部分。具体“!”/“#”/“$”/“%”/“&”/“'”/“*”/“+”/“-”/“.”/“^”/“_”/“ `" / "|" / "~" / DIGIT / ALPHA↩
原创声明:本文系作者授权爱码网发表,未经许可,不得转载;
原文地址:https://www.likecs.com/show-308629289.html