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。

posted @ 2021-09-15 21:58  独揽风月  阅读(2544)  评论(0编辑  收藏  举报