使用skywalking監控gin web服務

【導讀】本文介紹了分散式開源分散式tracing框架Skywalking的使用。

簡介

SkyWalking 是一個開源的 APM 系統,包括對 Cloud Native 架構中分散式系統的監控、跟蹤、診斷能力。核心功能如下。

服務、服務例項、端點指標分析

根本原因分析。在執行時分析程式碼

服務拓撲圖分析

服務、服務例項和端點依賴分析

檢測到緩慢的服務和端點

效能最佳化

分散式跟蹤和上下文傳播

資料庫訪問指標。檢測慢速資料庫訪問語句(包括 SQL 語句)

警報

瀏覽器效能監控

基礎設施(VM、網路、磁碟等)監控

跨指標、跟蹤和日誌的協作

使用skywalking監控gin web服務

skywalking-arch

快速安裝 skywalking

skywalking 支援多種安裝方式,二進位制安裝,docker 安裝以及 helm、k8s 安裝等。

這裡直接使用 docker-compose 進行快速部署測試

docker-compose。yaml

version: ‘3。3’services: # storage elasticsearch: image: elasticsearch:6。8。16 container_name: elasticsearch restart: always ports: - 9200:9200 environment: discovery。type: single-node ulimits: memlock: soft: -1 hard: -1 volumes: - 。/elasticsearch/logs:/usr/share/elasticsearch/logs - 。/elasticsearch/data:/usr/share/elasticsearch/data - /etc/localtime:/etc/localtime # server oap: image: apache/skywalking-oap-server:8。6。0-es6 container_name: oap depends_on: - elasticsearch links: - elasticsearch restart: always ports: - 11800:11800 - 12800:12800 environment: SW_STORAGE: elasticsearch # 預設為 es6,es7 為 elasticsearch7 SW_STORAGE_ES_CLUSTER_NODES: elasticsearch:9200 volumes: - /etc/localtime:/etc/localtime # dashboard ui: image: apache/skywalking-ui:8。6。0 container_name: ui depends_on: - oap links: - oap restart: always ports: - 8080:8080 environment: SW_OAP_ADDRESS: oap:12800 volumes: - /etc/localtime:/etc/localtime

執行

1docker-compose up -d

訪問控制檯

瀏覽器訪問 http://localhost:8080

gin 接入 skywalking

這裡使用第三方庫 go2sky 以及 go2sky 的 gin 中介軟體

1go get -u github。com/SkyAPM/go2sky2go get github。com/SkyAPM/go2sky-plugins/gin/v3

程式碼示例

兩個 gin 服務 demo-server1 和 demo-server2,demo-server1 呼叫 demo-server2 demo-server2 提供 POST /user/info 介面 demo-server1 提供 GET /tracer 介面

demo-server2 程式碼如下

