简介

SkyWalking是一个开源的 APM 系统,包括对 Cloud Native 架构中分布式系统的监控、跟踪、诊断能力。核心功能如下。

  • 服务、服务实例、端点指标分析
  • 根本原因分析。在运行时分析代码
  • 服务拓扑图分析
  • 服务、服务实例和端点依赖分析
  • 检测到缓慢的服务和端点
  • 性能优化
  • 分布式跟踪和上下文传播
  • 数据库访问指标。检测慢速数据库访问语句(包括SQL语句)
  • 警报
  • 浏览器性能监控
  • 基础设施(VM、网络、磁盘等)监控
  • 跨指标、跟踪和日志的协作

skywalking-arch

快速安装skywalking

skywalking支持多种安装方式,二进制安装,docker安装以及helm、k8s安装等。

这里直接使用docker-compose进行快速部署测试

docker-compose.yaml

 1version: '3.3'
 2services:
 3  # storage
 4  elasticsearch:
 5    image: elasticsearch:6.8.16
 6    container_name: elasticsearch
 7    restart: always
 8    ports:
 9      - 9200:9200
10    environment:
11      discovery.type: single-node
12    ulimits:
13      memlock:
14        soft: -1
15        hard: -1
16    volumes: 
17      - ./elasticsearch/logs:/usr/share/elasticsearch/logs
18      - ./elasticsearch/data:/usr/share/elasticsearch/data
19      - /etc/localtime:/etc/localtime
20  # server
21  oap:
22    image: apache/skywalking-oap-server:8.6.0-es6
23    container_name: oap
24    depends_on:
25      - elasticsearch
26    links:
27      - elasticsearch
28    restart: always
29    ports:
30      - 11800:11800
31      - 12800:12800
32    environment:
33      SW_STORAGE: elasticsearch # 默认为es6,es7为elasticsearch7
34      SW_STORAGE_ES_CLUSTER_NODES: elasticsearch:9200
35    volumes: 
36      - /etc/localtime:/etc/localtime
37  # dashboard
38  ui:
39    image: apache/skywalking-ui:8.6.0
40    container_name: ui
41    depends_on:
42      - oap
43    links:
44      - oap
45    restart: always
46    ports:
47      - 8080:8080
48    environment:
49      SW_OAP_ADDRESS: oap:12800
50    volumes: 
51      - /etc/localtime:/etc/localtime

运行

1docker-compose up -d

访问控制台

浏览器访问http://localhost:8080

gin接入skywalking

这里使用第三方库go2sky以及go2sky的gin中间件

1go get -u github.com/SkyAPM/go2sky
2go 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代码如下

 1package main
 2
 3import (
 4  "fmt"
 5  "time"
 6
 7  "github.com/SkyAPM/go2sky"
 8  "github.com/SkyAPM/go2sky/reporter"
 9  "github.com/gin-gonic/gin"
10
11  v3 "github.com/SkyAPM/go2sky-plugins/gin/v3"
12)
13
14const (
15  serverName = "demo-server2"
16  serverPort = 8082
17)
18
19var skyAddr = "localhost:11800"
20
21type Params struct {
22  Name string
23}
24
25func panicErr(err error) {
26  if err != nil {
27    panic(err)
28  }
29}
30
31func main() {
32  r := gin.Default()
33  // skyAddr 是skywaling的grpc地址,默认是localhost:11800, 默认心跳检测时间是1s
34  rp, err := reporter.NewGRPCReporter(skyAddr, reporter.WithCheckInterval(5*time.Second))
35  panicErr(err)
36  // 初始化一个 tracer,一个服务只需要一个tracer,其含义是这个服务名称
37  tracer, err := go2sky.NewTracer(serverName, go2sky.WithReporter(rp))
38  panicErr(err)
39  // gin 使用 sky自带的middleware
40  r.Use(v3.Middleware(r, tracer))
41
42  // 自定义一个接口
43  r.POST("/user/info", func(context *gin.Context) {
44    // LocalSpan可以理解为本地日志的tracer,一般用户当前应用
45    span, ctx, err := tracer.CreateLocalSpan(context.Request.Context())
46    panicErr(err)
47    // 每一个span都有一个名字去标实操作的名称!
48    span.SetOperationName("UserInfo")
49    // 记住重新设置一个ctx,再其次这个ctx不是gin的ctx,而是http request的ctx
50    context.Request = context.Request.WithContext(ctx)
51
52    params := new(Params)
53    err = context.BindJSON(params)
54    panicErr(err)
55    // 记录日志信息
56    span.Log(time.Now(), "[UserInfo]", fmt.Sprintf(serverName+" satrt, req : %+v", params))
57    local := gin.H{
58      "msg": fmt.Sprintf(serverName+" time : %s", time.Now().Format("15:04:05.9999")),
59    }
60    context.JSON(200, local)
61    span.Log(time.Now(), "[UserInfo]", fmt.Sprintf(serverName+" end, resp : %s", local))
62    // 切记最后要设置span - end,不然就是一个非闭环的
63    span.End()
64  })
65
66  r.Run(fmt.Sprintf(":%d", serverPort))
67}

