搭建Jaeger

news2024/9/23 23:33:56

本篇是对 Golang 上手GORM V2 + Opentracing链路追踪优化CRUD体验(源码阅读)[1] 阅读与实践


该篇相关代码[2]




GORM V2版本开始支持Context上下文传递,支持插件Plugins(有了插件,callback和hook的代码就能更优雅一点)

ORM利用反射,以牺牲一定的性能为代价,快速构建项目


使用Docker搭建Opentracing + jaeger 平台


docker run -d --name jaeger -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 14268:14268 -p 14250:14250 -p 9411:9411 jaegertracing/all-in-one:1.18

alt

访问 http://localhost:16686/[3] 如下:

alt



编写CallBacks插件


CallBacks和Hook不同,前者将伴随GORM的DB对象的整个生命周期,利用CallBacks对GORM框架进行侵入,实现自定义的一些功能


1. 在每次SQL操作前,从context上下文生成子span


gormTracing.go:

package gormTracing

import (
 "github.com/opentracing/opentracing-go"
 "gorm.io/gorm"
)

const gormSpanKey = "__gorm_spqn"

func before(db *gorm.DB) {

 //生成子span。 名字可以自定义
 span, _ := opentracing.StartSpanFromContext(db.Statement.Context, "shuang_gorm_jaeger")

 // 利用db实例去传递span
 // gorm v1.x版本没有InstanceSet,有scope.Set
 db.InstanceSet(gormSpanKey, span)

}

2. 在每次SQL操作后 从DB实例拿到Span并记录数据


gormTracing.go:

func after(db *gorm.DB) {
 _span, isExist := db.InstanceGet(gormSpanKey)
 if !isExist {
  // 不存在则直接抛弃掉
  return
 }

 // 断言 进行类型转换
 span, ok := _span.(opentracing.Span)
 if !ok {
  return
 }

 // 一定要Finish掉
 defer span.Finish()

 // 记录error
 if db.Error != nil {
  span.LogFields(tracerLog.Error(db.Error))
 }

 span.LogFields(tracerLog.String("sql", db.Dialector.Explain(db.Statement.SQL.String(), db.Statement.Vars...)))

}

同样可以非常简单就可以从DB的Setting中,拿到用于处理GORM操作的子Span。

只需要调用span的LogFields方法就能记录下想要的信息


3. 创建结构体,实现gorm.Plugin接口


gormTracing.go:

const (
 callBackBeforeName = "opentracing:before"
 callBackAfterName  = "opentracing:after"
)

type OpentracingPlugin struct{}

func (op *OpentracingPlugin) Name() string {
 return "opentracingPlugin"
}

func (op *OpentracingPlugin) Initialize(db *gorm.DB) (err error) {
 // 开始前 - 并不是都用相同的方法,可自定义
 db.Callback().Create().Before("gorm:before_create").Register(callBackBeforeName, before)
 db.Callback().Query().Before("gorm:query").Register(callBackBeforeName, before)
 db.Callback().Delete().Before("gorm:before_delete").Register(callBackBeforeName, before)
 db.Callback().Update().Before("gorm:setup_reflect_value").Register(callBackBeforeName, before)
 db.Callback().Row().Before("gorm:row").Register(callBackBeforeName, before)
 db.Callback().Raw().Before("gorm:raw").Register(callBackBeforeName, before)

 // 结束后 - 并不是都用相同的方法,可自定义
 db.Callback().Create().After("gorm:after_create").Register(callBackAfterName, after)
 db.Callback().Query().After("gorm:after_query").Register(callBackAfterName, after)
 db.Callback().Delete().After("gorm:after_delete").Register(callBackAfterName, after)
 db.Callback().Update().After("gorm:after_update").Register(callBackAfterName, after)
 db.Callback().Row().After("gorm:row").Register(callBackAfterName, after)
 db.Callback().Raw().After("gorm:raw").Register(callBackAfterName, after)
 return
}

