偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

OpenTelemetry 實(shí)戰(zhàn):從零實(shí)現(xiàn)分布式鏈路追蹤

開發(fā) 前端
對(duì)于支持自動(dòng)埋點(diǎn)的語(yǔ)言就很簡(jiǎn)單,只需要配置下 agent 即可;而原生的 Go 語(yǔ)言不支持自動(dòng)埋點(diǎn)就得手動(dòng)使用 OpenTelemetry 提供的 SDK 處理一些關(guān)鍵步驟;總體來(lái)說(shuō)也不算復(fù)雜。

背景

之前寫過(guò)一篇 從 Dapper 到 OpenTelemetry:分布式追蹤的演進(jìn)之旅的文章,主要是從概念上講解了 Trace 在 OpenTelemetry 的中的場(chǎng)景和使用。

也寫過(guò)一篇 實(shí)操 OpenTelemetry:通過(guò) Demo 掌握微服務(wù)監(jiān)控的藝術(shù):如何從一個(gè) demo 開始集成 OpenTelemetry。

但還是有不少小伙伴反饋說(shuō)無(wú)法快速上手(可能也是這個(gè) demo 的項(xiàng)目比較多),于是我準(zhǔn)備從 0 開始從真實(shí)的代碼一步步帶大家集成 OpenTelemetry,因?yàn)?OpenTelemetry 本身是跨多種語(yǔ)言的,所以也會(huì)以兩種語(yǔ)言為(Java、Golang)主進(jìn)行講解。

使用這兩種語(yǔ)言主要是因?yàn)?Java 幾乎全是自動(dòng)埋點(diǎn),而 Golang 因?yàn)檎Z(yǔ)言特性,大部分都得硬編碼埋點(diǎn);覆蓋到這兩種場(chǎng)景后其他語(yǔ)言也是類似的,頂多只是 API 名稱有些許區(qū)別。

在這個(gè)過(guò)程中也會(huì)穿插一些 OpenTelemetry 的原理,希望整個(gè)過(guò)程下來(lái)大家可以在項(xiàng)目中實(shí)際運(yùn)用起來(lái),同時(shí)也能知其所以然。

項(xiàng)目結(jié)構(gòu)

在這個(gè)過(guò)程中會(huì)涉及到以下項(xiàng)目:

名稱

作用

語(yǔ)言

版本

java-demo

發(fā)送 gRPC 請(qǐng)求的客戶端

Java

opentelemetry-agent: 2.4.0/SpringBoot: 2.7.14

k8s-combat

提供 gRPC 服務(wù)的服務(wù)端

Golang

go.opentelemetry.io/otel: 1.28/ Go: 1.22

Jaeger

trace 存儲(chǔ)的服務(wù)端以及 TraceUI 展示

Golang

jaegertracing/all-in-one:1.56

opentelemetry-collector-contrib

OpenTelemetry 的 collector 服務(wù)端,用于收集 trace/metrics/logs 然后寫入到遠(yuǎn)端存儲(chǔ)

Golang

otel/opentelemetry-collector-contrib:0.98.0

圖片圖片

在開始之前我們先看看實(shí)際的效果,我們需要先把 collector 和 Jaeger 部署好:

docker run --rm -d --name jaeger \
  -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 4317:4317 \
  -p 4318:4318 \
  -p 14250:14250 \
  -p 14268:14268 \
  -p 14269:14269 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.56


docker run --rm -d -v $(pwd)/coll-config.yaml:/etc/otelcol-contrib/config.yaml --name coll \
-p 5318:4318 \
-p 5317:4317 \
otel/opentelemetry-collector-contrib:0.98.0

這里有一個(gè) coll-config 的配置文件如下:

receivers:
  otlp:
    protocols:
      grpc:
      http:
exporters:
  debug:
  otlp:
    endpoint: "127.0.0.1:4317"
    tls:
      insecure: true
processors:
  batch:
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp, debug]

重點(diǎn)是這里的 endpoint: "127.0.0.1:4317" 我們需要配置位 Jaeger 的 IP 和端口。

更多關(guān)于這里的配置會(huì)在后續(xù)單獨(dú)的 collector 章節(jié)中講解。