demo-server1代码如下

  1package main
  2
  3import (
  4  "bytes"
  5  "encoding/json"
  6  "fmt"
  7  "io/ioutil"
  8  "log"
  9  "net/http"
 10  "time"
 11
 12  "github.com/SkyAPM/go2sky"
 13  "github.com/SkyAPM/go2sky/reporter"
 14  "github.com/gin-gonic/gin"
 15
 16  v3 "github.com/SkyAPM/go2sky-plugins/gin/v3"
 17  agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
 18)
 19
 20const (
 21  serverName       = "demo-server1"
 22  serverPort       = 8081
 23  remoteServerName = "demo-server2"
 24  remoteServerAddr = "localhost:8082"
 25  remotePath       = "/user/info"
 26)
 27
 28var skyAddr = "localhost:11800"
 29
 30func panicErr(err error) {
 31  if err != nil {
 32    log.Fatal(err.Error())
 33  }
 34}
 35
 36type Params struct {
 37  Name string
 38}
 39
 40var tracer *go2sky.Tracer
 41
 42func skyMiddleware(r *gin.Engine) {
 43  var err error
 44  rp, err := reporter.NewGRPCReporter(skyAddr, reporter.WithCheckInterval(5*time.Second))
 45  panicErr(err)
 46  tracer, err = go2sky.NewTracer(serverName, go2sky.WithReporter(rp))
 47  panicErr(err)
 48  r.Use(v3.Middleware(r, tracer))
 49}
 50
 51func trace(context *gin.Context) {
 52  span, ctx, err := tracer.CreateLocalSpan(context.Request.Context())
 53  panicErr(err)
 54  span.SetOperationName("Trace")
 55
 56  context.Request = context.Request.WithContext(ctx)
 57  span.Log(time.Now(), "[Trace]", fmt.Sprintf(serverName+" satrt, params : %s", time.Now().Format("15:04:05.9999")))
 58
 59  result := make([]map[string]interface{}, 0)
 60
 61  //1、请求一次
 62  {
 63    url := fmt.Sprintf("http://%s%s", remoteServerAddr, remotePath)
 64
 65    params := Params{
 66      Name: serverName + time.Now().Format("15:04:05.9999"),
 67    }
 68    buffer := &bytes.Buffer{}
 69    _ = json.NewEncoder(buffer).Encode(params)
 70    req, err := http.NewRequest(http.MethodPost, url, buffer)
 71    panicErr(err)
 72
 73    // op_name 是每一个操作的名称
 74    reqSpan, err := tracer.CreateExitSpan(context.Request.Context(), "invoke - "+remoteServerName, "localhost:8082/user/info", func(headerKey, headerValue string) error {
 75      req.Header.Set(headerKey, headerValue)
 76      return nil
 77    })
 78    panicErr(err)
 79    reqSpan.SetComponent(2)
 80    reqSpan.SetSpanLayer(agentv3.SpanLayer_RPCFramework) // rpc 调用
 81
 82    resp, err := http.DefaultClient.Do(req)
 83    panicErr(err)
 84    defer resp.Body.Close()
 85
 86    reqSpan.Log(time.Now(), "[HttpRequest]", fmt.Sprintf("开始请求,请求服务:%s,请求地址:%s,请求参数:%+v", remoteServerName, url, params))
 87    body, err := ioutil.ReadAll(resp.Body)
 88    panicErr(err)
 89    fmt.Printf("接受到消息: %s\n", body)
 90    reqSpan.Tag(go2sky.TagHTTPMethod, http.MethodPost)
 91    reqSpan.Tag(go2sky.TagURL, url)
 92    reqSpan.Log(time.Now(), "[HttpRequest]", fmt.Sprintf("结束请求,响应结果: %s", body))
 93    reqSpan.End()
 94    res := map[string]interface{}{}
 95    err = json.Unmarshal(body, &res)
 96    panicErr(err)
 97    result = append(result, res)
 98  }
 99
100  //2 、再请求一次
101  {
102    url := fmt.Sprintf("http://%s%s", remoteServerAddr, remotePath)
103
104    params := Params{
105      Name: serverName + time.Now().Format("15:04:05.9999"),
106    }
107    buffer := &bytes.Buffer{}
108    _ = json.NewEncoder(buffer).Encode(params)
109    req, err := http.NewRequest(http.MethodPost, url, buffer)
110    panicErr(err)
111
112    // 出去必须用这个携带header
113    reqSpan, err := tracer.CreateExitSpan(context.Request.Context(), "invoke - "+remoteServerName, "localhost:8082/user/info", func(headerKey, headerValue string) error {
114      req.Header.Set(headerKey, headerValue)
115      return nil
116    })
117    panicErr(err)
118    reqSpan.SetComponent(2)
119    reqSpan.SetSpanLayer(agentv3.SpanLayer_RPCFramework) // rpc 调用
120
121    resp, err := http.DefaultClient.Do(req)
122    panicErr(err)
123    defer resp.Body.Close()
124
125    reqSpan.Log(time.Now(), "[HttpRequest]", fmt.Sprintf("开始请求,请求服务:%s,请求地址:%s,请求参数:%+v", remoteServerName, url, params))
126    body, err := ioutil.ReadAll(resp.Body)
127    panicErr(err)
128    fmt.Printf("接受到消息: %s\n", body)
129
130    reqSpan.Tag(go2sky.TagHTTPMethod, http.MethodPost)
131    reqSpan.Tag(go2sky.TagURL, url)
132    reqSpan.Log(time.Now(), "[HttpRequest]", fmt.Sprintf("结束请求,响应结果: %s", body))
133    reqSpan.End()
134    res := map[string]interface{}{}
135    err = json.Unmarshal(body, &res)
136    panicErr(err)
137    result = append(result, res)
138  }
139
140  // 设置响应结果
141  local := gin.H{
142    "msg": result,
143  }
144  context.JSON(200, local)
145  span.Log(time.Now(), "[Trace]", fmt.Sprintf(serverName+" end, resp : %s", local))
146  span.End()
147  {
148    span, ctx, err := tracer.CreateEntrySpan(context.Request.Context(), "Send", func(s string) (string, error) {
149      return "", nil
150    })
151    context.Request = context.Request.WithContext(ctx)
152    panicErr(err)
153    span.SetOperationName("Send")
154    span.Log(time.Now(), "[Info]", "send resp")
155    span.End()
156  }
157}
158
159func main() {
160
161  // 这些都一样
162  r := gin.Default()
163  // 使用go2sky gin中间件
164  skyMiddleware(r)
165
166  // 调用接口
167  r.GET("/trace", trace)
168
169  r.Run(fmt.Sprintf(":%d", serverPort))
170}

调用测试

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

demo-server2

demo-server2

demo-server1

demo-server1

dashboard

skywalking-dashboard

拓扑

skywalking-topology

追踪

skywalking-trace

总结

  1. gin接入skywalking总体还是比较简单的,正式使用的时候还需要再次封装,简化操作;
  2. 应用程序和skywalking之前不是强依赖关系,skywalking关闭的情况下能够正常响应请求。