可扩展多组件监控方案

2021-11-23 13:11:25 浏览数 (1)

1、背景

项目开发中经常会用到很多外部组件,比如mongo、mysql、redis等,虽然在公有环境中使用云上的组件一般都会有完整的监控视图,但是有些项目是部署在私有环境中,使用的都是自建组件,没有完整的监控视图,其次,业务侧也希望将所有组件收归到一起进行统一的监控管理,这样就需要业务侧有一个多组件的监控平台,并且能够方便进行扩展。像笔者目前所在的项目组开发的大数据处理平台,使用了很多外部组件,元数据存储方面有:mongo、mysql、elasticsearch、redis、postgres,大数据存储方面有:hadoop(spark、hive、hbase、hdfs、yarn),为了更好的发现和定位问题,我们需要一个统一的监控管理中心。

2、选型

统一监控平台采用业界广泛使用的框架:prometheus grafana,其在功能和效果方面完全能达到业务侧需求,并且针对这个选型网上有很多开源组件的exporter和对应grafana视图,避免了很多自定义视图的繁琐工作。比如在这里可以找到很多不错的exporter和视图:https://promcat.io/、https://prometheus.io/docs/instrumenting/exporters/。

3、初始方案

Prometheus grafana工作原理是由prometheus定期拉取其配置的exporter进程的监控数据,然后grafana视图读取prometheus中的监控数据展示出来,如图1所示。既然我们可以从网上下载到个组件exporter和grafana视图,那就只需要启动各组件exporter进程,将进程地址配置到prometheus中进行定期拉取即可,方案很简单也不需要额外写代码,但是缺点是不方便新增和管理组件,像我们平台使用到了很多组件,我们就需要维护很多exporter进程。

图1 初始方案图1 初始方案

4、改进方案

上面方案因为prometheus直接对接exporter导致维护成本增加,如果在prometheus和exporter之间增加一个中间层进行管理就可以解决这个问题,如图2所示。

图2 改进方案图2 改进方案

4.1 管理exporter

将所有exporter、grafana视图放在monitor启动目录中,用于自动导入grafana视图、启动exporter进程、检测exporter进程存活状态。

图3 exporter管理目录图3 exporter管理目录

设计monitor的配置如下:

代码语言:javascript复制
[Plugin]
  [[Plugin.Instances]]
    Enable = false
    Folder = "Hadoop"
    Title = "Hadoop"
    DashboardDir = "../tools/hadoop"
    Port = 9001
    Command = "nohup python3 %v -cluster %v -nns %v -rms %v -host 0.0.0.0 -port %v &"
    Parameters = [
      "../tools/hadoop/hadoop_jmx_exporter-master/hadoop_jmx_exporter.py",
      "jm-test-emr",
      "http://127.0.0.1:4008/jmx http://127.0.0.1:4008/jmx",
      "http://127.0.0.1:5004/jmx http://127.0.0.1:5004/jmx",
      "9001",
   ]
   ...

4.2 聚合监控数据

Prometheus拉取数据有特定的协议,因此monitor需要汇聚所有exporter的监控数据后按照同样的协议发送给prometheus。

正常情况我们在写业务侧exporter暴露给prometheus时可以通过注册自己的Collector来收集监控数据,但是这里我们需要对接的是其他exporter返回的特定格式数据,为了避免解析数据然后重新格式化,我们可以直接在http层进行聚合然后返回给prometheus。

为了在http层聚合,我们先看看prometheus拉取接口是怎么处理的。下载prometheus的golang库,prometheus拉取监控数据流程为:

  • 注册prometheus请求地址
代码语言:javascript复制
...
http.NewServeMux().Handle("/metrics", promhttp.Handler())
...
  • 默认请求处理句柄
代码语言:javascript复制
promhttp.Handler() {
  return InstrumentMetricHandler(
    prometheus.DefaultRegisterer, HandlerFor(prometheus.DefaultGatherer, HandlerOpts{}),
  )
}
promhttp.HandlerFor() {
  ...
  //收集数据
  mfs, err := reg.Gather()
  //获取回包编码
  enc := expfmt.NewEncoder(w, contentType)
  //编码所有数据返回prometheus
  for _, mf := range mfs {
    if enc.Encode(mf) {
      return
    }
  }
  ...
}

从上面的流程可以看出,我们只需要重写HandlerFor,将各组件exporter的回包拼接后返回prometheus就可以完成数据汇聚,简化代码如下:

代码语言:javascript复制
promhttp.HandlerFor() {
  ...
  //收集数据
  mfs, err := reg.Gather()
  //获取回包编码
  enc := expfmt.NewEncoder(w, contentType)
  //编码所有数据返回prometheus
  for _, mf := range mfs {
    if enc.Encode(mf) {
      return
    }
  }
  //加上其他exporter数据回包并返回
  var wg sync.WaitGroup
  var rsps [][]byte
  for _, p := range plugins {
    wg.Add(1)
    go func(plugin *Plugin) {
      defer wg.Done()
      body, e := HttpGet(plugin.Url, nil, nil, nil, 0)
      if e == nil && len(body) > 0 {
        body = append(body, 'n')
        rsps = append(rsps, body)
      }
    }(p)
  }
  wg.Wait()
  for _, rsp := range rsps {
    w.Write(rsp)
  }
  ...
}

5、实际效果

图4 各组件监控列表图4 各组件监控列表
图5 Redis组件监控图5 Redis组件监控
图6 MySQL组件监控图6 MySQL组件监控

0 人点赞