golang使用skywalking


前言

skywalking是業界比較常用的一款APM監控工具,采用java開發,對java應用適配比較好,應用不需要埋點上報,只需要在啟動時加上 -javaagent: 參數即可。

而對於go應用想要上報指標到skywalking,則需要通過埋點的方式注入。skywalking官方提供了golang版的庫github.com/SkyAPM/go2sky

安裝skywalking

使用docker-compose安裝,先准備yaml文件:

version: '3.3'
services:
  skywalking-oap:
    image: apache/skywalking-oap-server:8.0.1-es7
    container_name: skywalking-oap
    restart: always
    ports:
      - 11800:11800
      - 12800:12800
    environment:
      SW_STORAGE: h2
  skywalking-ui:
    image: apache/skywalking-ui:8.0.1
    container_name: skywalking-ui
    depends_on:
      - skywalking-oap
    links:
      - skywalking-oap
    restart: always
    ports:
      - 8080:8080
    environment:
      SW_OAP_ADDRESS: skywalking-oap:12800

#安裝命令: 
docker-compose -f skywalking-docker-compose.yaml up -d

訪問http://127.0.0.1:8080,即可進入skywalking首頁

上報數據

測試文件地址:http://gitee.com/zqwlai/skywalking/test.go

package main

import (
	"context"
	"github.com/SkyAPM/go2sky"
	"github.com/SkyAPM/go2sky/reporter"
	"log"
)

func main(){
	r, err := reporter.NewGRPCReporter("127.0.0.1:11800")
	if err != nil {
		log.Fatalf("new reporter error %v \n", err)
	}
	defer r.Close()
	tracer, err := go2sky.NewTracer("example", go2sky.WithReporter(r))

	span, ctx, err := tracer.CreateLocalSpan(context.Background())
	subSpan, _, err := tracer.CreateLocalSpan(ctx)

	subSpan.End()    //調用End方法觸發上報
	span.End()
}

和所有的鏈路監控工具一樣,skywalking也遵循Open Tracing協議,首先需要創建一個Trace,表示一個調用鏈,然后再調用鏈上創建span和子span,每個span表示一次調用,因為span和子span是有關聯關系的,所以通過span和子span可以了解鏈路的上下游調用情況。

在go-sky里,可以創建三種類型的span

  • LocalSpan:可以用來表示本程序內的一次調用。

    span, ctx, err := tracer.CreateLocalSpan(context.Background())
    
  • EntrySpan:用來從下游服務提取context信息。

    span, ctx, err := h.tracer.CreateEntrySpan(r.Context(), getOperationName(h.name, r), func() (string, error) {
    		return r.Header.Get(propagation.Header), nil
    	})
    
  • ExitSpan: 用來向上游服務注入context信息。

    span, err := t.tracer.CreateExitSpan(req.Context(), getOperationName(t.name, req), req.Host, func(header string) error {
    		req.Header.Set(propagation.Header, header)
    		return nil
    	})
    

HTTP請求時如何上報數據

官方包里給出了測試demo,我這里拆成了兩部分,方便理解:

客戶端

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/SkyAPM/go2sky"
	httpPlugin "github.com/SkyAPM/go2sky/plugins/http"
	"github.com/SkyAPM/go2sky/reporter"
)

func main() {
	r, err := reporter.NewGRPCReporter("127.0.0.1:11800")
	if err != nil {
		log.Fatalf("new reporter error %v \n", err)
		return
	}

	tracer, err := go2sky.NewTracer("client", go2sky.WithReporter(r))
	if err != nil {
		log.Fatalf("create tracer error %v \n", err)
	}

	client, err := httpPlugin.NewClient(tracer)
	if err != nil {
		log.Fatalf("create client error %v \n", err)
	}

	// call server
	request, err := http.NewRequest("GET", fmt.Sprintf("http://127.0.0.1:2046/hello"), nil)
	if err != nil {
		log.Fatalf("unable to create http request: %+v\n", err)
	}

	res, err := client.Do(request)
	if err != nil {
		log.Fatalf("unable to do http request: %+v\n", err)
	}
	_ = res.Body.Close()
	time.Sleep(time.Second)

}

服務端

package main

