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。