package mainimport ( “fmt” “time” “github。com/SkyAPM/go2sky” “github。com/SkyAPM/go2sky/reporter” “github。com/gin-gonic/gin” v3 “github。com/SkyAPM/go2sky-plugins/gin/v3”)const ( serverName = “demo-server2” serverPort = 8082)var skyAddr = “localhost:11800”type Params struct { Name string}func panicErr(err error) { if err != nil { panic(err) }}func main() { r := gin。Default() // skyAddr 是 skywaling 的 grpc 地址,預設是 localhost:11800, 預設心跳檢測時間是 1s rp, err := reporter。NewGRPCReporter(skyAddr, reporter。WithCheckInterval(5*time。Second)) panicErr(err) // 初始化一個 tracer,一個服務只需要一個 tracer,其含義是這個服務名稱 tracer, err := go2sky。NewTracer(serverName, go2sky。WithReporter(rp)) panicErr(err) // gin 使用 sky 自帶的 middleware r。Use(v3。Middleware(r, tracer)) // 自定義一個介面 r。POST(“/user/info”, func(context *gin。Context) { // LocalSpan 可以理解為本地日誌的 tracer,一般使用者當前應用 span, ctx, err := tracer。CreateLocalSpan(context。Request。Context()) panicErr(err) // 每一個 span 都有一個名字去標實操作的名稱! span。SetOperationName(“UserInfo”) // 記住重新設定一個 ctx,再其次這個 ctx 不是 gin 的 ctx,而是 http request 的 ctx context。Request = context。Request。WithContext(ctx) params := new(Params) err = context。BindJSON(params) panicErr(err) // 記錄日誌資訊 span。Log(time。Now(), “[UserInfo]”, fmt。Sprintf(serverName+“ satrt, req : %+v”, params)) local := gin。H{ “msg”: fmt。Sprintf(serverName+“ time : %s”, time。Now()。Format(“15:04:05。9999”)), } context。JSON(200, local) span。Log(time。Now(), “[UserInfo]”, fmt。Sprintf(serverName+“ end, resp : %s”, local)) // 切記最後要設定 span - end,不然就是一個非閉環的 span。End() }) r。Run(fmt。Sprintf(“:%d”, serverPort))}

demo-server1 程式碼如下

package mainimport ( “bytes” “encoding/json” “fmt” “io/ioutil” “log” “net/http” “time” “github。com/SkyAPM/go2sky” “github。com/SkyAPM/go2sky/reporter” “github。com/gin-gonic/gin” v3 “github。com/SkyAPM/go2sky-plugins/gin/v3” agentv3 “skywalking。apache。org/repo/goapi/collect/language/agent/v3”)const ( serverName = “demo-server1” serverPort = 8081 remoteServerName = “demo-server2” remoteServerAddr = “localhost:8082” remotePath = “/user/info”)var skyAddr = “localhost:11800”func panicErr(err error) { if err != nil { log。Fatal(err。Error()) }}type Params struct { Name string}var tracer *go2sky。Tracerfunc skyMiddleware(r *gin。Engine) { var err error rp, err := reporter。NewGRPCReporter(skyAddr, reporter。WithCheckInterval(5*time。Second)) panicErr(err) tracer, err = go2sky。NewTracer(serverName, go2sky。WithReporter(rp)) panicErr(err) r。Use(v3。Middleware(r, tracer))}func trace(context *gin。Context) { span, ctx, err := tracer。CreateLocalSpan(context。Request。Context()) panicErr(err) span。SetOperationName(“Trace”) context。Request = context。Request。WithContext(ctx) span。Log(time。Now(), “[Trace]”, fmt。Sprintf(serverName+“ satrt, params : %s”, time。Now()。Format(“15:04:05。9999”))) result := make([]map[string]interface{}, 0) //1、請求一次 { url := fmt。Sprintf(“http://%s%s”, remoteServerAddr, remotePath) params := Params{ Name: serverName + time。Now()。Format(“15:04:05。9999”), } buffer := &bytes。Buffer{} _ = json。NewEncoder(buffer)。Encode(params) req, err := http。NewRequest(http。MethodPost, url, buffer) panicErr(err) // op_name 是每一個操作的名稱 reqSpan, err := tracer。CreateExitSpan(context。Request。Context(), “invoke - ”+remoteServerName, “localhost:8082/user/info”, func(headerKey, headerValue string) error { req。Header。Set(headerKey, headerValue) return nil }) panicErr(err) reqSpan。SetComponent(2) reqSpan。SetSpanLayer(agentv3。SpanLayer_RPCFramework) // rpc 呼叫 resp, err := http。DefaultClient。Do(req) panicErr(err) defer resp。Body。Close() reqSpan。Log(time。Now(), “[HttpRequest]”, fmt。Sprintf(“開始請求,請求服務:%s, 請求地址:%s, 請求引數:%+v”, remoteServerName, url, params)) body, err := ioutil。ReadAll(resp。Body) panicErr(err) fmt。Printf(“接受到訊息: %s\n”, body) reqSpan。Tag(go2sky。TagHTTPMethod, http。MethodPost) reqSpan。Tag(go2sky。TagURL, url) reqSpan。Log(time。Now(), “[HttpRequest]”, fmt。Sprintf(“結束請求,響應結果:%s”, body)) reqSpan。End() res := map[string]interface{}{} err = json。Unmarshal(body, &res) panicErr(err) result = append(result, res) } //2 、再請求一次 { url := fmt。Sprintf(“http://%s%s”, remoteServerAddr, remotePath) params := Params{ Name: serverName + time。Now()。Format(“15:04:05。9999”), } buffer := &bytes。Buffer{} _ = json。NewEncoder(buffer)。Encode(params) req, err := http。NewRequest(http。MethodPost, url, buffer) panicErr(err) // 出去必須用這個攜帶 header reqSpan, err := tracer。CreateExitSpan(context。Request。Context(), “invoke - ”+remoteServerName, “localhost:8082/user/info”, func(headerKey, headerValue string) error { req。Header。Set(headerKey, headerValue) return nil }) panicErr(err) reqSpan。SetComponent(2) reqSpan。SetSpanLayer(agentv3。SpanLayer_RPCFramework) // rpc 呼叫 resp, err := http。DefaultClient。Do(req) panicErr(err) defer resp。Body。Close() reqSpan。Log(time。Now(), “[HttpRequest]”, fmt。Sprintf(“開始請求,請求服務:%s, 請求地址:%s, 請求引數:%+v”, remoteServerName, url, params)) body, err := ioutil。ReadAll(resp。Body) panicErr(err) fmt。Printf(“接受到訊息: %s\n”, body) reqSpan。Tag(go2sky。TagHTTPMethod, http。MethodPost) reqSpan。Tag(go2sky。TagURL, url) reqSpan。Log(time。Now(), “[HttpRequest]”, fmt。Sprintf(“結束請求,響應結果:%s”, body)) reqSpan。End() res := map[string]interface{}{} err = json。Unmarshal(body, &res) panicErr(err) result = append(result, res) } // 設定響應結果 local := gin。H{ “msg”: result, } context。JSON(200, local) span。Log(time。Now(), “[Trace]”, fmt。Sprintf(serverName+“ end, resp : %s”, local)) span。End() { span, ctx, err := tracer。CreateEntrySpan(context。Request。Context(), “Send”, func(s string) (string, error) { return “”, nil }) context。Request = context。Request。WithContext(ctx) panicErr(err) span。SetOperationName(“Send”) span。Log(time。Now(), “[Info]”, “send resp”) span。End() }}func main() { // 這些都一樣 r := gin。Default() // 使用 go2sky gin 中介軟體 skyMiddleware(r) // 呼叫介面 r。GET(“/trace”, trace) r。Run(fmt。Sprintf(“:%d”, serverPort))}

呼叫測試

1for i in $(seq 100); do curl -s http://localhost:8081/trace >/dev/null; sleep 0。1 ; done

demo-server2

使用skywalking監控gin web服務

demo-server2

demo-server1

使用skywalking監控gin web服務

demo-server1

dashboard

使用skywalking監控gin web服務

skywalking-dashboard

拓撲

使用skywalking監控gin web服務

skywalking-topology

追蹤

使用skywalking監控gin web服務

skywalking-trace

總結

gin 接入 skywalking 總體還是比較簡單的,正式使用的時候還需要再次封裝,簡化操作;

應用程式和 skywalking 之前不是強依賴關係,skywalking 關閉的情況下能夠正常響應請求。

huangzhongde。cn/post/Golang/%E4%BD%BF%E7%94%A8skywalking%E7%9B%91%E6%8E%A7gin_web%E6%9C%8D%E5%8A%A1/