Prometheus埋点技巧:统计 GORM 连接池状态和查询时间
一、背景
而数据库查询是一个比 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
我们要关注的东西不多:
- 首先关注
gorm_dbstats_wait_count
和gorm_dbstats_wait_duration
,两个值很大的话,都说明你的连接数量不够,要增大配置。 - 其次要关注
gorm_dbstats_idle
,这个如果很大,可以调小最大空闲连接数的值。 - 如果
gorm_dbstats_max_idletime_closed
的值很大,可能是你的最大空闲时间设置得太小
2.2 统计查询时间
利用 GORM 的
Callback
来统计查询时间(理解为回调,当 GORM 执行语句的前或后, 就会执行你注册的回调)
执行注册的回调,要区分Before
和 After
,如下图
(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)具体实现
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()
接口,结果如下
可见,平均查询时间为 231毫秒(我的电脑性能好差emmm
转载自:https://juejin.cn/post/7392071838640799755