<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          使用 OpenTelemetry 實現(xiàn) Golang 服務(wù)的可觀測系統(tǒng)

          共 20669字,需瀏覽 42分鐘

           ·

          2024-05-14 20:52


          這篇文章中我們會討論可觀測性概念,并了解了有關(guān) OpenTelemetry 的一些細(xì)節(jié),然后會在 Golang 服務(wù)中對接 OpenTelemetry 實現(xiàn)分布式系統(tǒng)可觀測性。

          Test Project

          我們將使用 Go 1.22 開發(fā)我們的測試服務(wù)。我們將構(gòu)建一個 API,返回服務(wù)的名稱及其版本。

          我們將把我們的項目分成兩個簡單的文件(main.go 和 info.go)。

          // file: main.go

          package main

          import (
             "log"
             "net/http"
          )

          const portNum string = ":8080"

          func main() {
             log.Println("Starting http server.")

             mux := http.NewServeMux()
             mux.HandleFunc("/info", info)

             srv := &http.Server{
                Addr:    portNum,
                Handler: mux,
             }

             log.Println("Started on port", portNum)
             err := srv.ListenAndServe()
             if err != nil {
                log.Println("Fail start http server.")
             }

          }

          // file: info.go

          package main

          import (
             "encoding/json"
             "net/http"
          )

          type InfoResponse struct {
             Version     string `json:"version"`
             ServiceName string `json:"service-name"`
          }

          func info(w http.ResponseWriter, r *http.Request) {
             w.Header().Set("Content-Type""application/json")
             response := InfoResponse{Version: "0.1.0", ServiceName: "otlp-sample"}
             json.NewEncoder(w).Encode(response)
          }

          使用 go run . 運行后,應(yīng)該在 console 中輸出:

          Starting http server.
          Started on port :8080

          訪問 localhost:8080 會顯示:

          // http://localhost:8080/info
          {
            "version""0.1.0",
            "service-name""otlp-sample"
          }

          現(xiàn)在我們的服務(wù)已經(jīng)可以運行了,現(xiàn)在要以對其進行監(jiān)控(或者配置我們的流水線)。在這里,我們將執(zhí)行手動監(jiān)控以理解一些觀測細(xì)節(jié)。

          First Steps

          第一步是安裝 Open Telemetry 的依賴。

          go get "go.opentelemetry.io/otel" \
                 "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" \
                 "go.opentelemetry.io/otel/metric" \
                 "go.opentelemetry.io/otel/sdk" \
                 "go.opentelemetry.io/otel/trace" \
                 "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"

          目前,我們只會安裝項目的初始依賴。這里我們將 OpenTelemetry 配置 otel.go文件。

          在我們開始之前,先看下配置的流水線:

          定義 Exporter

          為了演示簡單,我們將在這里使用 console Exporter 。

          // file: otel.go

          package main

          import (
             "context"
             "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
             "go.opentelemetry.io/otel/sdk/trace"
          )

          func newTraceExporter() (trace.SpanExporter, error) {
             return stdouttrace.New(stdouttrace.WithPrettyPrint())
          }

          main.go 的代碼如下:

          // file: main.go

          package main


          import (
             "context"
             "log"
             "net/http"
          )

          const portNum string = ":8080"

          func main() {
             log.Println("Starting http server.")

             mux := http.NewServeMux()

             _, err := newTraceExporter()
             if err != nil {
                log.Println("Failed to get console exporter.")
             }

             mux.HandleFunc("/info", info)

             srv := &http.Server{
                Addr:    portNum,
                Handler: mux,
             }

             log.Println("Started on port", portNum)
             err := srv.ListenAndServe()
             if err != nil {
                log.Println("Fail start http server.")
             }

          }

          Trace

          我們的首個信號將是 Trace。為了與這個信號互動,我們必須創(chuàng)建一個 provider,如下所示。作為一個參數(shù),我們將擁有一個 Exporter,它將接收收集到的信息。

          // file: otel.go

          package main

          import (
             "context"
             "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
             "go.opentelemetry.io/otel/sdk/trace"
             "time"
          )

          func newTraceExporter() (trace.SpanExporter, error) {
             return stdouttrace.New(stdouttrace.WithPrettyPrint())
          }

          func newTraceProvider(traceExporter trace.SpanExporter) *trace.TracerProvider {
             traceProvider := trace.NewTracerProvider(
                trace.WithBatcher(traceExporter,
                   trace.WithBatchTimeout(time.Second)),
             )
             return traceProvider
          }

          在 main.go 文件中,我們將使用創(chuàng)建跟蹤提供程序的函數(shù)。

          // file: main.go

          package main


          import (
             "context"
             "go.opentelemetry.io/otel"
             "log"
             "net/http"
          )

          const portNum string = ":8080"

          func main() {
             log.Println("Starting http server.")

             mux := http.NewServeMux()
             ctx := context.Background()

             consoleTraceExporter, err := newTraceExporter()
             if err != nil {
                log.Println("Failed get console exporter.")
             }

             tracerProvider := newTraceProvider(consoleTraceExporter)

             defer tracerProvider.Shutdown(ctx)
             otel.SetTracerProvider(tracerProvider)

             mux.HandleFunc("/info", info)

             srv := &http.Server{
                Addr:    portNum,
                Handler: mux,
             }

             log.Println("Started on port", portNum)
             err = srv.ListenAndServe()
             if err != nil {
                log.Println("Fail start http server.")
             }

          }

          請注意,在實例化一個 provider 時,我們必須保證它會“關(guān)閉”。這樣可以避免內(nèi)存泄露。

          現(xiàn)在我們的服務(wù)已經(jīng)配置了一個 trace provider,我們準(zhǔn)備好收集數(shù)據(jù)了。讓我們調(diào)用 “/info” 接口來產(chǎn)生數(shù)據(jù)。

          // file: info.go

          package main

          import (
             "encoding/json"
             "go.opentelemetry.io/otel"
             "net/http"
          )

          type InfoResponse struct {
             Version     string `json:"version"`
             ServiceName string `json:"service-name"`
          }

          var (
             tracer = otel.Tracer("info-service")
          )

          func info(w http.ResponseWriter, r *http.Request) {
             _, span := tracer.Start(r.Context(), "info")
             defer span.End()

             w.Header().Set("Content-Type""application/json")
             response := InfoResponse{Version: "0.1.0", ServiceName: "otlp-sample"}
             json.NewEncoder(w).Encode(response)
          }

          tracer = otel.Tracer(“info-service”) 將在我們已經(jīng)在 main.go 中注冊的全局 trace provider 中創(chuàng)建一個命名的跟蹤器。如果未提供名稱,則將使用默認(rèn)名稱。

          tracer.Start(r.Context(), “info”) 創(chuàng)建一個 Span 和一個包含新創(chuàng)建的 spancontext.Context。如果 "ctx" 中提供的 context.Context 包含一個 Span,那么新創(chuàng)建的 Span 將是該 Span 的子 Span,否則它將是根 Span

          Span 對我們來說是一個新的概念。Span 代表一個工作單元或操作。Span 是跟蹤(Traces)的構(gòu)建塊。

          同樣地,正如提供程序一樣,我們必須始終關(guān)閉 Spans 以避免“內(nèi)存泄漏”。

          現(xiàn)在,我們的端點已經(jīng)被監(jiān)控,我們可以在控制臺中查看我們的觀測數(shù)據(jù)。

          {
           "Name":"info",
           "SpanContext":{
             "TraceID":"6216cbe99bfd1165974dc2bda24e0d5c",
             "SpanID":"728454ee6b9a72e3",
             "TraceFlags":"01",
             "TraceState":"",
             "Remote":false
           },
           "Parent":{
             "TraceID":"00000000000000000000000000000000",
             "SpanID":"0000000000000000",
             "TraceFlags":"00",
             "TraceState":"",
             "Remote":false
           },
           "SpanKind":1,
           "StartTime":"2024-03-02T23:39:51.791979-03:00",
           "EndTime":"2024-03-02T23:39:51.792140908-03:00",
           "Attributes":null,
           "Events":null,
           "Links":null,
           "Status":{
             "Code":"Unset",
             "Description":""
           },
           "DroppedAttributes":0,
           "DroppedEvents":0,
           "DroppedLinks":0,
           "ChildSpanCount":0,
           "Resource":[
             {
               "Key":"service.name",
               "Value":{
                 "Type":"STRING",
                 "Value":"unknown_service:otlp-golang"
               }
             },
             {
               "Key":"telemetry.sdk.language",
               "Value":{
                 "Type":"STRING",
                 "Value":"go"
               }
             },
             {
               "Key":"telemetry.sdk.name",
               "Value":{
                 "Type":"STRING",
                 "Value":"opentelemetry"
               }
             },
             {
               "Key":"telemetry.sdk.version",
               "Value":{
                 "Type":"STRING",
                 "Value":"1.24.0"
               }
             }
           ],
           "InstrumentationLibrary":{
             "Name":"info-service",
             "Version":"",
             "SchemaURL":""
           }
          }

          添加 Metrics

          我們已經(jīng)有了我們的 tracing 配置。現(xiàn)在來添加我們的第一個指標(biāo)。

          首先,安裝并配置一個專門用于指標(biāo)的導(dǎo)出器。

          go get "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"

          通過修改我們的 otel.go 文件,我們將有兩個導(dǎo)出器:一個專門用于 tracing,另一個用于 metrics。

          // file: otel.go

          func newTraceExporter() (trace.SpanExporter, error) {
             return stdouttrace.New(stdouttrace.WithPrettyPrint())
          }

          func newMetricExporter() (metric.Exporter, error) {
             return stdoutmetric.New()
          }

          現(xiàn)在添加我們的 metrics Provider 實例化:

          // file: otel.go

          func newMeterProvider(meterExporter metric.Exporter) *metric.MeterProvider {
             meterProvider := metric.NewMeterProvider(
                metric.WithReader(metric.NewPeriodicReader(meterExporter,
                   metric.WithInterval(10*time.Second))),
             )
             return meterProvider
          }

          我將提供商的行為更改為每10秒進行一次定期讀?。J(rèn)為1分鐘)。

          在實例化一個 MeterProvide r時,我們將創(chuàng)建一個Meter。Meters 允許您創(chuàng)建您可以使用的儀器,以創(chuàng)建不同類型的指標(biāo)(計數(shù)器、異步計數(shù)器、直方圖、異步儀表、增減計數(shù)器、異步增減計數(shù)器……)。

          現(xiàn)在我們可以在 main.go 中配置我們的新 exporter 和 provider。

          // file: main.go

          func main() {
             log.Println("Starting http server.")

             mux := http.NewServeMux()
             ctx := context.Background()

             consoleTraceExporter, err := newTraceExporter()
             if err != nil {
                log.Println("Failed get console exporter (trace).")
             }

             consoleMetricExporter, err := newMetricExporter()
             if err != nil {
                log.Println("Failed get console exporter (metric).")
             }

             tracerProvider := newTraceProvider(consoleTraceExporter)

             defer tracerProvider.Shutdown(ctx)
             otel.SetTracerProvider(tracerProvider)

             meterProvider := newMeterProvider(consoleMetricExporter)

             defer meterProvider.Shutdown(ctx)
             otel.SetMeterProvider(meterProvider)

             mux.HandleFunc("/info", info)

             srv := &http.Server{
                Addr:    portNum,
                Handler: mux,
             }

             log.Println("Started on port", portNum)
             err = srv.ListenAndServe()
             if err != nil {
                log.Println("Fail start http server.")
             }
          }

          最后,讓我們測量我們想要的數(shù)據(jù)。我們將在 info.go 中做這件事,這與我們之前在 trace 中所做的非常相似。

          我們將使用 otel.Meter("info-service") 在已經(jīng)注冊的全局提供者上創(chuàng)建一個命名的計量器。我們還將通過 metric.Int64Counter 定義我們的測量工具。Int64Counter 是一種記錄遞增的 int64 值的工具。

          然而,與 trace不同,我們需要初始化我們的測量工具。我們將為我們的度量配置名稱、描述和單位。

          // file: info.go

          var (
             tracer      = otel.Tracer("info-service")
             meter       = otel.Meter("info-service")
             viewCounter metric.Int64Counter
          )

          func init() {
             var err error
             viewCounter, err = meter.Int64Counter("user.views",
                metric.WithDescription("The number of views"),
                metric.WithUnit("{views}"))
             if err != nil {
                panic(err)
             }
          }

          一旦完成這個步驟,我們就可以開始測量了。最終代碼看起來會像這樣:

          // file: info.go

          package main

          import (
             "encoding/json"
             "go.opentelemetry.io/otel"
             "go.opentelemetry.io/otel/metric"
             "net/http"
          )

          type InfoResponse struct {
             Version     string `json:"version"`
             ServiceName string `json:"service-name"`
          }

          var (
             tracer      = otel.Tracer("info-service")
             meter       = otel.Meter("info-service")
             viewCounter metric.Int64Counter
          )

          func init() {
             var err error
             viewCounter, err = meter.Int64Counter("user.views",
                metric.WithDescription("The number of views"),
                metric.WithUnit("{views}"))
             if err != nil {
                panic(err)
             }
          }

          func info(w http.ResponseWriter, r *http.Request) {
             ctx, span := tracer.Start(r.Context(), "info")
             defer span.End()

             viewCounter.Add(ctx, 1)

             w.Header().Set("Content-Type", "application/json")
             response := InfoResponse{Version: "0.1.0", ServiceName: "otlp-sample"}
             json.NewEncoder(w).Encode(response)
          }

          運行我們的服務(wù)時,每10秒系統(tǒng)將在控制臺顯示我們的數(shù)據(jù):


            "Resource":[
             {
               "Key":"service.name",
               "Value":{
                 "Type":"STRING",
                 "Value":"unknown_service:otlp-golang"
               }
             },
             {
               "Key":"telemetry.sdk.language",
               "Value":{
                 "Type":"STRING",
                 "Value":"go"
               }
             },
             {
               "Key":"telemetry.sdk.name",
               "Value":{
                 "Type":"STRING",
                 "Value":"opentelemetry"
               }
             },
             {
               "Key":"telemetry.sdk.version",
               "Value":{
                 "Type":"STRING",
                 "Value":"1.24.0"
               }
             }
           ],
           "ScopeMetrics":[
             {
               "Scope":{
                 "Name":"info-service",
                 "Version":"",
                 "SchemaURL":""
               },
               "Metrics":[
                 {
                   "Name":"user.views",
                   "Description":"The number of views",
                   "Unit":"{views}",
                   "Data":{
                     "DataPoints":[
                       {
                         "Attributes":[


                         ],
                         "StartTime":"2024-03-03T08:50:39.07383-03:00",
                         "Time":"2024-03-03T08:51:45.075332-03:00",
                         "Value":1
                       }
                     ],
                     "Temporality":"CumulativeTemporality",
                     "IsMonotonic":true
                   }
                 }
               ]
             }
           ]
          }

          Context

          為了將追蹤信息發(fā)送出去,我們需要傳播上下文。為了做到這一點,我們必須注冊一個傳播器。我們將在 otel.go和main.go 中實現(xiàn),跟追 Tracing 和 metric 的實現(xiàn)差不多。

          // file: otel.go

          func newPropagator() propagation.TextMapPropagator {
             return propagation.NewCompositeTextMapPropagator(
                propagation.TraceContext{},
             )
          }
          // file: main.go 

          prop := newPropagator()
          otel.SetTextMapPropagator(prop)

          HTTP Server

          我們將通過觀測數(shù)據(jù)來豐富我們的 HTTP 服務(wù)器以完成我們的監(jiān)控。為此我們將使用帶有 OTel 的 http handler 。

          // main.go


          handleFunc := func(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) {
             handler := otelhttp.WithRouteTag(pattern, http.HandlerFunc(handlerFunc))
             mux.Handle(pattern, handler)
          }


          handleFunc("/info", info)
          newHandler := otelhttp.NewHandler(mux, "/")


          srv := &http.Server{
             Addr:    portNum,
             Handler: newHandler,
          }

          因此,我們將在我們的收集到的數(shù)據(jù)中獲得來自 HTTP 服務(wù)器的額外信息(用戶代理、HTTP方法、協(xié)議、路由等)。

          Conclusion

          這篇文章我們詳細(xì)展示了如何使用 Go 來對接 OpenTelemetry 以實現(xiàn)完整的可觀測系統(tǒng),這里使用 console Exporter 僅作演示使用 ,在實際的開發(fā)中我們可能需要使用更加強大的 Exporter 將數(shù)據(jù)可視化,比如可以使用 Google Cloud Trace[1] 來將數(shù)據(jù)直接導(dǎo)出到 Goole Cloud Monitoring 。

          References

          OpenTelemetry[2]The Future of Observability with OpenTelemetry[3]Cloud-Native Observability with OpenTelemetry[4]Learning OpenTelemetry[5]

          參考資料
          [1]

          google cloud opentelementry: github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace

          [2]

          OpenTelementry: https://opentelemetry.io/

          [3]

          The furure of observability: https://learning.oreilly.com/library/view/the-future-of/9781098118433/

          [4]

          Cloud-Native Observisability with Opentelementry: https://learning.oreilly.com/library/view/cloud-native-observability-with/9781801077705/

          [5]

          Learning OpenTelementry: https://learning.oreilly.com/library/view/learning-opentelemetry/9781098147174/

          瀏覽 68
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本视频,日本高清视频 | 尹人大香蕉网 | 蜜桃臀美女被操 | 7777偷窥盗摄视频 | 亚洲黄色电影在线观看 |