Prometheus export 开发系列:3 MySQL Exporter 开发

1 数据采集原理

这里我通过 mysql 监控开发,来演示其实监控其他的第三方程序都是一样的

这里有一个 exporter 暴露一个 /metrics 的 api , 但是这又有一个第三方应用程序,exporter 如何去获取到当前第三方程序的采集数据,因为他获取数据以后才能够实现暴露。

exporter 对外提供接口,这个接口并不是一个 web 的 api ,比如我们的 mysql 协议就是一种 API ,但是有的 exporter 会提供 http 接口或者接口写到文件里面去,如 Linux 的内存使用信息或者 cpu 使用信息我们可以在 /proc 里面获取

获取文件信息

如下读取文件获取信息:

# 获取内存信息
[16:52:14 root@go ~]#cat /proc/meminfo 
MemTotal:        3861300 kB
MemFree:         2257728 kB
MemAvailable:    2897096 kB
....

# 获取 cpu 信息
[18:21:20 root@go ~]#cat /proc/cpuinfo 
processor   : 0
vendor_id   : AuthenticAMD
cpu family  : 23
model       : 96
model name  : AMD Ryzen 7 4800H with Radeon Graphics
stepping    : 1
microcode   : 0x8600104
...

获取程序信息

如果是监控 mysql 的话我们可以通过执行 sql 语句获取信息

[18:25:08 root@go ~]#mysql -u root -p123456
# 获取数据库的运行状态
MariaDB [(none)]> show global status;

# 获取数据库链接信息
MariaDB [(none)]> show global variables;

获取命令信息

我们也可以通过命令来获取当前信息

[18:32:43 root@go ~]#free -h
              total        used        free      shared  buff/cache   available
Mem:           3.7G        693M        2.1G         11M        879M        2.8G
Swap:          6.0G          0B        6.0G

以上就是获取数据的几种方式,当我知道这些数据之后我们就可以定时的来进行调用,如果是采集的时间比较长那就不适合用在监控中触发

2 MySQL 监控资源采集项

对 mysql 的监控资源采集一般我们关系的是下面两个数据资源,这两条命令查询出来的数据是 KEY:V 格式,但是需要将那些数据暴露给 Prometheus 呢

# 获取数据库的运行状态
MariaDB [(none)]> show global status;

# 获取数据库链接信息
MariaDB [(none)]> show global variables;

# 开启慢查询
MariaDB [(none)]> set global slow_query_log = true;

一般常用监控资源如下:

mysq1 可用性
    操作失败
        select 1;   # 如果在mysql 中执行 sql 语句失败也是不可用状态
        ping        # 如果ping mysql 失败也是一个不可用状态

慢查询次数,也就是对于执行的 sql 语句速度比较慢的进行统计,慢查询次数是影响数据库性能的
show global status where variable_name='slow_queries';

容量: 当前执行 sql 语句的总数
show global status where variable_name='Queries';

操作:想知道不同的操作如: insert, update, delete 操作的数量
show global status where Variable_name like 'com_insert';
show global status where Variable_name like 'com_update';
show global status where Variable_name like 'com_delete';
show global status where Variable_name like 'com_select';
show global status where Variable_name like 'com_replace';

连接:
# 当前链接数
show global status where variable_name='Threads_connected';

# 当前最大链接数。后期可以配置告警,当我们的连接数量和我的最大连接数如果差值为 20 的时候产生报警
show global variables where variable_name='max_connections';


流量:
# 发送流量
show global status where variable_name='Bytes_ received';

# 接收流量
show global status where variable_name= 'Bytes_sent';

我们拿到了 mysql 的监控资源采集指标就可以通过这些资源进行暴露

2.1 开发 mysql 监控指标

现在我们已经知道了要采集那些 mysql 的指标,那指标如何采集到,我们就需要定下来采集指标的类型以及我们采集指标的触发时间

数据库监控项采集类型定义:

    /*
        A.带固定标签的指标项
            1.MySQL 存活状态,定义类型:gauage
            2.mysql 监控的地址信息暴露,定义类型:gauage

        B.指标项:
            1.慢查询数量,定义类型:counter,不用带 label
            2.数据库执行操作数量,定义类型:counter,不用带 label
            3.数据库执行查询数量,定义类型:counter,不用带 label
              这里只统计 insert, update, delete select 语句的数量,带可变label
            4.统计流量,定义类型:counter,不用带 label
            5.统计连接数量,定义类型:gauage,不用带 label

        C.指标类型和触发时间:
            在采集 API 的时候进行触发
    */