// 告诉编译器这个结构体实现了gorm.Plugin接口
var _ gorm.Plugin = &OpentracingPlugin{}

需要给GORM所有的最终操作(Create、Query、Delete、Update、Row、Raw等), 注册上刚刚编写的两个方法 beforeafter (即在sql执行前要做的操作,和sql执行后要做的操作)


GORM的Plugin接口源码如下:

// Plugin GORM plugin interface
type Plugin interface {
 Name() string
 Initialize(*DB) error
}

只需如上面代码,实现NameInitialize这两个方法,即实现了这个接口




单元测试


1. 初始化Jeager


gormTracing_test.go:

package gormTracing

import (
 "github.com/opentracing/opentracing-go"
 "github.com/uber/jaeger-client-go"
 "github.com/uber/jaeger-client-go/config"
 "io"
)

func initJaeger() (closer io.Closer, err error) {
 // 根据配置初始化Tracer, 返回Closer

 tracer, closer, err := (&config.Configuration{
  ServiceName: "gormTracing",
  Disabled:    false,
  Sampler: &config.SamplerConfig{
   Type: jaeger.SamplerTypeConst,
   // param的值在0到1之间,设置为1则将所有的Operation输出到Reporter
   Param: 1,
  },
  Reporter: &config.ReporterConfig{
   LogSpans:           true,
   LocalAgentHostPort: "localhost:6831",
  },
 }).NewTracer()

 if err != nil {
  return
 }

 // 设置全局Tracer - 如果不设置将会导致上下文无法生成正确的Span
 opentracing.SetGlobalTracer(tracer)
 return

}

2. 实现GORM官方范例


GORM V2文档[4]

package gormTracing

import (
 "context"
 "github.com/opentracing/opentracing-go"
 "github.com/uber/jaeger-client-go"
 "github.com/uber/jaeger-client-go/config"
 "gorm.io/driver/mysql"
 "gorm.io/gorm"
 "io"
 "testing"
)

func initJaeger() (closer io.Closer, err error) {
 // 根据配置初始化Tracer, 返回Closer

 tracer, closer, err := (&config.Configuration{
  ServiceName: "gormTracing",
  Disabled:    false,
  Sampler: &config.SamplerConfig{
   Type: jaeger.SamplerTypeConst,
   // param的值在0到1之间,设置为1则将所有的Operation输出到Reporter
   Param: 1,
  },
  Reporter: &config.ReporterConfig{
   LogSpans:           true,
   LocalAgentHostPort: "localhost:6831",
  },
 }).NewTracer()

 if err != nil {
  return
 }

 // 设置全局Tracer - 如果不设置将会导致上下文无法生成正确的Span
 opentracing.SetGlobalTracer(tracer)
 return

}

type Product struct {
 gorm.Model
 Code  string
 Price uint
}
type User struct {
 gorm.Model
 Id     int
 Name   string
 gender string
}

// V2需要利用Driver来连接MySQL数据库
func Test_GormTracing(t *testing.T) {
 // 1. 初始化Jaeger
 closer, err := initJaeger()
 if err != nil {
  t.Fatal(err)
 }
 defer closer.Close()

 // 2. 连接数据库
 // "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
 dsn := "root:12345678@tcp(localhost:3306)/shuang?charset=utf8mb4&parseTime=True&loc=Local"
 db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
 if err != nil {
  t.Fatal(err)
 }

 // 3. 最重要的一步,使用之前自定义的插件
 _ = db.Use(&OpentracingPlugin{})

 // 迁移 schema ---> 生成对应的数据表
 //_ = db.AutoMigrate(&Product{})

 // 4. 生成新的Span - 注意将span结束掉,不然无法发送对应的结果
 span := opentracing.StartSpan("gormTracing unit test")
 defer span.Finish()

 // 5. 把生成的Root Span写入到Context上下文,获取一个子Context
 // 通常在Web项目中,Root Span由中间件生成
 ctx := opentracing.ContextWithSpan(context.Background(), span)

 // 6. 将上下文传入DB实例,生成Session会话
 // 这样子就能把这个会话的全部信息反馈给Jaeger
 session := db.WithContext(ctx)

 // ---> 下面是官方文档GORM的范例
 // Create
 //session.Create(&Product{Code: "D42", Price: 100})
 //
  Read
 //var product Product
 //session.First(&product, 1)                 // 根据整形主键查找
 //session.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录
 //
  Update - 将 product 的 price 更新为 200
 //session.Model(&product).Update("Price", 200)
  Update - 更新多个字段
 //session.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
 //session.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
 //
  Delete - 删除 product
 //session.Delete(&product, 1)

 var user User
 //db.Table("user").Where("id=?", 1).First(&user)
 session.Table("user").Where("id=?"1).First(&user)

}



