likes
comments
collection
share

Prometheus埋点技巧:统计 GORM 连接池状态和查询时间

作者站长头像
站长
· 阅读数 59

一、背景

而数据库查询是一个比 HTTP 更加高频的东西, 所以在监控的时候要注意性能开销,所以本文利用Prometheus 统计 GORM 连接池状态和查询时间。

二、实现

GORM 自带一个 Prometheus 插件,可直接使用。

import "gorm.io/plugin/prometheus"

2.1 统计连接池状态

(1)在 ioc 包中的 db.go 中的初始化 db 的方法中添加如下代码

// note 接入 GORM 自己的 prometheus
err = db.Use(prometheus.New(prometheus.Config{
    DBName: "webook",
    // note 每 15s 收集一些数据
    RefreshInterval: 15,
    MetricsCollector: []prometheus.MetricsCollector{
       &prometheus.MySQL{
          VariableNames: []string{"thread_running"},
       },
    },
}))
if err != nil {
    panic(err)
}

(2)启动 main ,然后在不发送请求下,查看 http://localhost:8081/metrics

Prometheus埋点技巧:统计 GORM 连接池状态和查询时间

我们要关注的东西不多:

  • 首先关注 gorm_dbstats_wait_countgorm_dbstats_wait_duration,两个值很大的话,都说明你的连接数量不够,要增大配置。
  • 其次要关注 gorm_dbstats_idle,这个如果很大,可以调小最大空闲连接数的值。
  • 如果 gorm_dbstats_max_idletime_closed 的值很大,可能是你的最大空闲时间设置得太小

2.2 统计查询时间

利用 GORM 的 Callback 来统计查询时间(理解为回调,当 GORM 执行语句的前或后, 就会执行你注册的回调)

文档:gorm.io/zh_CN/docs/…

执行注册的回调,要区分BeforeAfter ,如下图

Prometheus埋点技巧:统计 GORM 连接池状态和查询时间

(1)在 ioc 包中的 db.go 中的初始化 db 的方法中添加如下代码

import prometheus2 "github.com/prometheus/client_golang/prometheus"

cb := gormx.NewCallbacks(prometheus2.SummaryOpts{
    Namespace: "webook",
    Subsystem: "gorm",
    Name:      "gorm_db",
    Help:      "统计 GORM 的数据库查询",
    ConstLabels: map[string]string{
       "instance_id": "my_instance",
    },
    Objectives: map[float64]float64{
       0.5:   0.01,
       0.75:  0.01,
       0.9:   0.01,
       0.99:  0.001,
       0.999: 0.0001,
    },
})

err = db.Use(cb)
if err != nil {
    panic(err)
}

(2)具体实现

Prometheus埋点技巧:统计 GORM 连接池状态和查询时间

package gormx

import (
    "github.com/prometheus/client_golang/prometheus"
    "gorm.io/gorm"
    "time"
)

type Callbacks struct {
    vector *prometheus.SummaryVec
}

func (c *Callbacks) Name() string {
    return "prometheus"
}

func (c *Callbacks) Initialize(db *gorm.DB) error {
    err := db.Callback().Create().Before("*").
       Register("prometheus_create_before", c.Before())
    if err != nil {
       return err
    }

    err = db.Callback().Create().After("*").
       Register("prometheus_create_after", c.After("CREATE"))
    if err != nil {
       return err
    }

    err = db.Callback().Query().Before("*").
       Register("prometheus_query_before", c.Before())
    if err != nil {
       return err
    }

    err = db.Callback().Query().After("*").
       Register("prometheus_query_after", c.After("QUERY"))
    if err != nil {
       return err
    }

    err = db.Callback().Query().Before("*").
       Register("prometheus_raw_before", c.Before())
    if err != nil {
       return err
    }

    err = db.Callback().Raw().After("*").
       Register("prometheus_raw_after", c.After("RAW"))
    if err != nil {
       return err
    }

    err = db.Callback().Update().Before("*").
       Register("prometheus_update_before", c.Before())
    if err != nil {
       return err
    }

    err = db.Callback().Update().After("*").
       Register("prometheus_update_after", c.After("UPDATE"))
    if err != nil {
       return err
    }

    err = db.Callback().Delete().Before("*").
       Register("prometheus_delete_before", c.Before())
    if err != nil {
       return err
    }

    err = db.Callback().Update().After("*").
       Register("prometheus_delete_after", c.After("DELETE"))
    if err != nil {
       return err
    }

    err = db.Callback().Row().Before("*").
       Register("prometheus_row_before", c.Before())
    if err != nil {
       return err
    }

    err = db.Callback().Update().After("*").
       Register("prometheus_row_after", c.After("ROW"))
    return err
}

func NewCallbacks(opts prometheus.SummaryOpts) *Callbacks {
    vector := prometheus.NewSummaryVec(opts,
       []string{"type", "table"})
    prometheus.MustRegister(vector)
    return &Callbacks{
       vector: vector,
    }
}

func (c *Callbacks) Before() func(db *gorm.DB) {
    return func(db *gorm.DB) {
       start := time.Now()
       db.Set("start_time", start)
    }
}

func (c *Callbacks) After(typ string) func(db *gorm.DB) {
    return func(db *gorm.DB) {
       //db.Statement.Context = xxx
       val, _ := db.Get("start_time")
       start, ok := val.(time.Time)
       if ok {
          duration := time.Since(start).Milliseconds()
          c.vector.WithLabelValues(typ, db.Statement.Table).
             Observe(float64(duration))
       }
    }
}

(3)利用 wrk 压测 signUp() 接口,结果如下

Prometheus埋点技巧:统计 GORM 连接池状态和查询时间

可见,平均查询时间为 231毫秒(我的电脑性能好差emmm

转载自:https://juejin.cn/post/7392071838640799755
评论
请登录