2.1.1 MySQL存活状态指标采集范例

package main

import (
    "database/sql"
    "log"
    "net/http"

    _ "github.com/go-sql-driver/mysql"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

// MySQL 存活状态指标采集
func main() {
    webAddr := ":9090"

    // 连接数据库
    dsn := "root:123456@tcp(10.0.0.3:3306)/mysql"
    mySqlAddr := "localhost:3306"
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        log.Fatal(err)
    }

    // 定义指标

    // 1.检查 mysql 是否存活,是通过 gauage 指标类型定义,
    // 2.prometheus.MustRegister 注册 mysql_up 指标
    prometheus.MustRegister(prometheus.NewGaugeFunc(prometheus.GaugeOpts{
        Namespace:   "mysql",
        Name:        "mysql_up",
        Help:        "Mysql UP info",
        ConstLabels: prometheus.Labels{"addr": mySqlAddr},
    }, func() float64 {
        // 采集数据通过 db.ping 来对数据库进行存活检查,如果 err 为 nil 表示连接成功返回 1 否则返回 0
        if err := db.Ping(); err == nil {
            return 1
        } else {
            return 0
        }
    }))

    // 3.暴露指标
    http.Handle("/metrics/", promhttp.Handler())
    http.ListenAndServe(webAddr, nil)
}

浏览器访问,可以看到状态码为 1 ,已经链接mysql,mysql 存活检查成功

http://10.0.0.3:9090/metrics/

验证程序,这里我将10.0.0.3 服务器上的 mysql stop 不掉

[22:28:27 root@go exec]#systemctl stop mariadb.service 

浏览器访问状态码为 0 状态失败,所以我们这个程序已经能够实现对数据库的存活状态监控

我这里再将 mariadb 数据库启动

[00:04:44 root@go ~]#systemctl start mariadb.service 

状态已经为 1 ,后期就通过这种方式来对 mysql 的状态进行检测

2.1.2 慢查询指标范例

我通过定义 collector 接口来实现对数据库的慢查询采集,并且单独写到 collectors 目录中

1.编写链接数据库程序

package linkmysql

import (
    "database/sql"
    "log"
)

func LinkDB() *sql.DB {
    // 链接 mysql
    dsn := "root:123456@tcp(10.0.0.3:3306)/mysql"

    db, err := sql.Open("mysql", dsn)
    if err != nil {
        log.Fatal(err)
    }
    return db
}

2.编写慢查询监控项

package collectors

import (
    "database/sql"
    "log"

    "github.com/prometheus/client_golang/prometheus"
)

type SlowQueriesCollector struct {
    // 定义 db 属性类型为 *sql.DB 结构体
    db       *sql.DB
    slowDesc *prometheus.Desc
}

// 通过调用结构体属性来查询监控指标
func (c *SlowQueriesCollector) Describe(descs chan<- *prometheus.Desc) {
    // 将指标描述信息写入 descs 只写管道中
    descs <- c.slowDesc
}

// 对监控指标采集数据
func (c *SlowQueriesCollector) Collect(metric chan<- prometheus.Metric) {
    // 因为采集慢查询有两个数据值,所以需要定义两个变量
    var (
        name  string
        count float64
    )

    // 执行单条 sql 查询语句,并将返回值重新赋值给 name, count
    if err := c.db.QueryRow("show global status where variable_name='slow_queries';").Scan(&name, &count); err != nil {
        log.Panic(err)
    }

    metric <- prometheus.MustNewConstMetric(
        c.slowDesc,              // 采集监控指标
        prometheus.CounterValue, // 指标类型为 counter 对递增、递减的指标进行采集
        count,                   // 采集指标的值
    )
}

// 注册监控指标项
func SlowQueriesRegister(db *sql.DB) {
    prometheus.MustRegister(&SlowQueriesCollector{
        // 赋值 db
        db: db,
        slowDesc: prometheus.NewDesc(
            // 指标名称
            "Mysql_globel_status_slow_queries",
            // 指标帮助
            "MySQL 慢查询指标",
            // 没有可变标签
            nil,
            // 没有固定标签
            nil,
        ),
    })
}

3.编写 mysql 存活指标项

package collectors

import (
    "database/sql"

    "github.com/prometheus/client_golang/prometheus"
)