這兩個(gè)服務(wù)都啟動(dòng)成功后再啟動(dòng)我們的 Java 客戶端和  Go  服務(wù)端:

java -javaagent:opentelemetry-javaagent-2.4.0-SNAPSHOT.jar \
-Dotel.traces.exporter=otlp \
-Dotel.metrics.exporter=otlp \
-Dotel.logs.exporter=none \
-Dotel.service.name=demo \
-Dotel.exporter.otlp.protocol=grpc \
-Dotel.propagators=tracecontext,baggage \
-Dotel.exporter.otlp.endpoint=http://127.0.0.1:5317 \
      -jar target/demo-0.0.1-SNAPSHOT.jar

# Golang
export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:5317
export OTEL_RESOURCE_ATTRIBUTES=service.name=k8s-combat
./k8s-combat

可以看到不管是 Java 還是 Golang 應(yīng)用都是需要配置 OTEL_EXPORTER_OTLP_ENDPOINT 參數(shù),也就是 opentelemetry-collector-contrib 的地址。

其余的一些配置在后面會(huì)講到。

curl http://127.0.0.1:9191/request\?name\=1232

然后我們觸發(fā)一下 Java 客戶端的入口,就可以在 JaegerUI 中查詢到剛才的鏈路了。http://localhost:16686/search

圖片圖片

圖片這樣整個(gè) trace 鏈路就串起來(lái)了。

Java 應(yīng)用

下面來(lái)看看具體的應(yīng)用代碼里是如何編寫的。

Java 是基于 springboot 編寫的,具體 springboot 的使用就不再贅述了。

因?yàn)槲覀儜?yīng)用是使用 gRPC 通信的,所以需要提供一個(gè) helloworld.proto 的 pb 文件:

syntax = "proto3";  
  
option go_package = "google.golang.org/grpc/examples/helloworld/helloworld";  
option java_multiple_files = true;  
option java_package = "io.grpc.examples.helloworld";  
option java_outer_classname = "HelloWorldProto";  
  
package helloworld;  
  
// The greeting service definition.  
service Greeter {  
  // Sends a greeting  
  rpc SayHello (HelloRequest) returns (HelloReply) {}  
}  
  
// The request message containing the user's name.  
message HelloRequest {  
  string name = 1;  
}  
  
// The response message containing the greetings  
message HelloReply {  
  string message = 1;  
}

這個(gè)文件也沒(méi)啥好說(shuō)的,就定義了一個(gè)簡(jiǎn)單的 SayHello 接口。

<dependency>  
  <groupId>net.devh</groupId>  
  <artifactId>grpc-spring-boot-starter</artifactId>  
  <version>3.1.0.RELEASE</version>  
</dependency>  
  
<dependency>  
  <groupId>io.grpc</groupId>  
  <artifactId>grpc-stub</artifactId>  
  <version>${grpc.version}</version>  
</dependency>  
<dependency>  
  <groupId>io.grpc</groupId>  
  <artifactId>grpc-protobuf</artifactId>  
  <version>${grpc.version}</version>  
</dependency>

在 Java 中使用了 grpc-spring-boot-starter 這個(gè)庫(kù)來(lái)處理 gRPC 的客戶端和服務(wù)端請(qǐng)求。

grpc:  
  server:  
    port: 9192  
  client:  
    greeter:  
      address: 'static://127.0.0.1:50051'  
      enableKeepAlive: true  
      keepAliveWithoutCalls: true  
      negotiationType: plaintext

然后我們定義了一個(gè)接口用于接收請(qǐng)求觸發(fā) gRPC 的調(diào)用:

@RequestMapping("/request")  
    public String request(@RequestParam String name) {  
       log.info("request: {}", request);    
       HelloReply abc = greeterStub.sayHello(io.grpc.examples.helloworld.HelloRequest.newBuilder().setName(request.getName()).build());   
       return abc.getMessage();  
    }

Java 應(yīng)用的實(shí)現(xiàn)非常簡(jiǎn)單,和我們?nèi)粘H粘i_發(fā)沒(méi)有任何區(qū)別;唯一的區(qū)別就是在啟動(dòng)時(shí)需要加入一個(gè) javaagent以及一些啟動(dòng)參數(shù)。