import (
   "log"
   "net/http"

   "fmt"
   "github.com/SkyAPM/go2sky"
   httpPlugin "github.com/SkyAPM/go2sky/plugins/http"
   "github.com/SkyAPM/go2sky/reporter"
)



type emptyHandler struct {
}

func (h emptyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   // r.URL = /hello
   fmt.Println(222, r.URL)
}

func main() {
   r, err := reporter.NewGRPCReporter("127.0.0.1:11800")
   if err != nil {
      log.Fatalf("new reporter error %v \n", err)
      return
   }

   tracer, err := go2sky.NewTracer("server", go2sky.WithReporter(r))
   if err != nil {
      log.Fatalf("create tracer error %v \n", err)
   }

   sm, err := httpPlugin.NewServerMiddleware(tracer)
   if err != nil {
      log.Fatalf("create server middleware error %v \n", err)
   }

   //http.HandleFunc("/", serveHTTP)
   http.ListenAndServe("127.0.0.1:2046", sm(emptyHandler{}))
}

上報結果如圖所示:

代碼分析

總結:其實在發起http請求時,也是注入式地上報skywalking,只不過通過github.com/SkyAPM/go2sky/plugins/http這個包進行了封裝而已,下面介紹一下關鍵代碼。

客戶端在發起http請求時,會執行github.com/SkyAPM/go2sky/plugins/http/client.go里的RoundTrip()方法

func (t *transport) RoundTrip(req *http.Request) (res *http.Response, err error) {
    //將span信息寫入到HTTP頭里傳遞到服務端
	span, err := t.tracer.CreateExitSpan(req.Context(), getOperationName(t.name, req), req.Host, func(header string) error {
		req.Header.Set(propagation.Header, header)
		return nil
	})
	if err != nil {
		return t.delegated.RoundTrip(req)
	}
    //退出時觸發上報
	defer span.End()
    //給span打標簽
	span.SetComponent(componentIDGOHttpClient)
	for k, v := range t.extraTags {
		span.Tag(go2sky.Tag(k), v)
	}
	span.Tag(go2sky.TagHTTPMethod, req.Method)
	span.Tag(go2sky.TagURL, req.URL.String())
	span.SetSpanLayer(v3.SpanLayer_Http)
    //發起請求
	res, err = t.delegated.RoundTrip(req)
	if err != nil {
		span.Error(time.Now(), err.Error())
		return
	}
	span.Tag(go2sky.TagStatusCode, strconv.Itoa(res.StatusCode))
	if res.StatusCode >= http.StatusBadRequest {
		span.Error(time.Now(), "Errors on handling client")
	}
	return res, nil
}

其核心邏輯是創建span,並將span信息寫入到header里來傳遞到上游服務。

再看看Server端,Server端在處理請求時,會執行github.com/SkyAPM/go2sky/plugins/http/server.go里的ServeHTTP()方法:

func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   span, ctx, err := h.tracer.CreateEntrySpan(r.Context(), getOperationName(h.name, r), func() (string, error) {
      return r.Header.Get(propagation.Header), nil
   })
   if err != nil {
      if h.next != nil {
         h.next.ServeHTTP(w, r)
      }
      return
   }
   span.SetComponent(componentIDGOHttpServer)
   for k, v := range h.extraTags {
      span.Tag(go2sky.Tag(k), v)
   }
   span.Tag(go2sky.TagHTTPMethod, r.Method)
   span.Tag(go2sky.TagURL, fmt.Sprintf("%s%s", r.Host, r.URL.Path))
   span.SetSpanLayer(v3.SpanLayer_Http)

   rww := &responseWriterWrapper{w: w, statusCode: 200}
   //處理完請求后觸發上報
   defer func() {
      code := rww.statusCode
      if code >= 400 {
         span.Error(time.Now(), "Error on handling request")
      }
      span.Tag(go2sky.TagStatusCode, strconv.Itoa(code))
      span.End()
   }()
   if h.next != nil {
      //服務端處理請求
      h.next.ServeHTTP(rww, r.WithContext(ctx))
   }
}

其核心邏輯是從header里解析出下游的span信息,並基於此構造自己的span,並將span上報到skywalking。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM