运维实战来了!如何构建适用于YashanDB的Prometheus Exporter

简介: 今天分享的是构建YashanDB Exporter的核心设计理念和关键方法,希望也能为你的运维实战加分!

背景
在数据库运维工作中,数据库监控是至关重要的一环。常见的数据库监控方案有:Prometheus+Grafana、Zabbix、Nagios等。其中,Prometheus+Grafana是目前业界较为流行的监控解决方案之一,广泛应用于:实时资源监控、监控结果可视化、资源异常告警、监控数据分析等场景。该方案中包含几个关键组件,其核心功能的简要说明如下:

根据上述核心组件的工作原理,我们可以分析出一个完整的监控流程:

Exporter负责收集特定的监控指标,并且按特定的格式组织结果;

Prometheus Server定时抓取Exporter提供的数据,解析数据并存储;

同时,如果指标触发告警条件,Prometheus Server会向Alert Manager推送告警信息;

Alert Manager收到推送后对告警进行相应处理,例如:使用短信、邮件等方式通知运维人员;

Grafana Server接入Prometheus Server作为数据源,将查询到的监控数据进行可视化展示。

综上所述,本方案中实现YashanDB监控的关键点在于——实现适用于YashanDB的Prometheus Exporter,后续我们称其为:YashanDB Exporter。至于其他组件,均为通用组件,使用官方发布的稳定软件包即可。下面就让我们一起探讨,如何构建YashanDB Exporter。

功能设计
1 整体架构设计
基于Prometheus+Grafana监控YashanDB,整体架构设计图如下。其中YashanDB Exporter可以抓取多个数据库实例的监控指标。Prometheus Server通过拉取YashanDB Exporter的Metrics接口,获取指标数据,并将数据存储到其后台时序数据库TSDB中。其他组件的工作流程在上文的背景中已经说明,在此不再赘述。

2 YashanDB Exporter架构设计
YashanDB Exporter的功能还是比较简单的,核心工作主要分成三大步骤:采集数据、包装数据以及分享数据。

1.采集数据:主要是借助协程池(Goroutines)并发使用SQL查询数据库的指标信息;

2.包装数据:把数据库的指标结果转换成Prometheus Server能够解析的数据格式(Prometheus Metrics);

3.分享数据:对外提供HTTP服务,外部服务通过请求Metrics接口就能够获取Exporter采集到的数据。

此外,为了灵活控制YashanDB Exporter的行为,我们需要两个配置文件:指标配置文件(Metric Config)和崖山数据库实例配置文件(DB Instance Config)。

1.指标配置文件:定义了用于查询数据的SQL,后续用户也可以通过编辑该文件来控制需要采集的指标;

崖山数据库实例配置文件:主要用于定义数据库实例的基本信息和连接信息,控制需要采集哪些数据库实例的信息。整体架构图如下:

背景与挑战
我们可以采用Go语言编码,利用官方提供的github.com/prometheus/client_golang开发包,快速构建YashanDB Exporter。

  1. 程序入口
    在程序入口中,我们定义了一个名为yashandb_exporter的命令行工具。启动程序时,核心工作流程如下:

1.首先执行必要的初始化工作;

2.其次根据配置,创建一个exporter实例;

3.通过prometheus.MustRegister(exporter)注册实例;

4.定义HTTP服务,将指标处理逻辑绑定到Mertics接口;

启动HTTP服务,对外提供Mertics接口。
const servername = "yashandb_exporter"

