使用skywalking监控gin web服务
简介
SkyWalking是一个开源的 APM 系统,包括对 Cloud Native 架构中分布式系统的监控、跟踪、诊断能力。核心功能如下。
- 服务、服务实例、端点指标分析
- 根本原因分析。在运行时分析代码
- 服务拓扑图分析
- 服务、服务实例和端点依赖分析
- 检测到缓慢的服务和端点
- 性能优化
- 分布式跟踪和上下文传播
- 数据库访问指标。检测慢速数据库访问语句(包括SQL语句)
- 警报
- 浏览器性能监控
- 基础设施(VM、网络、磁盘等)监控
- 跨指标、跟踪和日志的协作
快速安装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
访问控制台
gin接入skywalking
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-server1
dashboard
拓扑
追踪
总结
- gin接入skywalking总体还是比较简单的,正式使用的时候还需要再次封装,简化操作;
- 应用程序和skywalking之前不是强依赖关系,skywalking关闭的情况下能够正常响应请求。
- 原文作者:黄忠德
- 原文链接:https://huangzhongde.cn/post/Golang/%E4%BD%BF%E7%94%A8skywalking%E7%9B%91%E6%8E%A7gin_web%E6%9C%8D%E5%8A%A1/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。