3. 执行并查看结果


alt
alt

访问Jaeger控制台(localhost:16686),可发现有一条新的记录:

alt

点击进入查看详情,可以非常清楚看到整个单元测试从开始到结束的SQL执行情况:

总共执行了2条SQL命令,整个过程耗时3.76ms(因为连接的本地库,所以比较快)

alt

点开对应的Span,可以看到每次GORM操作所执行的SQL命令:

alt

至此使用OpenTracing对GORM执行过程进行链路追踪已成功实现,从此摆脱需要检索庞大日志查找慢查询、异常和错误的情况,直接一目了然


4. 并发情况下链路追踪的效果


func Test_GormTracing2(t *testing.T) {
 closer, err := initJaeger()
 if err != nil {
  t.Fatal(err)
 }
 defer closer.Close()

 db, err := gorm.Open(mysql.Open("root:12345678@tcp(localhost:3306)/shuang?charset=utf8mb4&parseTime=True&loc=Local"), &gorm.Config{})
 if err != nil {
  t.Fatal(err)
 }
 _ = db.Use(&OpentracingPlugin{})

 rand.Seed(time.Now().UnixNano())

 num, wg := 1<<10, &sync.WaitGroup{}

 wg.Add(num)

 for i := 0; i < num; i++ {
  go func(t int) {
   span := opentracing.StartSpan(fmt.Sprintf("gormTracing unit test %d", t))
   defer span.Finish()

   ctx := opentracing.ContextWithSpan(context.Background(), span)
   session := db.WithContext(ctx)

   p := &Product{Code: strconv.Itoa(t), Price: uint(rand.Intn(1 << 10))}

   session.Create(p)

   session.First(p, p.ID)

   session.Delete(p, p.ID)

   wg.Done()
  }(i)
 }

 wg.Wait()
}
alt
alt

番外:GORM V2 部分源码阅读


GORM V2 部分源码阅读[5]




更多参考:

Jaeger V1.18文档[6]

分布式链路追踪:OpenTracing SDK 与 Jaeger 的对接方法[7]

gRPC与分布式链路追踪[8]

全链路监控Jaeger搭建实战[9]

参考资料

[1]

Golang 上手GORM V2 + Opentracing链路追踪优化CRUD体验(源码阅读): https://github.com/codehorseman/gormTracing

[2]

该篇相关代码: https://github.com/cuishuang/gorm_and_jaeger

[3]

http://localhost:16686/: http://localhost:16686/

[4]

GORM V2文档: https://gorm.io/zh_CN/docs/

[5]

GORM V2 部分源码阅读: https://github.com/avtion/gormTracing

[6]

Jaeger V1.18文档: https://www.jaegertracing.io/docs/1.18/getting-started/

[7]

分布式链路追踪:OpenTracing SDK 与 Jaeger 的对接方法: https://www.lijiaocn.com/%E7%BC%96%E7%A8%8B/2020/02/15/opentracing.html

[8]

gRPC与分布式链路追踪: https://www.selinux.tech/golang/grpc/grpc-tracing

[9]

全链路监控Jaeger搭建实战: https://www.jianshu.com/p/ffc597bb4ce8

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1014616.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Seata1.5.2解决分布式事务问题

