网站首页 > 技术文章 正文
【导读】本文介绍了分布式开源分布式tracing框架Skywalking的使用。
简介
SkyWalking 是一个开源的 APM 系统,包括对 Cloud Native 架构中分布式系统的监控、跟踪、诊断能力。核心功能如下。
- 服务、服务实例、端点指标分析
- 根本原因分析。在运行时分析代码
- 服务拓扑图分析
- 服务、服务实例和端点依赖分析
- 检测到缓慢的服务和端点
- 性能优化
- 分布式跟踪和上下文传播
- 数据库访问指标。检测慢速数据库访问语句(包括 SQL 语句)
- 警报
- 浏览器性能监控
- 基础设施(VM、网络、磁盘等)监控
- 跨指标、跟踪和日志的协作
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/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 代码如下
package main
import (
"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 main
import (
"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.Tracer
func 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
demo-server1
dashboard
拓扑
追踪
总结
- 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/
猜你喜欢
- 2024-10-03 Spring Boot API 如何获得 JSON 数据
- 2024-10-03 大白话 golang 教程-20-使用 RPC 远程调用
- 2024-10-03 软件更新速递:Go发布1.15版本,这些新特性抢先看
- 2024-10-03 掌握 Postman 工具发送请求的简易教程
- 2024-10-03 python接口自动化-Json数据处理(python接口自动化流程)
- 2024-10-03 python接口自动化(八)--发送post请求的接口(详解)
- 2024-10-03 Flink 在唯品会的实践(唯品会 分析)
- 2024-10-03 web开发之-ajax的基本使用(处理json数据)
- 2024-10-03 Java POST JSON 数据处理异常 (code 160)): was expecting double-quote
- 2024-10-03 使用Postman发送POST请求的指南(postman如何发送post请求)
- 02-21走进git时代, 你该怎么玩?_gits
- 02-21GitHub是什么?它可不仅仅是云中的Git版本控制器
- 02-21Git常用操作总结_git基本用法
- 02-21为什么互联网巨头使用Git而放弃SVN?(含核心命令与原理)
- 02-21Git 高级用法,喜欢就拿去用_git基本用法
- 02-21Git常用命令和Git团队使用规范指南
- 02-21总结几个常用的Git命令的使用方法
- 02-21Git工作原理和常用指令_git原理详解
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)