var (
httpsConfig = kingpinflag.AddFlags(kingpin.CommandLine, ":9100")
pprofAddr = kingpin.Flag(
"web.pprof-address",
"Address to listen on for pprof debug. (env: YAS_EXPORTER_PPROF_ADDRESS)").
Envar("YAS_EXPORTER_PPROF_ADDRESS").String()
metricsURL = kingpin.Flag(
"web.telemetry-path",
"Telemetry path under which to expose metrics. (env: YAS_EXPORTER_TELEMETRY_PATH)").
Default("/metrics").Envar("YAS_EXPORTER_TELEMETRY_PATH").String()
metricsConf = kingpin.Flag(
"yashandb.metrics",
"File with metrics in a YAML file. (env: YAS_EXPORTER_METRICS)").
Default("yashandb-metrics.yml").Envar("YAS_EXPORTER_METRICS").String()
databaseTargets = kingpin.Flag(
"yashandb.targets",
"File with database targets in a YAML file. (env: YAS_EXPORTER_TARGETS)").
Default("yashandb-targets.yml").Envar("YAS_EXPORTER_TARGETS").String()
scrapeTimeout = kingpin.Flag(
"scrape.timeout",
"Scrape timeout (in seconds). (env: YAS_EXPORTER_SCRAPE_TIMEOUT)").
Default("15").Envar("YAS_EXPORTER_SCRAPE_TIMEOUT").Uint()
scrapeMaxConcurrency = kingpin.Flag(
"scrape.max-concurrency",
"Maximum number of concurrent scrape tasks at the same time. (env: YAS_EXPORTER_SCRAPE_MAX_CONCURRENCY)").
Default("512").Envar("YAS_EXPORTER_SCRAPE_MAX_CONCURRENCY").Uint()
logLevel = kingpin.Flag(
"log.level",
"Log level of YashanDB Exporter. One of: [debug, info, warn, error]. (env: YAS_EXPORTER_LOG_LEVEL)").
Default("info").Envar("YAS_EXPORTER_LOG_LEVEL").String()
maxOpenConns = kingpin.Flag(
"max.open.conns",
"Max open connections of database. (env: YAS_EXPORTER_MAX_OPEN_CONNS)").
Default("3").Envar("YAS_EXPORTER_MAX_OPEN_CONNS").Uint()
)

func init() {
commons.InitBasePath()
commons.InitAuth()
}

func generateResponse(code int, message string) []byte {
res := &struct {
Code int json:"code"
Message string json:"message"
}{Code: code, Message: message}
data, _ := json.Marshal(res)
return data
}

func main() {
kingpin.Version(fmt.Sprintf("%s-%s", servername, commons.Version))
kingpin.HelpFlag.Short('h')
kingpin.Parse()

initLog(*logLevel)
log.Sugar.Infof("Starting %s-%s", servername, commons.Version)

exporterOpts := []collector.ExporterOpt{
    collector.WithScrapeMaxConcurrency(*scrapeMaxConcurrency),
    collector.WithScrapeTimeout(*scrapeTimeout),
    collector.WithMaxOpenConns(*maxOpenConns),
}
exporter, err := collector.NewExporter(*databaseTargets, *metricsConf, exporterOpts...)
if err != nil {
    log.Sugar.Fatalf("load configs failed: %s", err)
}
prometheus.MustRegister(exporter)

logger, _ := zap.NewStdLogAt(log.Logger, zap.ErrorLevel)
opts := promhttp.HandlerOpts{
    ErrorLog:      logger,
    ErrorHandling: promhttp.ContinueOnError,
}

mux := http.NewServeMux()
mux.Handle(*metricsURL, promhttp.HandlerFor(prometheus.DefaultGatherer, opts))
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/html; charset=UTF-8")
    _, _ = w.Write(generateServerHome(*metricsURL))
})

if *pprofAddr != "" {
    go debug.StartHTTPDebuger(*pprofAddr)
}
server := &http.Server{
    Handler:           mux,
    ReadHeaderTimeout: 32 * time.Second,
}
promlogger := promlog.New(&promlog.Config{})
if err := web.ListenAndServe(server, httpsConfig, promlogger); err != nil {
    log.Sugar.Fatal("Error running HTTP server:", err)
}
AI 代码解读

}

  1. Collector接口
    在3.1小节中,我们通过prometheus.MustRegister(exporter)注册实例后,再通过mux.Handle(*metricsURL, promhttp.HandlerFor(prometheus.DefaultGatherer, opts))就可以完成Metrics接口的构建。其中的关键在于如何实现exporter实例。通过查看prometheus.MustRegister函数,发现其参数是一个Collector接口,那么我们的exporter实例只需要实现Collector接口即可。接口定义如下:

type Collector interface {
Describe(chan<- *Desc)
Collect(chan<- Metric)
}
接口有两个函数:Describe和Collect。Describe会在prometheus.MustRegister时调用,执行一次标准的采集动作。而Collect则会在收到Metrics接口请求的时候调用,执行一次标准的采集动作。从Exporter的功能上来说,关键点在于Collect,而Describe就算不做任何事情也不会影响整体的功能。所以我们可以聚焦Collect函数,代码内容如下:

func (e Exporter) Describe(ch chan<- prometheus.Desc) {}

func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
e.mu.RLock()
yashandbList := e.originalYashandbList.DeepCopy()
metricList := e.originalMetricList.DeepCopy()
e.mu.RUnlock()

e.scrape(ch, yashandbList, metricList)
e.sendInstanceDisconnected(ch, yashandbList)
ch <- e.duration
ch <- e.totalScrapes
ch <- e.success
e.scrapeErrors.Collect(ch)
ch <- e.up
}
可以看到,关键在于e.scrape(ch, yashandbList, metricList)。传入的参数有三个:指标信息接收通道(ch)、YashanDB实例列表(yashandbList)以及采集的指标列表(metricList)。展开分析该函数:

func (e Exporter) scrape(ch chan<- prometheus.Metric, yashandbList YashanDBList, metricList *MetricList) {
e.reset()
e.totalScrapes.Inc()
e.up.Set(1)
defer func(begun time.Time) {
s := time.Since(begun).Seconds()
e.duration.Set(s)
log.Sugar.Debugf("scrape total time: %0.4fs", s)
}(time.Now())

scrapeSuccess := true
swg := sizedwaitgroup.New(int(e.scrapeMaxConcurrency))
for , yashandb := range yashandbList.YashanDBs {
for
, i := range yashandb.ScrapeInstances {
for _, m := range metricList.Metrics {
swg.Add()
go e.scrapeWithMetric(&swg, ch, yashandb, i, m, &scrapeSuccess)
}
}
}
swg.Wait()

if scrapeSuccess {
log.Sugar.Debug("scrape all metrics successfully")
e.success.Set(1)
}
}

