1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 20669字,需瀏覽 42分鐘

         ·

        2024-05-14 20:52


        這篇文章中我們會討論可觀測性概念,并了解了有關(guān) OpenTelemetry 的一些細節(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)控以理解一些觀測細節(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秒進行一次定期讀取(默認(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

        這篇文章我們詳細展示了如何使用 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
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美三级免费看 | 国产成人av | 亚洲四虎久久草草四虎 | 爆肏熟妇视频 | 91丝袜呻吟高潮美腿白嫩在线观看 | 久久葡京 | 日本久久免费 | 鸡巴在线观看 | 蜜桃91精品秘 入口内裤 | 久久久久久久久久久高清毛片一级 |