分布式事务–Seata ​ 前面了解到一些分布式事务的解决方案&#xff0c;业内也涌现出不少解决分布式事务的优秀框架&#xff0c;如Atomikos、Seata等&#xff0c;本章来了解使用下Seata。 ​ Seata的前身是Fescar&#xff0c;而后改名Seata&#xff0c;简单可扩展的自治分布式…

性能工程全景图、实施方案、建设目标

性能工程是一个关注系统性能层面的体系,包含测试环境的性能测试、生产环境的性能测试、性能调优、容量规划等多个方面 性能工程主要包括如下3个部分 ❑建设性能测试理论体系和流程规范、链路分析基础知识体系和流程规范、性能调优基础理论体系和流程规范。 ❑搭建高效协同的工…

js通过xpath定位元素并且操作元素以下拉框select为例

js也可以使用xpath定位元素&#xff0c;现在实例讲解。 页面上有一个下拉框&#xff0c;里面内容有三个&#xff0c;用F12看一下 一、使用xpath定位这个下拉框select eldocument.evaluate(//select[name"shoppingPreference"], document).iterateNext()二、为下拉框…

代码随想录算法训练营Day60 | 84. 柱状图中最大的矩形

文章目录 84. 柱状图中最大的矩形首尾加 0双指针 84. 柱状图中最大的矩形 题目链接 | 解题思路 本题和接雨水的题目相互呼应&#xff0c;但是难度略有提升&#xff0c;同样是一道非常棒的题&#xff01; 在接雨水中&#xff0c;需要找到每一列的左侧最大值和右侧最大值&…

高阶数据结构(2)-----红黑树

一)红黑树的基本概念和基本性质: 1)红黑树就是一种高度平衡的二叉搜索树&#xff0c;但是在每一个节点上面都增加了一个存储位来表示结点的颜色&#xff0c;可以是红色或者是黑色&#xff0c;通过对任何一条从根节点到叶子节点上面的路径各个节点着色方式的限制&#xff0c;红黑…

Selenium+Pytest自动化测试框架实战(下)

前言 本文接上篇文章哟。 一、简单学习元素定位 在日常的工作中&#xff0c;我见过很多在浏览器中直接在浏览器中右键Copy Xpath复制元素的同学。这样获得的元素表达式放在 webdriver 中去运行往往是不够稳定的&#xff0c;像前端的一些微小改动&#xff0c;都会引起元素无法…

Truenas scale 安装 Tailscale 内网穿透远程连接SMB服务

起源 没有公网IP&#xff0c;不在同一个路由器&#xff0c;没法远程连接电脑或者服务器。之前一直使用 zerotier&#xff0c; 但是这次使用 Truenas scale 安装 zerotier&#xff0c; 每次重启都变化IP&#xff0c;一直没解决。转投 tailscale。 注册 YouTube有教程&#xf…

基于ssm智能停车场031

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…

安装并设置linux虚拟机ubuntu20.04.6 LTS

1 安装虚拟机 1、安装虚拟机模拟软件virtualboxhttps://download.virtualbox.org/virtualboxhttps://download.virtualbox.org/virtualbox/7.0.10/VirtualBox-7.0.10-158379-Win.exe 2、在virtualbox中安装虚拟机ubuntu 20.04.6 LTS 桌面版https://www.releases.ubuntu.com/fo…

进度条--QProgressBar,进度对话框--QProgressDialog

一、QProgressBar 进度条 1、QProgressBar 类继承自 QWidget&#xff0c;它是一个 QWidget 部件&#xff0c; QProgressBar 除了将其放置 于进度对话框之中外&#xff0c;还可将其放置于窗口的状态栏等其他部件中。 QProgressBar提供了一个水平或垂直的进度条&#xff0c;可以…

JDBC基本概念

什么是JDBC JDBC概念 JDBC&#xff08;Java DataBase Connectivity&#xff09;是一套统一的基于Java语言的关系数据库编程接口规范。 该规范允许将SQL语句作为参数通过JDBC接口发送给远端数据库&#xff0c; …