func (e Exporter) scrapeWithMetric(
swg
sizedwaitgroup.SizedWaitGroup,
ch chan<- prometheus.Metric,
yashandb YashanDB,
scrapeInstance
ScrapeInstance,
metric YasMetric,
scrapeSuccess
bool,
) {
defer swg.Done()
start := time.Now()
if err := metric.Scrape(yashandb, scrapeInstance, ch, e); err != nil {
*scrapeSuccess = false
log.Sugar.Errorf("%0.4fs %s failed to scrape <%s>: %s", time.Since(start).Seconds(), scrapeInstance.Mark(), metric.Name, err)
return
}
if len(metric.SubMetrics) == 1 {
log.Sugar.Debugf("%0.4fs %s succeeded to scrape <%s>", time.Since(start).Seconds(), scrapeInstance.Mark(), metric.Name)
} else {
for , sub := range metric.SubMetrics {
log.Sugar.Debugf("%0.4fs %s succeeded to scrape <%s
%s>", time.Since(start).Seconds(), scrapeInstance.Mark(), metric.Name, sub.ColName)
}
}
}
上述代码中,采用了一个拥有最大并发数控制的协程池,对于每个数据库下每个实例的每个指标,都使用协程去并发采集数据。由此可见,一次采集所需的时间就是所有协程中耗时最长的协程的采集时间。根据代码我们可以看到,具体的采集操作是由metric.Scrape(yashandb, scrapeInstance, ch, e)函数执行的。查看函数的具体内容如下:

func (m YasMetric) Scrape(yashandb YashanDB, i ScrapeInstance, ch chan<- prometheus.Metric, e Exporter) error {
if i.genQuery(m) == "" {
log.Sugar.Debugf("%s, skip empty query", m.Name)
return nil
}
if err := i.Query(yashandb, m, ch, e.scrapeTimeout); err != nil {
i.scrapeFailedOnce = true
return err
}
i.scrapeSuccessOnce = true
return nil
}
可以看到,核心功能在于i.Query(yashandb, m, ch, e.scrapeTimeout),查看Query函数的相关内容如下所示。在该函数中,主要是使用YashanDB的Go语言驱动,通过SQL查询数据库信息,并把它转换成Prometheus Metric格式。最后把采集的指标通过i.exposeMetrics(yashandb, ch, prometheusMetrics)发送到指标信息接收通道。其中还引入了超时机制,如果本次查询达到最大超时时间,则终止查询,指标采集失败,协程超时返回。这样可以有效控制Metrics接口的返回时间,避免接口长时间无法返回数据。

func (i ScrapeInstance) Query(yashandb YashanDB, m *YasMetric, ch chan<- prometheus.Metric, timeout uint) error {
var prometheusMetrics []prometheus.Metric
errCh := make(chan error)
done := make(chan struct{})
defer close(done)
go func() {
defer func() {
if err := recover(); err != nil {
log.Sugar.Errorf("recover error: %v", err)
log.Sugar.Warnf("debug stack warn: %s", string(debug.Stack()))
}
}()
var err error
defer func() {
select {
case <-done:
case errCh <- err:
}
close(errCh)
}()

query := i.genQuery(m)
if query == "" {
  return
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(timeout))
defer cancel()
rows, err := i.db.QueryContext(ctx, query)
if err != nil {
  return
}
defer rows.Close()
err = rows.Err()
if err != nil {
  return
}
cols, err := rows.Columns()
if err != nil {
  return
}

result := []map[string]string{}
for rows.Next() {
  columnData := make([]interface{}, len(cols))
  scanArgs := make([]interface{}, len(cols))
  for i := range columnData {
    scanArgs[i] = &columnData[i]
  }
  err = rows.Scan(scanArgs...)
  if err != nil {
    return
  }
  row := make(map[string]string)
  for i, colName := range cols {
    if columnData[i] == nil {
      columnData[i] = ""
    }
    row[strings.ToLower(colName)] = fmt.Sprintf("%v", columnData[i])
  }
  result = append(result, row)
}

for _, row := range result {
  metrics, err := m.genPrometheusMetrics(yashandb, i, row)
  if err != nil {
    return
  }
  prometheusMetrics = append(prometheusMetrics, metrics...)
}
AI 代码解读

}()
select {
case err := <-errCh:
if err != nil {
return err
}
i.exposeMetrics(yashandb, ch, prometheusMetrics)
return nil
case <-time.After(time.Second * time.Duration(timeout)):
return errors.New("query timeout")
}
}
至此,我们已经完整个Exporter的核工作原理介绍:通过配置文件控制需要监控的指标以及数据库实例;再通过实现Collector接口,利用prometheus.MustRegister(exporter)注册完成后,通过promhttp.HandlerFor(prometheus.DefaultGatherer, opts)快速构建一个Metrics接口处理器;最后将处理器绑定到HTTP服务上,并启动服务即可。

  1. 配置文件说明
    配置文件其实就是一些数据结构的设计和简单的数据输入,能够获取到需要的数据即可。本方案中设计的配置文件示例如下:

指标配置文件示例如下,在配置文件中我们定义了数据库启动时间和数据库最大会话数两个简单指标。实际使用时,可以通过配置文件内置所需的监控指标,也可以后续增加自定义指标。

metrics:

  • name: uptime
    query: select EXTRACT(DAY FROM (sysdate - startup_time)) 60 60 24 +
    EXTRACT(HOUR FROM (sysdate - startup_time))
    60 60 +
    EXTRACT(MINUTE FROM (sysdate - startup_time))
    60 +
    EXTRACT(SECOND FROM (sysdate - startup_time)) AS uptime
    from v$instance
    sub_metrics:
    • col: uptime
      type: gauge
      description: Uptime of the database instance (in seconds).
  • name: max_sessions
    query: select value as max_sessions from sys.v$parameter where name = 'MAX_SESSIONS'
    sub_metrics:
    • col: max_sessions
      type: gauge
      description: Maximum number of database sessions.
      数据库实例配置文件示例如下,在配置文件中我们定义了一个名为yasdb的数据库,其拥有两个数据库实例:

targets:

- type: SE
  name: yasdb
  pkgVersion: 23.2.0.13
  nodes:
    - id: 1-1
      type: yasdn
      group: "1"
      name: instance1
      connection:
        ip: 127.0.0.1
        port: 1688
    - id: 1-2
      type: yasdn
      group: "1"
      name: instance2
      connection:
        ip: 127.0.0.1
        port: 1690
AI 代码解读

结果展示
1.首先部署YashanDB,我们快速部署一个单机单节点数据库,实例的监听地址设置为:127.0.0.1:1688。

2.然后编辑数据库实例配置文件,设置好数据库地址等信息。接着在9100端口启动YashanDB Exporter,此时访问Metrics接口已经可以看到采集到的数据库信息:

3.随后,我们可以通过docker快速拉起Prometheus Server和Grafana Server。先拉取docker镜像:

docker pull prom/prometheus
docker pull grafana/grafana
4.启动Prometheus Server:

生成配置文件

mkdir /opt/prometheus
cd /opt/prometheus/
vim prometheus.yml

键入以下内容并保存

global:
scrape_interval: 60s
evaluation_interval: 60s

scrape_configs:

  • job_name: prometheus
    static_configs:

    • targets: ['localhost:9090'] # Prometheus服务本身
      labels:
      instance: prometheus
  • job_name: yashandb_exporter
    static_configs:

    • targets: ['127.0.0.1:9100'] # YashanDB Exporter服务
      labels:
      instance: localhost

      在9090端口启动服务

      docker run -d \
      -p 9090:9090 \
      -v /opt/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \
      prom/prometheus
      5.启动Grafana Server:

新建空文件夹grafana-storage

mkdir /opt/grafana-storage

设置权限,测试环境设置777,比较简单粗暴!

chmod 777 -R /opt/grafana-storage

在3000端口启动服务

docker run -d \
-p 3000:3000 \
--name=grafana \
-v /opt/grafana-storage:/var/lib/grafana \
grafana/grafana
6.访问3000端口登录Grafana Server,初始用户名和密码均为:admin。

7.配置Grafana Server的数据源,选择Prometheus。根据我们前面的配置,其在9090端口运行:

8.添加Dashboard,以监控数据库实例的启动时间(yashandb_uptime)为例:

以上就是我通过第三方开发包快速搭建YashanDB Exporter的实战记录啦。YashanDB作为近年来关系数据库领域的后起之秀,其数据库生态也在不断蓬勃发展中。欢迎小伙伴们一起来探讨学习,共同摸索更多运维的高效方法。

目录
打赏
0
0
0
0
63
分享
相关文章
【运维实战分享】轻松搞定 SSL 证书管理,告别证书繁琐操作
Spug证书平台的最大亮点之一就是其极为简化的证书申请流程,无论是新手还是经验丰富的运维专家,都可以在几分钟内轻松完成证书的申请,通过微信扫码直接登录申请,无需复杂注册,整个过程既方便又快捷。
43 17
解锁高效运维新姿势!操作系统智能助手OS Copilot新功能实战测评
阿里云OS Copilot经过多轮迭代,现已支持多端操作系统(包括Ubuntu、CentOS、Anolis OS等)及aarch64架构,极大扩展了其适用范围。新特性包括阿里云CLI调用、系统运维及调优工具的直接调用、Agent模式实装以及复杂任务处理能力。这些更新显著提升了用户体验和效率,特别是在处理紧急情况时,OS Copilot能快速查找并执行命令,节省大量时间和精力。此外,通过自然语言交互,用户可以轻松完成如系统健康检查、文件操作及日志分析等任务。总之,OS Copilot已从内测时的辅助工具进化为合格的贴身管家,极大地简化了日常运维工作。
构建深度可观测、可集成的网络智能运维平台
本文介绍了构建深度可观测、可集成的网络智能运维平台(简称NIS),旨在解决云上网络运维面临的复杂挑战。内容涵盖云网络运维的三大难题、打造云原生AIOps工具集的解决思路、可观测性对业务稳定的重要性,以及产品发布的亮点,包括流量分析NPM、网络架构巡检和自动化运维OpenAPI,助力客户实现自助运维与优化。
基于AI的网络流量分析:构建智能化运维体系
基于AI的网络流量分析:构建智能化运维体系
467 13
Prometheus+Grafana+NodeExporter:构建出色的Linux监控解决方案,让你的运维更轻松
本文介绍如何使用 Prometheus + Grafana + Node Exporter 搭建 Linux 主机监控系统。Prometheus 负责收集和存储指标数据,Grafana 用于可视化展示,Node Exporter 则采集主机的性能数据。通过 Docker 容器化部署,简化安装配置过程。完成安装后,配置 Prometheus 抓取节点数据,并在 Grafana 中添加数据源及导入仪表盘模板,实现对 Linux 主机的全面监控。整个过程简单易行,帮助运维人员轻松掌握系统状态。
408 3
自动化运维的利器:Ansible实战应用
【10月更文挑战第41天】在现代IT运维领域,自动化已成为提高效率、减少错误的关键。Ansible作为一种简单而强大的自动化工具,正被越来越多的企业采纳。本文将通过实际案例,展示如何使用Ansible简化日常运维任务,包括配置管理和批量部署等,旨在为读者提供一种清晰、易懂的自动化解决方案。
68 1
智能运维实战:Prometheus与Grafana的监控与告警体系
【10月更文挑战第26天】Prometheus与Grafana是智能运维中的强大组合,前者是开源的系统监控和警报工具,后者是数据可视化平台。Prometheus具备时间序列数据库、多维数据模型、PromQL查询语言等特性,而Grafana支持多数据源、丰富的可视化选项和告警功能。两者结合可实现实时监控、灵活告警和高度定制化的仪表板,广泛应用于服务器、应用和数据库的监控。
532 3
Grafana Prometheus Altermanager 监控系统
Grafana、Prometheus 和 Alertmanager 是一套强大的开源监控系统组合。Prometheus 负责数据采集与存储,Alertmanager 处理告警通知,Grafana 提供可视化界面。本文简要介绍了这套系统的安装配置流程,包括各组件的下载、安装、服务配置及开机自启设置,并提供了访问地址和重启命令。适用于希望快速搭建高效监控平台的用户。
198 20
Prometheus+Grafana监控Linux主机
通过本文的步骤,我们成功地在 Linux 主机上使用 Prometheus 和 Grafana 进行了监控配置。具体包括安装 Prometheus 和 Node Exporter,配置 Grafana 数据源,并导入预设的仪表盘来展示监控数据。通过这种方式,可以轻松实现对 Linux 主机的系统指标监控,帮助及时发现和处理潜在问题。
270 7
无痛入门Prometheus:一个强大的开源监控和告警系统,如何快速安装和使用?
Prometheus 是一个完全开源的系统监控和告警工具包,受 Google 内部 BorgMon 系统启发,自2012年由前 Google 工程师在 SoundCloud 开发以来,已被众多公司采用。它拥有活跃的开发者和用户社区,现为独立开源项目,并于2016年加入云原生计算基金会(CNCF)。Prometheus 的主要特点包括多维数据模型、灵活的查询语言 PromQL、不依赖分布式存储、通过 HTTP 拉取时间序列数据等。其架构简单且功能强大,支持多种图形和仪表盘展示模式。安装和使用 Prometheus 非常简便,可以通过 Docker 快速部署,并与 Grafana 等可
891 2

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等