java -javaagent:opentelemetry-javaagent-2.4.0-SNAPSHOT.jar \
-Dotel.traces.exporter=otlp \
-Dotel.metrics.exporter=otlp \
-Dotel.logs.exporter=none \
-Dotel.service.name=demo \
-Dotel.exporter.otlp.protocol=grpc \
-Dotel.propagators=tracecontext,baggage \
-Dotel.exporter.otlp.endpoint=http://127.0.0.1:5317 \
      -jar target/demo-0.0.1-SNAPSHOT.jar

下面來(lái)仔細(xì)看看這些參數(shù)

名稱

作用

javaagent:opentelemetry-javaagent-2.4.0-SNAPSHOT.jar

這個(gè)沒(méi)啥好說(shuō)的,指定一個(gè) javaagent

otel.traces.exporter

指定 trace 以什么格式傳輸(默認(rèn)是這里的 otlp);當(dāng)然還有其他的值:logging/jaeger/zipkin 等,我們這里使用 otlp 會(huì)將數(shù)據(jù)傳輸?shù)?collector 中。

otel.metrics.exporter

同上,只是指定的是 metrics 的傳輸方式,我們?cè)谥笾v解指標(biāo)的時(shí)候會(huì)用到。

otel.service.name

定義在 trace 中的應(yīng)用名稱,springboot 會(huì)默認(rèn)使用 spring.application.name 這個(gè)變量。

otel.exporter.otlp.protocol

指定傳輸協(xié)議;除了 grpc 之外還有 http/protobuf,當(dāng)然我們也可以根據(jù) trace 和 metrics 分開指定:otel.exporter.otlp.traces.protocol/otel.exporter.otlp.metrics.protocol

otel.propagators

指定我們跨服務(wù)傳播上下文的時(shí)候使用哪種格式,默認(rèn)是 W3C Trace Context,baggage,當(dāng)然也有其他的- "b3": B3 Single,- "xray": AWS X-Ray,"jaeger": Jaeger等

otel.exporter.otlp.endpoint

指定 collector 的 endpoint

更多細(xì)節(jié)的參數(shù)大家可以在這里找到:


https://opentelemetry.io/docs/languages/java/configuration/


Golang 應(yīng)用

接著我們來(lái)看看 Go 是如何集成 OpenTelemetry 的。

在創(chuàng)建好項(xiàng)目之后我們需要添加 OpenTelemetry 所提供的包:

go get "go.opentelemetry.io/otel" \
  "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" \
  "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" \
  "go.opentelemetry.io/otel/propagation" \
  "go.opentelemetry.io/otel/sdk/metric" \
  "go.opentelemetry.io/otel/sdk/resource" \
  "go.opentelemetry.io/otel/sdk/trace" \       "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"\

然后我們需要?jiǎng)?chuàng)建一個(gè)初始化 tracer 的函數(shù):

func initTracerProvider() *sdktrace.TracerProvider {
 ctx := context.Background()

 exporter, err := otlptracegrpc.New(ctx)
 if err != nil {
  log.Printf("new otlp trace grpc exporter failed: %v", err)
 }
 tp := sdktrace.NewTracerProvider(
  sdktrace.WithBatcher(exporter),
  sdktrace.WithResource(initResource()),
 )
 otel.SetTracerProvider(tp)
 otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
 return tp
}

因?yàn)槲覀兪褂玫氖?nbsp;grpc 協(xié)議上報(bào) otlp 數(shù)據(jù),所以這里使用的是 exporter, err := otlptracegrpc.New(ctx)  創(chuàng)建了一個(gè) exporter。

otel.SetTextMapPropagator() 這個(gè)函數(shù)里配置數(shù)據(jù)和剛才 Java 里配置的 -Dotel.propagators=tracecontext,baggage 是一樣的效果。

與此同時(shí)我們還需要提供一個(gè) initResource() 的函數(shù):

func initResource() *sdkresource.Resource {
 initResourcesOnce.Do(func() {
  extraResources, _ := sdkresource.New(
   context.Background(),
   sdkresource.WithOS(),
   sdkresource.WithProcess(),
   sdkresource.WithContainer(),
   sdkresource.WithHost(),
  )
  resource, _ = sdkresource.Merge(
   sdkresource.Default(),
   extraResources,
  )
 })
 return resource
}

