Golang 上手GORM V2 + Opentracing链路追踪优化CRUD体验(源码阅读)

Golang 上手GORM V2 + Opentracing链路追踪优化CRUD体验(源码阅读)

一、前言

在这里插入图片描述
在这里插入图片描述

系统环境(过几年我翻回来看或许会感慨我当初如此不堪)
go version go1.14.3 windows/amd64
gorm.io/gorm v0.2.31

不会吧不会吧,0202年了,还有人单纯依赖日志去排查CRUD问题?快了解一下链路追踪吧!

六月份前后,比较有名的GORM框架更新了V2版本,尽管现在依旧在测试阶段,但是我们还是能体验一下框架的一部分新特性 Feature,其中最馋的还是支持Context上下文传递的特性,结合分布式链路追踪技术,有助于我们服务在分布式部署的情况下精准排查问题。

还是要提及一下,ORM作为辅助工具能帮助我们快速构建项目,追求极致的响应速度应该手撸SQL,但是手撸SQL往往会遇到SQL注入、过程繁琐的问题,所以ORM是一把双刃剑,利用反射牺牲一定的性能以便我们更快上手项目。

文章按例依旧有部分源码分析,之前有做过同类型ORM框架XORM的链路追踪教程,有兴趣的可以看一下

Golang XORM实现分布式链路追踪(源码分析,分布式CRUD必学)

二、Docker搭建Opentracing + jaeger all in one平台

注:Docker是最简单的,还有其他的方式,有兴趣的朋友可以去翻阅技术文档

使用的镜像:jaegertracing/all-in-one:1.18

Docker命令

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

浏览器访问localhost:16686,可以看到JaegerUI界面,如下所示:

在这里插入图片描述

三、创建项目

在项目目录下使用控制台输入,懂得都懂

go mod init
go get -u gorm.io/gorm
go get -u github.com/uber/jaeger-client-go

四、编写CallBacks插件

这里的CallBacks和模型的钩子不一样,CallBacks伴随GORM的DB对象整个生命周期,我们需要利用CallBacks对GORM框架进行侵入,以达到操作和访问GORM的DB对象的行为

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

通常我们服务(业务)在入口会分配一个根Span,然后在后续操作中会分裂出子Span,每个span都有自己的具体的标识,Finsh之后就会汇集在我们的链路追踪系统中

在这里插入图片描述

(OpenTracing的Span示意图,我初学也看不懂,其实也就那样)

文件:gormTracing.go

package gormTracing

// 包内静态变量
const gormSpanKey = "__gorm_span"

func before(db *gorm.DB) {
   
	// 先从父级spans生成子span ---> 这里命名为gorm,但实际上可以自定义
	// 自己喜欢的operationName
	span, _ := opentracing.StartSpanFromContext(db.Statement.Context, "gorm")
	
	// 利用db实例去传递span
	db.InstanceSet(gormSpanKey, span)
	
	return
}

就这么朴实无华的两行代码就能生成子span,按照惯例,我们需要抛弃掉StartSpanFromContext第二个结果,因为我们不能把父span覆盖掉,同时利用db的Setting(sync.Map)去寄存子Span

下文会对db.InstanceSet方法进行源码说明,有兴趣的朋友可以看一下

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

文件: gormTracing.go

// 注意,这里重命名了log模块
import tracerLog "github.com/opentracing/opentracing-go/log"

func after(db *gorm.DB) {
   
	// 从GORM的DB实例中取出span
	_span, isExist := db.InstanceGet(gormSpanKey)
	if !isExist {
   
	    // 不存在就直接抛弃掉
		return
	}

	// 断言进行类型转换
	span, ok := _span.(opentracing.Span)
	if !ok {
   
		return
	}
	// <---- 一定一定一定要Finsih掉!!!
	defer span.Finish()

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

	// sql --> 写法来源GORM V2的日志
	span.LogFields(tracerLog.String("sql", db.Dialector.Explain(db.Statement.SQL.String(), db.Statement.Vars...)))
	return
}

同样非常简单地就能从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"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值