func MysqlUp(db *sql.DB) {
    // 定义标签
    mySqlAddr := "localhost:3306"

    // 1.检查 mysql 是否存活,是通过 gauage 指标类型定义,
    // 2.prometheus.MustRegister 注册 mysql_up 指标
    prometheus.MustRegister(prometheus.NewGaugeFunc(prometheus.GaugeOpts{
        Namespace:   "mysql",
        Name:        "mysql_up",
        Help:        "Mysql 存活检查",
        ConstLabels: prometheus.Labels{"addr": mySqlAddr},
    }, func() float64 {
        // 采集数据通过 db.ping 来对数据库进行存活检查,如果 err 为 nil 表示连接成功返回 1 否则返回 0
        if err := db.Ping(); err == nil {
            return 1
        } else {
            return 0
        }
    }))
}

4.到 main 程序调用 mysql 存活和 慢查询

package main

import (
    "mysqlmonitor/collectors"
    "mysqlmonitor/linkmysql"
    "net/http"

    _ "github.com/go-sql-driver/mysql"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

// MySQL 存活状态指标采集
func main() {
    webAddr := ":9090"

    // 链接数据库
    db := linkmysql.LinkDB()

    // 调用 MysqlUp 监控指标
    collectors.MysqlUp(db)

    // 调用慢查询
    collectors.SlowQueriesRegister(db)
    // 3.暴露指标
    http.Handle("/metrics/", promhttp.Handler())
    http.ListenAndServe(webAddr, nil)
}

5.启动程序浏览器访问

6.验证慢查询,这里我查询一条 sql 语句并通过 sleep 函数人为产生一条查询

MariaDB [mysql]> select sleep(12), Host from user where Host like 'l%' ;
+-----------+-----------+
| sleep(12) | Host      |
+-----------+-----------+
|         0 | localhost |
|         0 | localhost |
+-----------+-----------+
2 rows in set (24.01 sec)       # 因为有两条数据所以耗时 24 秒

7.此时我们可以看到在慢查询指标中就触发了一条记录

2.1.3 采集当前执行 sql 语句总数范例

这里我对 qps 语句进行查询,由于 qps 也是属于在查询语句中的,所以和慢查询语句放到同一个文件中

1.编写采集指标

package collectors

import (
    "database/sql"

    "github.com/prometheus/client_golang/prometheus"
)

// 定义 Qps 语句的监控指标
type QpsCollector struct {
    db   *sql.DB
    desc *prometheus.Desc
}

// 查询监控指标
func (c *QpsCollector) Describe(descs chan<- *prometheus.Desc) {
    descs <- c.desc
}

// 采集监控指标
func (c *QpsCollector) Collect(metric chan<- prometheus.Metric) {
    var (
        name  string
        count float64
    )
    // 获取监控指标
    if err := c.db.QueryRow("show global status where variable_name='Queries';").Scan(&name, &count); err != nil {
        log.Panic(err)
    }

    metric <- prometheus.MustNewConstMetric(
        c.desc,                  // 采集监控指标
        prometheus.CounterValue, // 采集数据类型
        count,                   // 采集指标的值
    )
}

// 注册并给 QpsCollector 结构体赋值
func QpsRegister(db *sql.DB) {
    prometheus.MustRegister(&QpsCollector{
        db: db,
        desc: prometheus.NewDesc(
            "MySQL_QPS",        // 采集名称
            "MySQL 当前执行语句总数采集", // 采集帮助信息
            nil,                // 可变标签
            nil,                // 固定标签
        ),
    })
}

2.连接数据库

package linkmysql

import (
    "database/sql"
    "log"
)

func LinkDB() *sql.DB {
    // 链接 mysql
    dsn := "root:123456@tcp(10.0.0.3:3306)/mysql"

    db, err := sql.Open("mysql", dsn)
    if err != nil {
        log.Fatal(err)
    }
    return db
}

3.main 程序调用

package main

import (
    "mysqlmonitor/collectors"
    "mysqlmonitor/linkmysql"
    "net/http"

    _ "github.com/go-sql-driver/mysql"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
    webAddr := ":9090"

    // 调用 qps 语句监控指标
    collectors.QpsRegister(db)
    // 3.暴露指标
    http.Handle("/metrics/", promhttp.Handler())
    http.ListenAndServe(webAddr, nil)
}

3.浏览器访问

http://10.0.0.3:9090/metrics/

4.mysql 中执行一条sql 语句用来验证

MariaDB [(none)]> show global status where variable_name='Queries';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Queries       | 397   |           # 397 条数据查询
+---------------+-------+

5.浏览器验证

之所以 398 因为程序在执行本身他就会去执行 show global status where variable_name='Queries'; SQL 语句

2.1.4 采集当前 mysql 操作语句范例

我这里单独创建一个 comd.go 程序,用来采集数据库的执行语句数据

并且 mysql 中的增删改查语句都是带有可变标签的,我们将 增删改查 这四个动作写到同一个指标里面,通过标签来进行区分

1.编写采集指标

package collectors

import (
    "database/sql"

    "github.com/prometheus/client_golang/prometheus"
)

type CommandCollector struct {
    db   *sql.DB
    desc *prometheus.Desc
}

// 定义采集监控指标
func (c *CommandCollector) Describe(desc chan<- *prometheus.Desc) {
    desc <- c.desc
}

// 采集监控指标
func (c *CommandCollector) Collect(metric chan<- prometheus.Metric) {
    var (
        name  string
        count float64
    )

    // 执行 sql 语句获取 insert 数量
    c.db.QueryRow("show global status where variable_name like 'com_insert';").Scan(&name, &count)
    // 采集 insert 语句指标
    metric <- prometheus.MustNewConstMetric(c.desc, prometheus.CounterValue, count, "insert")

    // 执行 sql 语句获取 delete 数量
    c.db.QueryRow("show global status where variable_name like 'com_delete';").Scan(&name, &count)
    // 采集 delete 语句指标
    metric <- prometheus.MustNewConstMetric(c.desc, prometheus.CounterValue, count, "delete")

    // 执行 sql 语句获取 update 数量
    c.db.QueryRow("show global status where variable_name like 'com_update';").Scan(&name, &count)
    // 采集 update 语句指标
    metric <- prometheus.MustNewConstMetric(c.desc, prometheus.CounterValue, count, "update")

    // 执行 sql 语句获取 select 数量
    c.db.QueryRow("show global status where variable_name like 'com_select';").Scan(&name, &count)
    // 采集 select 语句指标
    metric <- prometheus.MustNewConstMetric(c.desc, prometheus.CounterValue, count, "select")

}

// 注册
func SqlRegister(db *sql.DB) {
    prometheus.MustRegister(&CommandCollector{
        db: db,
        desc: prometheus.NewDesc(
            "mysql_sql",         // 指标名称
            "mysql_sql 指标获取",    // 指标帮助
            []string{"command"}, // 指标可变标签
            nil,                 // 由于是可变标签没有固定标签
        ),
    })
}

2.连接数据库

package linkmysql

import (
    "database/sql"
    "log"
)

func LinkDB() *sql.DB {
    // 链接 mysql
    dsn := "root:123456@tcp(10.0.0.3:3306)/mysql"

    db, err := sql.Open("mysql", dsn)
    if err != nil {
        log.Fatal(err)
    }
    return db
}

3.main 程序调用

package main

import (
    "mysqlmonitor/collectors"
    "mysqlmonitor/linkmysql"
    "net/http"

    _ "github.com/go-sql-driver/mysql"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

// MySQL 存活状态指标采集
func main() {
    webAddr := ":9090"

    // 链接数据库
    db := linkmysql.LinkDB()

    // 调用 sql 语句监控指标
    collectors.SqlRegister(db)

    // 3.暴露指标
    http.Handle("/metrics/", promhttp.Handler())
    http.ListenAndServe(webAddr, nil)
}

4.浏览器访问

2.1.5 修改代码去掉冗余性

但是我们发现在采集指标项的时候有太多的代码都是相同的,所以我们为了让他不要这么冗余、我们可以对代码进行修改,这里单独编写一个 mysqlCollector 结构体,由于是该程序内部调用,所以我们这里首字母为小写

1.编写 mysql 查询语句

package collectors

import "database/sql"

type mysqlCollector struct {
    db *sql.DB
}

// 通过 sql 语句来获取 status 监控项
func (c *mysqlCollector) status(name string) float64 {
    var (
        newname string
        total   float64
    )

    sql := "show global status where variable_name=?"
    c.db.QueryRow(sql, name).Scan(&newname, &total)
    return total
}

// 通过 sql 语句来获取 variable 监控项
func (c *mysqlCollector) variable(name string) float64 {
    var (
        newname string
        total   float64
    )

    sql := "show global variables where variable_name=?"
    c.db.QueryRow(sql, name).Scan(&newname, &total)
    return total

}

2.修改 queries 程序代码

package collectors

import (
    "database/sql"

    "github.com/prometheus/client_golang/prometheus"
)

type SlowQueriesCollector struct {
    mysqlCollector // 匿名结构体组合 mysqlCollector
    slowDesc       *prometheus.Desc
}

// 通过调用结构体属性来查询监控指标
func (c *SlowQueriesCollector) Describe(desc chan<- *prometheus.Desc) {
    // 将指标描述信息写入 descs 只写管道中
    desc <- c.slowDesc
}

// 对慢查询指标采集数据
func (c *SlowQueriesCollector) Collect(metric chan<- prometheus.Metric) {
    metric <- prometheus.MustNewConstMetric(
        c.slowDesc,              // 采集监控指标
        prometheus.CounterValue, // 指标类型为 counter 对递增、递减的指标进行采集
        c.sql("slow_queries"),   // 采集慢查询指标的值
    )
}

// 注册慢查询监控项
func SlowQueriesRegister(db *sql.DB) {
    prometheus.MustRegister(&SlowQueriesCollector{
        // 赋值 db 到 mysqlCollector 结构体组合中,拿到当前数据库信息
        mysqlCollector: mysqlCollector{db},
        slowDesc: prometheus.NewDesc(
            "Mysql_globel_status_slow_queries", // 指标名称
            "MySQL 慢查询指标",                      // 指标帮助
            nil,                                // 没有可变标签
            nil,                                // 没有固定标签
        ),
    })
}

// 定义 Qps 语句的监控指标
type QpsCollector struct {
    mysqlCollector // 匿名结构体组合 mysqlCollector
    desc           *prometheus.Desc
}

// 查询监控指标
func (c *QpsCollector) Describe(descs chan<- *prometheus.Desc) {
    descs <- c.desc
}

// 采集当前执行 sql 语句监控指标
func (c *QpsCollector) Collect(metric chan<- prometheus.Metric) {
    metric <- prometheus.MustNewConstMetric(
        c.desc,                  // 采集监控指标
        prometheus.CounterValue, // 采集数据类型
        c.sql("Queries"),        // 采集当前执行 sql 语句指标的值
    )
}

// 注册并给 QpsCollector 结构体赋值
func QpsRegister(db *sql.DB) {
    prometheus.MustRegister(&QpsCollector{
        mysqlCollector: mysqlCollector{db},
        desc: prometheus.NewDesc(
            "MySQL_QPS",        // 采集名称
            "MySQL 当前执行语句总数采集", // 采集帮助信息
            nil,                // 可变标签
            nil,                // 固定标签
        ),
    })
}

3.修改 sql 查询程序代码

package collectors

import (
    "database/sql"

    "github.com/prometheus/client_golang/prometheus"
)

type CommandCollector struct {
    mysqlCollector
    desc *prometheus.Desc
}

// 定义采集监控指标
func (c *CommandCollector) Describe(desc chan<- *prometheus.Desc) {
    desc <- c.desc
}

// 采集监控指标
func (c *CommandCollector) Collect(metric chan<- prometheus.Metric) {
    // 采集 insert,delete,update,select 语句
    sqlCmd := []string{"com_insert", "com_delete", "com_update", "com_select"}
    for _, sql := range sqlCmd {
        metric <- prometheus.MustNewConstMetric(c.desc, prometheus.CounterValue, c.sql(sql), sql)
    }
}

// 注册
func SqlRegister(db *sql.DB) {
    prometheus.MustRegister(&CommandCollector{
        mysqlCollector: mysqlCollector{db},
        desc: prometheus.NewDesc(
            "mysql_sql",         // 指标名称
            "mysql_sql 指标获取",    // 指标帮助
            []string{"command"}, // 指标可变标签
            nil,                 // 由于是可变标签没有固定标签
        ),
    })
}

启动程序,浏览器访问实现监控指标获取

2.1.6 mysql链接数指标范例

我们修改了代码的冗余之后,接下来就编写 mysql 链接指标范例,编写一个 connection.go 的程序

1.编写链接数据

package collectors

import (
    "database/sql"

    "github.com/prometheus/client_golang/prometheus"
)

type ConnectionCollector struct {
    mysqlCollector

    // 由于有两个指标所以这里我写到一起
    connectedDesc    *prometheus.Desc
    maxconnectedDesc *prometheus.Desc
}

// 每个收集器都必须实现descripe函数
func (c *ConnectionCollector) Describe(desc chan<- *prometheus.Desc) {
    desc <- c.connectedDesc
    desc <- c.maxconnectedDesc
}

// 采集监控指标
func (c *ConnectionCollector) Collect(metric chan<- prometheus.Metric) {
    // 当前链接数指标
    metric <- prometheus.MustNewConstMetric(
        c.connectedDesc,               // 采集监控指标
        prometheus.CounterValue,       // 采集值类型
        c.status("Threads_connected"), // 采集 “已连接的线程” 监控指标
    )

    // 最大链接数指标
    metric <- prometheus.MustNewConstMetric(
        c.maxconnectedDesc,
        prometheus.CounterValue,
        c.variable("max_connections"),
    )
}

// 注册
func ConnectionRegister(db *sql.DB) {
    prometheus.MustRegister(&ConnectionCollector{
        mysqlCollector: mysqlCollector{db},
        connectedDesc: prometheus.NewDesc(
            "mysql_connection", // 指标名称
            "mysql 当前链接采集",     // 指标帮助信息
            nil,                // 没有可变标签
            nil,                // 没有固定标签
        ),
        maxconnectedDesc: prometheus.NewDesc(
            "mysql_MAX_connection",
            "mysql 最大链接数",
            nil,
            nil,
        ),
    })
}

2.在 main 程序中注册

package main

import (
    "mysqlmonitor/collectors"
    "mysqlmonitor/linkmysql"
    "net/http"

    _ "github.com/go-sql-driver/mysql"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

// MySQL 存活状态指标采集
func main() {
    webAddr := ":9090"

    // 链接数据库
    db := linkmysql.LinkDB()
    
    // 调用连接数监控项
    collectors.ConnectionRegister(db)

    // 3.暴露指标
    http.Handle("/metrics/", promhttp.Handler())
    http.ListenAndServe(webAddr, nil)
}

浏览器访问

2.1.7 mysql 流量指标获取范例

我们都知道流量是可变的所以这里我采用 Gauge 类型进行检测

1.编写 Flow 程序

package collectors

import (
    "database/sql"

    "github.com/prometheus/client_golang/prometheus"
)

type Flow struct {
    mysqlCollector
    desc *prometheus.Desc   // 因为他们都属一同一类指标所以用一个指标项
}

// 获取收集器
func (c *Flow) Describe(desc chan<- *prometheus.Desc) {
    desc <- c.desc
}

// 采集监控指标
func (c *Flow) Collect(metric chan<- prometheus.Metric) {
    metric <- prometheus.MustNewConstMetric(c.desc, prometheus.GaugeValue, c.status("Bytes_ received"), "发送流量")
    metric <- prometheus.MustNewConstMetric(c.desc, prometheus.GaugeValue, c.status("Bytes_sent"), "接收流量")
}

// 注册
func FlowRegister(db *sql.DB) {
    prometheus.MustRegister(&Flow{
        mysqlCollector: mysqlCollector{db},
        desc: prometheus.NewDesc(
            "mysql_Folw",
            "mysql 流量监控",
            []string{"flow_to"},
            nil,
        ),
    })
}

2.在 main 程序中注册

package main

import (
    "mysqlmonitor/collectors"
    "mysqlmonitor/linkmysql"
    "net/http"

    _ "github.com/go-sql-driver/mysql"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

// MySQL 存活状态指标采集
func main() {
    webAddr := ":9090"

    // 链接数据库
    db := linkmysql.LinkDB()

    // 调用流量监测监控项
    collectors.FlowRegister(db)

    // 3.暴露指标
    http.Handle("/metrics/", promhttp.Handler())
    http.ListenAndServe(webAddr, nil)
}

浏览器访问

3 代码总览

以上就是一个 exporter 基本的开发流程:

  • 数据收集

  • 数据指标监控

  • 注册数据

所有的第三方 exporter 都是这样的一个流程:

  1. 我们要知道数据暴露的这么一个接口

  2. 需要采集哪些数据进行暴露

项目地址:https://github.com/As9530272755/Prometheus_exporter/tree/main/mysql_exporter

代码流程:

项目目录结构:

1.编写链接数据库代码 linkmysql.go,如果后期我们采集其他数据就连接其他服务

package linkmysql

import (
    "database/sql"
    "log"
)

func LinkDB() *sql.DB {
    // 链接 mysql
    dsn := "root:123456@tcp(10.0.0.3:3306)/mysql"

    db, err := sql.Open("mysql", dsn)
    if err != nil {
        log.Fatal(err)
    }
    return db
}

2.编写数据库执行 sql 语句程序mysql.go

package collectors

import "database/sql"

type mysqlCollector struct {
    db *sql.DB
}

// 通过 sql 语句来获取 status 监控项
func (c *mysqlCollector) status(name string) float64 {
    var (
        newname string
        total   float64
    )

    sql := "show global status where variable_name=?"
    c.db.QueryRow(sql, name).Scan(&newname, &total)
    return total
}

// 通过 sql 语句来获取 variable 监控项
func (c *mysqlCollector) variable(name string) float64 {
    var (
        newname string
        total   float64
    )

    sql := "show global variables where variable_name=?"
    c.db.QueryRow(sql, name).Scan(&newname, &total)
    return total

}

3.编写数据库存活状态监控项代码,mysqlup.go 程序

package collectors

import (
    "database/sql"

    "github.com/prometheus/client_golang/prometheus"
)

func MysqlUp(db *sql.DB) {
    // 定义标签
    mySqlAddr := "localhost:3306"

    // 1.检查 mysql 是否存活,是通过 gauage 指标类型定义,
    // 2.prometheus.MustRegister 注册 mysql_up 指标
    prometheus.MustRegister(prometheus.NewGaugeFunc(prometheus.GaugeOpts{
        Namespace:   "mysql",
        Name:        "mysql_up",
        Help:        "Mysql 存活检查",
        ConstLabels: prometheus.Labels{"addr": mySqlAddr},
    }, func() float64 {
        // 采集数据通过 db.ping 来对数据库进行存活检查,如果 err 为 nil 表示连接成功返回 1 否则返回 0
        if err := db.Ping(); err == nil {
            return 1
        } else {
            return 0
        }
    }))
}

4.慢查询程序和当前执行的 sql 语句程序放到了一个程序文件中,queries.go

package collectors

import (
    "database/sql"

    "github.com/prometheus/client_golang/prometheus"
)

type SlowQueriesCollector struct {
    mysqlCollector // 匿名结构体组合 mysqlCollector
    slowDesc       *prometheus.Desc
}

// 通过调用结构体属性来查询监控指标
func (c *SlowQueriesCollector) Describe(desc chan<- *prometheus.Desc) {
    // 将指标描述信息写入 descs 只写管道中
    desc <- c.slowDesc
}

// 对慢查询指标采集数据
func (c *SlowQueriesCollector) Collect(metric chan<- prometheus.Metric) {
    metric <- prometheus.MustNewConstMetric(
        c.slowDesc,               // 采集监控指标
        prometheus.CounterValue,  // 指标类型为 counter 对递增、递减的指标进行采集
        c.status("slow_queries"), // 采集慢查询指标的值
    )
}

// 注册慢查询监控项
func SlowQueriesRegister(db *sql.DB) {
    prometheus.MustRegister(&SlowQueriesCollector{
        // 赋值 db 到 mysqlCollector 结构体组合中,拿到当前数据库信息
        mysqlCollector: mysqlCollector{db},
        slowDesc: prometheus.NewDesc(
            "Mysql_globel_status_slow_queries", // 指标名称
            "MySQL 慢查询指标",                      // 指标帮助
            nil,                                // 没有可变标签
            nil,                                // 没有固定标签
        ),
    })
}

// 定义 Qps 语句的监控指标
type QpsCollector struct {
    mysqlCollector // 匿名结构体组合 mysqlCollector
    desc           *prometheus.Desc
}

// 查询监控指标
func (c *QpsCollector) Describe(descs chan<- *prometheus.Desc) {
    descs <- c.desc
}

// 采集当前执行 sql 语句监控指标
func (c *QpsCollector) Collect(metric chan<- prometheus.Metric) {
    metric <- prometheus.MustNewConstMetric(
        c.desc,                  // 采集监控指标
        prometheus.CounterValue, // 采集数据类型
        c.status("Queries"),     // 采集当前执行 sql 语句指标的值
    )
}

// 注册并给 QpsCollector 结构体赋值
func QpsRegister(db *sql.DB) {
    prometheus.MustRegister(&QpsCollector{
        mysqlCollector: mysqlCollector{db},
        desc: prometheus.NewDesc(
            "MySQL_QPS",        // 采集名称
            "MySQL 当前执行语句总数采集", // 采集帮助信息
            nil,                // 可变标签
            nil,                // 固定标签
        ),
    })
}

5.所有的 sql 语句查询,comd.go

package collectors

import (
    "database/sql"

    "github.com/prometheus/client_golang/prometheus"
)

type CommandCollector struct {
    mysqlCollector
    desc *prometheus.Desc
}

// 定义采集监控指标
func (c *CommandCollector) Describe(desc chan<- *prometheus.Desc) {
    desc <- c.desc
}

// 采集监控指标
func (c *CommandCollector) Collect(metric chan<- prometheus.Metric) {
    // 采集 insert,delete,update,select 语句
    sqlCmd := []string{"com_insert", "com_delete", "com_update", "com_select"}
    for _, sql := range sqlCmd {
        metric <- prometheus.MustNewConstMetric(c.desc, prometheus.CounterValue, c.status(sql), sql)
    }
}

// 注册
func CommandRegister(db *sql.DB) {
    prometheus.MustRegister(&CommandCollector{
        mysqlCollector: mysqlCollector{db},
        desc: prometheus.NewDesc(
            "mysql_sql",         // 指标名称
            "sql 语句指标获取",        // 指标帮助
            []string{"command"}, // 指标可变标签
            nil,                 // 由于是可变标签没有固定标签
        ),
    })
}

6.数据库链接数监控项程序,connection.go

package collectors

import (
    "database/sql"

    "github.com/prometheus/client_golang/prometheus"
)

type ConnectionCollector struct {
    mysqlCollector

    // 由于有两个指标所以这里我写到一起
    connectedDesc    *prometheus.Desc
    maxconnectedDesc *prometheus.Desc
}

// 每个收集器都必须实现descripe函数
func (c *ConnectionCollector) Describe(desc chan<- *prometheus.Desc) {
    desc <- c.connectedDesc
    desc <- c.maxconnectedDesc
}

// 采集监控指标
func (c *ConnectionCollector) Collect(metric chan<- prometheus.Metric) {
    // 当前链接数指标
    metric <- prometheus.MustNewConstMetric(
        c.connectedDesc,               // 采集监控指标
        prometheus.CounterValue,       // 采集值类型
        c.status("Threads_connected"), // 采集 “已连接的线程” 监控指标
    )

    // 最大链接数指标
    metric <- prometheus.MustNewConstMetric(
        c.maxconnectedDesc,
        prometheus.CounterValue,
        c.variable("max_connections"),
    )
}

// 注册
func ConnectionRegister(db *sql.DB) {
    prometheus.MustRegister(&ConnectionCollector{
        mysqlCollector: mysqlCollector{db},
        connectedDesc: prometheus.NewDesc(
            "mysql_connection", // 指标名称
            "mysql 当前链接采集",     // 指标帮助信息
            nil,                // 没有可变标签
            nil,                // 没有固定标签
        ),
        maxconnectedDesc: prometheus.NewDesc(
            "mysql_MAX_connection",
            "mysql 最大链接数",
            nil,
            nil,
        ),
    })
}

7.编写数据库流量监控项程序,flow.go

package collectors

import (
    "database/sql"

    "github.com/prometheus/client_golang/prometheus"
)

type Flow struct {
    mysqlCollector
    desc *prometheus.Desc
}

func (c *Flow) Describe(desc chan<- *prometheus.Desc) {
    desc <- c.desc
}

func (c *Flow) Collect(metric chan<- prometheus.Metric) {
    metric <- prometheus.MustNewConstMetric(c.desc, prometheus.GaugeValue, c.status("Bytes_ received"), "发送流量")
    metric <- prometheus.MustNewConstMetric(c.desc, prometheus.GaugeValue, c.status("Bytes_sent"), "接收流量")
}

func FlowRegister(db *sql.DB) {
    prometheus.MustRegister(&Flow{
        mysqlCollector: mysqlCollector{db},
        desc: prometheus.NewDesc(
            "mysql_Folw",
            "mysql 流量监控",
            []string{"flow_to"},
            nil,
        ),
    })
}

8.最后在 main 程序中调用所有监控项

package main

import (
    "mysqlmonitor/collectors"
    "mysqlmonitor/linkmysql"
    "net/http"

    _ "github.com/go-sql-driver/mysql"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

// MySQL 存活状态指标采集
func main() {
    webAddr := ":9090"

    // 链接数据库
    db := linkmysql.LinkDB()

    // 调用 MysqlUp 监控指标
    collectors.MysqlUp(db)

    // 调用慢查询监控指标
    collectors.SlowQueriesRegister(db)

    // 调用 qps 语句监控指标
    collectors.QpsRegister(db)

    // 调用 sql 执行语句监控指标
    collectors.CommandRegister(db)

    // 调用连接数监控项
    collectors.ConnectionRegister(db)

    // 调用流量监测监控项
    collectors.FlowRegister(db)

    // 3.暴露指标
    http.Handle("/metrics/", promhttp.Handler())
    http.ListenAndServe(webAddr, nil)
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