8位和32位单片机如何选择适合,以及主要区别!

单片机直接影响到项目的成功和性能&#xff0c;我们将分享如何选择适合您的应用的8位或32位单片机。 8位单片机 vs. 32位单片机&#xff1a; 一、性能和处理能力&#xff1a; 8位单片机&#xff1a; 8位单片机通常适用于相对简单的应用&#xff0c;如传感器控制、LED显示、小…

代码随想录算法训练营第三十五天| 860.柠檬水找零 406.根据身高重建队列 452. 用最少数量的箭引爆气球

860.柠檬水找零 本题看上好像挺难&#xff0c;其实挺简单的&#xff0c;大家先尝试自己做一做。 代码随想录 public boolean lemonadeChange(int[] bills) {int five 0;int ten 0;for (int i 0; i < bills.length; i) {if (bills[i] 5) {five;} else if (bills[i] 10)…

Mysql同步数据到Doris的踩坑过程

问题背景 由于项目需要&#xff0c;需要把多个Mysql数据库的数据同步到Doris数据库&#xff0c;然后利用Doris强调的计算和查询能力&#xff0c;来满足业务需求。有关Doris可以查看它的官方文档来了解它。 seatunnel的使用到放弃 缘起 从《第十届GIAC全球互联网架构大会》了…

论如何在Android中还原设计稿中的阴影

每当设计稿上注明需要添加阴影时&#xff0c;Android上总是显得比较棘手&#xff0c;因为Android的阴影实现方式与Web和iOS有所区别。 一般来说阴影通常格式是有&#xff1a; X: 在X轴的偏移度 Y: 在Y轴偏移度 Blur: 阴影的模糊半径 Color: 阴影的颜色 何为阴影 但是在A…

手刻 Deep Learning -第壹章 -PyTorch教学-激励函数与感知机入门(上)

一、前言 本文接续前篇教学 Pytorch 与线性回归 &#xff0c;本文着重在 Activation Function &#xff08; 中文称 激励函数 &#xff09;&#xff0c;我们会介绍激励函数 &#xff08;也有人称 激活函数&#xff1f; 激发函数&#xff1f; &#xff09; 为什么会有用&#xf…

Python 06 之面向对象基础

&#x1f600;前言 在日常编程和软件开发中&#xff0c;我们通常会遇到各种各样的问题&#xff0c;其中很多问题都可以通过面向对象的程序设计方法来解决。面向对象编程不仅可以使代码更加组织化和系统化&#xff0c;而且还可以提高代码的重用性和可维护性。 . 在本教程中&…

一个工位的演讲

前几天&#xff0c;知乎官方制作了一个视频&#xff0c;感觉这个视频非常不错&#xff0c;推荐给大家看看。 这个短片很有创意&#xff0c;而且&#xff0c;内容也很丰富。 为什么说这个短片很有创意呢&#xff1f;因为它是从一个工位的角度切入的&#xff0c;所谓铁打的工位&a…

基于matlab实现的电力系统稳定性分析摆幅曲线代码

完整程序&#xff1a; clear; clc; t 0; tf 0; tfl 0.5; tc 0.5; % tc 0.05, 0.125, 0.5 sec for 2.5 cycles, 6.25 cycles & 25 cycles resp ts 0.05; m 2.52 / (180 * 50); i 2; dt 21.64 * pi / 180; ddt 0; time(1) 0; ang(1) 21.64; pm 0.9; pm1 2.44;…

解锁大数据宝藏:使用AI预测未来趋势的新方法

文章目录 大数据的价值传统方法与AI的优势使用AI预测未来趋势的新方法1. 时间序列分析2. 自然语言处理&#xff08;NLP&#xff09;3. 预测市场趋势 应用前景与挑战应用前景挑战和限制 结论 &#x1f389;欢迎来到AIGC人工智能专栏~解锁大数据宝藏&#xff1a;使用AI预测未来趋…