這個(gè)函數(shù)用來(lái)告訴 trace 需要暴露那些 resource,也就是我們?cè)谶@里看到進(jìn)程相關(guān)的屬性:

圖片圖片

比如這里的 sdkresource.WithOS(), 就會(huì)顯示 OS 的類型和描述。

func WithOS() Option {  
    return WithDetectors(  
       osTypeDetector{},  
       osDescriptionDetector{},  
    )}

而 sdkresource.WithProcess(), 顯示的數(shù)據(jù)就更多了。

func WithProcess() Option {  
    return WithDetectors(  
       processPIDDetector{},  
       processExecutableNameDetector{},  
       processExecutablePathDetector{},  
       processCommandArgsDetector{},  
       processOwnerDetector{},  
       processRuntimeNameDetector{},  
       processRuntimeVersionDetector{},  
       processRuntimeDescriptionDetector{},  
    )}

以上這些代碼在 Java 中都是由 agent 指定創(chuàng)建的。


// Init OpenTelemetry start  
tp := initTracerProvider()  
defer func() {  
    if err := tp.Shutdown(context.Background()); err != nil {  
       log.Printf("Error shutting down tracer provider: %v", err)  
    }}()  
   
err := runtime.Start(runtime.WithMinimumReadMemStatsInterval(time.Second))  
if err != nil {  
    log.Err(err)  
}
tracer = tp.Tracer("k8s-combat")
// Init OpenTelemetry end

之后我們需要在 main 函數(shù)一開始就初始化 traceProvider。

對(duì)于 grpc 來(lái)說(shuō),OpenTelemetry 的 Go-SDK 提供了自動(dòng)埋點(diǎn),但我們也得手動(dòng)配置一下:

s := grpc.NewServer(  
    grpc.StatsHandler(otelgrpc.NewServerHandler()),  
)  
pb.RegisterGreeterServer(s, &server{})

使用 grpc.StatsHandler(otelgrpc.NewServerHandler()),  將 OTel 的 serverHandle 加入進(jìn)去,這個(gè) handle 會(huì)自動(dòng)創(chuàng)建 grpc 服務(wù)端的 span。

對(duì) trace/span 概念還有不了解的朋友可以查看這篇文章。

var port = ":50051"  
lis, err := net.Listen("tcp", port)  
if err != nil {  
    log.Fatal().Msgf("failed to listen: %v", err)  
}  
s := grpc.NewServer(  
    grpc.StatsHandler(otelgrpc.NewServerHandler()),  
)  
pb.RegisterGreeterServer(s, &server{})  
if err := s.Serve(lis); err != nil {  
    log.Fatal().Msgf("failed to serve: %v", err)  
} else {  
    log.Printf("served on %s \n", port)  
}

接著我們只需要啟動(dòng)這個(gè) grpc 服務(wù)即可,就算完成了 Go 服務(wù)的集成。

從這里可以看出 Java 相對(duì)于 Go 來(lái)說(shuō)會(huì)簡(jiǎn)單許多,只需要配置一個(gè) agent 就可以不該一行代碼支持目前市面上流行的絕大多數(shù)框架。

圖片圖片

自定義  span 的 attribute

我們?cè)诳存溌沸畔⒌臅r(shí)候其實(shí)看的最多的是某個(gè) span 里的 attribute 數(shù)據(jù)(有些地方又稱為 tag) 如下圖所示:

圖片圖片

這里會(huì)展示當(dāng)前 span 的各種信息,但如果我們想要額外加一些自己關(guān)心的數(shù)據(jù)應(yīng)該如何添加呢?

message HelloRequest {  
  string name = 1;  
}

比如我們想知道這個(gè) grpc 接口里的 name 參數(shù),如上圖所示那樣展示在 span 中。

好在 OpenTelemetry 已經(jīng)考慮到類似的需求:

span := trace.SpanFromContext(ctx)  
span.SetAttributes(attribute.String("request.name", in.Name))

我們使用 span := trace.SpanFromContext(ctx)  獲取到當(dāng)前的 span,然后調(diào)用 SetAttributes 就可以添加自定義的數(shù)據(jù)了。

對(duì)應(yīng)的 Java 也有類似的函數(shù)。

除了新增 attribute 之外還可以新增 Event,Link 等數(shù)據(jù),使用方式也是類似的。

