前言
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。