// AddEvent adds an event with the provided name and options.  
AddEvent(name string, options ...EventOption)  
  
// AddLink adds a link.  
// Adding links at span creation using WithLinks is preferred to calling AddLink  
// later, for contexts that are available during span creation, because head  
// sampling decisions can only consider information present during span creation.  
AddLink(link Link)

自定義新增 span

同理我們可能不局限于為某個(gè) span 新增 attribute,也有可能想要新增一個(gè)新的 span 來(lái)記錄關(guān)鍵的調(diào)用信息。

默認(rèn)情況下只有 OpenTelemetry 實(shí)現(xiàn)過(guò)的組件的核心函數(shù)才會(huì)有 span,自己代碼里的函數(shù)調(diào)用是不會(huì)創(chuàng)建span 的。

func (s *server) span(ctx context.Context) {  
    ctx, span := tracer.Start(ctx, "hello-span")  
    defer span.End()  
    // do some work  
    log.Printf("create span")  
}

在  Go 中只需要手動(dòng) Start 一個(gè) span 即可。

對(duì)應(yīng)到 Java 稍微簡(jiǎn)單一些,只需要為函數(shù)添加一個(gè)注解即可。

@WithSpan("span")  
public void span(@SpanAttribute("request.name") String name) {  
    TimeUnit.SECONDS.sleep(1);  
    log.info("span:{}", name);  
}

只不過(guò)得單獨(dú)引入一個(gè)依賴:

<dependency>  
  <groupId>io.opentelemetry</groupId>  
  <artifactId>opentelemetry-api</artifactId>  
</dependency>  
  
<dependency>  
  <groupId>io.opentelemetry.instrumentation</groupId>  
  <artifactId>opentelemetry-instrumentation-annotations</artifactId>  
  <version>2.3.0</version>  
</dependency>

最終我們?cè)?Jaeger UI 上看到的效果如下:

圖片圖片

總結(jié)

圖片圖片

最后總結(jié)一下,OpenTelemetry 支持許多流行的語(yǔ)言,主要分為兩類:是否支持自動(dòng)埋點(diǎn)。

圖片圖片

這里 Go 也可以零代碼埋點(diǎn),是使用了 eBPF,本文暫不做介紹。

對(duì)于支持自動(dòng)埋點(diǎn)的語(yǔ)言就很簡(jiǎn)單,只需要配置下 agent 即可;而原生的 Go 語(yǔ)言不支持自動(dòng)埋點(diǎn)就得手動(dòng)使用 OpenTelemetry 提供的 SDK 處理一些關(guān)鍵步驟;總體來(lái)說(shuō)也不算復(fù)雜。

參考鏈接:

  • https://opentelemetry.io/docs/languages/java/configuration/
  • https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/supported-libraries.md
  • https://crossoverjie.top/2024/06/06/ob/OpenTelemetry-trace-concept/
責(zé)任編輯:武曉燕 來(lái)源: crossoverJie
相關(guān)推薦

2020-12-16 09:24:18

Skywalking分布式鏈路追蹤

2024-06-07 13:04:31

2023-11-21 08:25:09

2024-01-26 07:49:49

Go分布式鏈路

2024-06-07 07:41:03

2021-02-22 07:58:51

分布式鏈路追蹤

2024-11-28 08:57:21

分布式鏈路Skywalking

2023-10-16 23:43:52

云原生可觀測(cè)性

2020-09-11 09:44:04

微服務(wù)分布式鏈路

2024-07-09 08:11:56

2022-05-23 08:23:24

鏈路追蹤SleuthSpring

2024-08-28 08:09:13

contextmetrics類型

2021-11-08 14:10:37

分布式Spring鏈路

2024-10-24 08:51:19

分布式鏈路項(xiàng)目

2022-11-26 09:49:07

分布式鏈路追蹤技術(shù)

2022-08-05 10:03:17

分布式微服務(wù)

2020-05-26 11:59:30

日志鏈路微服務(wù)架構(gòu)

2023-10-26 00:00:00

分布式系統(tǒng)定位

2020-10-19 07:30:57

Java Redis 開發(fā)

2017-09-01 05:35:58

分布式計(jì)算存儲(chǔ)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)