通过Exporter收集一切指标

本文转载自微信公众号「运维开发故事」,作者没有文案的夏老师。转载本文请联系运维开发故事公众号。

Exporter介绍

Exporter 是一个采集监控数据并通过 Prometheus 监控规范对外提供数据的组件,它负责从目标系统(Your 服务)搜集数据,并将其转化为 Prometheus 支持的格式。Prometheus 会周期性地调用 Exporter 提供的 metrics 数据接口来获取数据。那么使用 Exporter 的好处是什么?举例来说,如果要监控 Mysql/Redis 等数据库,我们必须要调用它们的接口来获取信息(前提要有),这样每家都有一套接口,这样非常不通用。所以 Prometheus 做法是每个软件做一个 Exporter,Prometheus 的 Http 读取 Exporter 的信息(将监控指标进行统一的格式化并暴露出来)。简单类比,Exporter 就是个翻译,把各种语言翻译成一种统一的语言。

对于Exporter而言,它的功能主要就是将数据周期性地从监控对象中取出来进行加工,然后将数据规范化后通过端点暴露给Prometheus,所以主要包含如下3个功能。

  • 封装功能模块获取监控系统内部的统计信息。
  • 将返回数据进行规范化映射,使其成为符合Prometheus要求的格式化数据。
  • Collect模块负责存储规范化后的数据,最后当Prometheus定时从Exporter提取数据时,Exporter就将Collector收集的数据通过HTTP的形式在/metrics端点进行暴露。

介绍Primetheus client

本文将介绍Primetheus client的使用,基于golang语言,golang client 是当pro收集所监控的系统的数据时,用于响应pro的请求,按照一定的格式给pro返回数据,说白了就是一个http server, 源码参见github,相关的文档参见GoDoc,读者可以直接阅读文档进行开发,本文只是帮助理解。以下是简化流程图:

四种数据类型

prometheus将所有数据保存为timeseries data,用metric name和label区分,label是在metric name上的更细维度的划分,其中的每一个实例是由一个float64和timestamp组成,只不过timestamp是隐式加上去的,有时候不会显示出来。如下面所示(数据来源于prometheus暴露的监控数据,访问http://localhost:9090/metrics 可得),其中go_gc_duration_seconds是metrics name,quantile="0.5"是key-value pair的label,而后面的值是float64 value。

 
 
 
  1. # HELP go_gc_duration_seconds A summary of the GC invocation durations. 
  2. # TYPE go_gc_duration_seconds summary 
  3. go_gc_duration_seconds{quantile="0.5"} 0.000107458 
  4. go_gc_duration_seconds{quantile="0.75"} 0.000200112 
  5. go_gc_duration_seconds{quantile="1"} 0.000299278 
  6. go_gc_duration_seconds_sum 0.002341738 
  7. go_gc_duration_seconds_count 18 
  8. # HELP go_goroutines Number of goroutines that currently exist. 
  9. # TYPE go_goroutines gauge 
  10. go_goroutines 107 

这些信息有一个共同点,就是采用了不同于JSON或者Protocol Buffers的数据组织形式——文本形式。在文本形式中,每个指标都占用一行,#HELP代表指标的注释信息,#TYPE用于定义样本的类型注释信息,紧随其后的语句就是具体的监控指标(即样本)。#HELP的内容格式如下所示,需要填入指标名称及相应的说明信息。

 
 
 
  1. HELP   

#TYPE的内容格式如下所示,需要填入指标名称和指标类型(如果没有明确的指标类型,需要返回untyped)。

 
 
 
  1. TYPE   

监控样本部分需要满足如下格式规范。

 
 
 
  1. metric_name [ "{" label_name "=" " label_value " { "," label_name "=" " label_value " } [ "," ] "}" ] value [ timestamp ] 

其中,metric_name和label_name必须遵循PromQL的格式规范。value是一个f loat格式的数据,timestamp的类型为int64(从1970-01-01 00:00:00开始至今的总毫秒数),可设置其默认为当前时间。具有相同metric_name的样本必须按照一个组的形式排列,并且每一行必须是唯一的指标名称和标签键值对组合。Prometheus为了方便client library的使用提供了四种数据类型:

Counter:Counter是一个累加的数据类型。一个Counter类型的指标只会随着时间逐渐递增(当系统重启的时候,Counter指标会被重置为0)。记录系统完成的总任务数量、系统从最近一次启动到目前为止发生的总错误数等场景都适合使用Counter类型的指标。

  • Gauge:Gauge指标主要用于记录一个瞬时值,这个指标可以增加也可以减少,比如CPU的使用情况、内存使用量以及硬盘当前的空间容量等。
  • Histogram:Histogram表示柱状图,主要用于统计一些数据分布的情况,可以计算在一定范围内的数据分布情况,同时还提供了指标值的总和。在大多数情况下,用户会使用某些指标的平均值作为参考,例如,使用系统的平均响应时间来衡量系统的响应能力。这种方式有个明显的问题——如果大多数请求的响应时间都维持在100ms内,而个别请求的响应时间需要1s甚至更久,那么响应时间的平均值体现不出响应时间中的尖刺,这就是所谓的“长尾问题”。为了更加真实地反映系统响应能力,常用的方式是按照请求延迟的范围进行分组,例如在上述示例中,可以分别统计响应时间在[0,100ms]、[100,1s]和[1s,∞]这3个区间的请求数,通过查看这3个分区中请求量的分布,就可以比较客观地分析出系统的响应能力。
  • Summary:Summary与Histogram类似,也会统计指标的总数(以_count作为后缀)以及sum值(以_sum作为后缀)。两者的主要区别在于,Histogram指标直接记录了在不同区间内样本的个数,而Summary类型则由客户端计算对应的分位数。例如下面展示了一个Summary类型的指标,其中quantile=”0.5”表示中位数,quantile=”0.9”表示九分位数。

广义上讲,所有可以向Prometheus提供监控样本数据的程序都可以被称为一个Exporter,Exporter的一个实例被称为target,Prometheus会通过轮询的形式定期从这些target中获取样本数据。

自己动手编写一个Exporter

一般来说,绝大多数Exporter都是基于Go语言编写的,一小部分是基于Python语言编写的,还有很小一部分是使用Java语言编写的。比如官方提供的Consul Metrics自定义采集器Exporter,如果是在Go语言的运行环境下,需要按照如下所示代码运行这个Exporter。

 
 
 
  1. package main 
  2.   
  3. import ( 
  4.     "log" 
  5.     "net/http" 
  6.   
  7.     "github.com/prometheus/client_golang/prometheus" 
  8.     "github.com/prometheus/client_golang/prometheus/promhttp" 
  9.   
  10. var ( 
  11.     cpuTemp = prometheus.NewGauge(prometheus.GaugeOpts{ 
  12.         NameSpace: "our_idc", 
  13.         Subsystem: "k8s" 
  14.         Name: "cpu_temperature_celsius", 
  15.         Help: "Current temperature of the CPU.", 
  16.     }) 
  17.     hdFailures = prometheus.NewCounterVec( 
  18.         prometheus.CounterOpts{ 
  19.             NameSpace: "our_idc", 
  20.             Subsystem: "k8s" 
  21.             Name: "hd_errors_total", 
  22.             Help: "Number of hard-disk errors.", 
  23.         }, 
  24.         []string{"device"}, 
  25.     ) 
  26.   
  27. func init() { 
  28.     // Metrics have to be registered to be exposed: 
  29.     prometheus.MustRegister(cpuTemp) 
  30.     prometheus.MustRegister(hdFailures) 
  31.   
  32. func main() { 
  33.     cpuTemp.Set(65.3) 
  34.     hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc() 
  35.   
  36.     // The Handler function provides a default handler to expose metrics 
  37.     // via an HTTP server. "/metrics" is the usual endpoint for that. 
  38.     http.Handle("/metrics", promhttp.Handler()) 
  39.     log.Fatal(http.ListenAndServe(":8888", nil)) 

其中创建了一个gauge和CounterVec对象,并分别指定了metric name和help信息,其中CounterVec是用来管理相同metric下不同label的一组Counter,同理存在GaugeVec,可以看到上面代码中声明了一个lable的key为“device”,使用的时候也需要指定一个lable: hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc()。变量定义后进行注册,最后再开启一个http服务的8888端口就完成了整个程序,Prometheus采集数据是通过定期请求该服务http端口来实现的。启动程序之后可以在web浏览器里输入http://localhost:8888/metrics 就可以得到client暴露的数据,其中有片段显示为:

 
 
 
  1. # HELP our_idc_k8s_cpu_temperature_celsius Current temperature of the CPU. 
  2. # TYPE our_idc_k8s_cpu_temperature_celsius gauge 
  3. our_idc_k8s_cpu_temperature_celsius 65.3 
  4. # HELP our_idc_k8s_hd_errors_total Number of hard-disk errors. 
  5. # TYPE our_idc_k8s_hd_errors_total counter 
  6. our_idc_k8s_hd_errors_total{device="/dev/sda"} 1 

上图就是示例程序所暴露出来的数据,并且可以看到counterVec是有label的,而单纯的gauage对象却不用lable标识,这就是基本数据类型和对应Vec版本的差别。此时再查看http://localhost:9090/graph 就会发现服务状态已经变为UP了。上面的例子只是一个简单的demo,因为在prometheus.yml配置文件中我们指定采集服务器信息的时间间隔为60s,每隔60s Prometheus会通过http请求一次自己暴露的数据,而在代码中我们只设置了一次gauge变量cupTemp的值,如果在60s的采样间隔里将该值设置多次,前面的值就会被覆盖,只有Prometheus采集数据那一刻的值能被看到,并且如果不再改变这个值,Prometheus就始终能看到这个恒定的变量,除非用户显式通过Delete函数删除这个变量。使用Counter,Gauage等这些结构比较简单,但是如果不再使用这些变量需要我们手动删,我们可以调用resetfunction来清除之前的metrics。

自定义Collector

直接使用Collector,go client Colletor只会在每次响应Prometheus请求的时候才收集数据。需要每次显式传递变量的值,否则就不会再维持该变量,在Prometheus也将看不到这个变量。Collector是一个接口,所有收集metrics数据的对象都需要实现这个接口,Counter和Gauage等不例外。它内部提供了两个函数,Collector用于收集用户数据,将收集好的数据传递给传入参数Channel就可;Descirbe函数用于描述这个Collector。

当收集系统收集的数据太多时时,就可以自定义Collector收集的方式,优化流程,并且在某些情况下如果已经有了一个成熟的metrics,就不需要使用Counter,Gauage等这些数据结构,直接在Collector内部实现一个代理的功能即可。

基本上所有的export都是通过自定义Collector实现。一个简单的Collector的实现export的代码如下:

 
 
 
  1. package main 
  2.  
  3. import ( 
  4.  "github.com/prometheus/client_golang/prometheus" 
  5.  "github.com/prometheus/client_golang/prometheus/promhttp" 
  6.  "net/http" 
  7.  "sync" 
  8.  
  9. type ClusterManager struct { 
  10.  sync.Mutex 
  11.  Zone              string 
  12.  metricMapCounters map[string]string 
  13.  metricMapGauges   map[string]string 
  14.  
  15. //Simulate prepare the data 
  16. func (c *ClusterManager) ReallyExpensiveAssessmentOfTheSystemState() ( 
  17.  metrics map[string]float64, 
  18. ) { 
  19.  metrics = map[string]float64{ 
  20.   "oom_crashes_total": 42.00, 
  21.   "ram_usage":         6.023e23, 
  22.  } 
  23.  return 
  24. //通过NewClusterManager方法创建结构体及对应的指标信息,代码如下所示。 
  25. // NewClusterManager creates the two Descs OOMCountDesc and RAMUsageDesc. Note 
  26. // that the zone is set as a ConstLabel. (It's different in each instance of the 
  27. // ClusterManager, but constant over the lifetime of an instance.) Then there is 
  28. // a variable label "host", since we want to partition the collected metrics by 
  29. // host. Since all Descs created in this way are consistent across instances, 
  30. // with a guaranteed distinction by the "zone" label, we can register different 
  31. // ClusterManager instances with the same registry. 
  32. func NewClusterManager(zone string) *ClusterManager { 
  33.  return &ClusterManager{ 
  34.   Zone: zone, 
  35.   metricMapGauges: map[string]string{ 
  36.    "ram_usage": "ram_usage_bytes", 
  37.   }, 
  38.   metricMapCounters: map[string]string{ 
  39.    "oom_crashes": "oom_crashes_total", 
  40.   }, 
  41.  } 
  42. //首先,采集器必须实现prometheus.Collector接口,也必须实现Describe和Collect方法。实现接口的代码如下所示。 
  43. // Describe simply sends the two Descs in the struct to the channel. 
  44. // Prometheus的注册器调用Collect来抓取参数  
  45. // 将收集的数据传递到Channel中并返回  
  46. // 收集的指标信息来自Describe,可以并发地执行抓取工作,但是必须要保证线程的安全  
  47.  
  48. func (c *ClusterManager) Describe(ch chan<- *prometheus.Desc) { 
  49.  // prometheus.NewDesc(prometheus.BuildFQName(namespace, "", metricName), docString, labels, nil) 
  50.  for _, v := range c.metricMapGauges { 
  51.   ch <- prometheus.NewDesc(prometheus.BuildFQName(c.Zone, "", v), v, nil, nil) 
  52.  } 
  53.  
  54.  for _, v := range c.metricMapCounters { 
  55.   ch <- prometheus.NewDesc(prometheus.BuildFQName(c.Zone, "", v), v, nil, nil) 
  56.  } 
  57.  
  58. //Collect方法是核心,它会抓取你需要的所有数据,根据需求对其进行分析,然后将指标发送回客户端库。 
  59. // 用于传递所有可能指标的定义描述符  
  60. // 可以在程序运行期间添加新的描述,收集新的指标信息  
  61. // 重复的描述符将被忽略。两个不同的Collector不要设置相同的描述符  
  62. func (c *ClusterManager) Collect(ch chan<- prometheus.Metric) { 
  63.  c.Lock() 
  64.  defer c.Unlock() 
  65.  m := c.ReallyExpensiveAssessmentOfTheSystemState() 
  66.  for k, v := range m { 
  67.   t := prometheus.GaugeValue 
  68.   if c.metricMapCounters[k] != "" { 
  69.    t = prometheus.CounterValue 
  70.   } 
  71.   c.registerConstMetric(ch, k, v, t) 
  72.  } 
  73. // 用于传递所有可能指标的定义描述符给指标 
  74. func (c *ClusterManager) registerConstMetric(ch chan<- prometheus.Metric, metric string, val float64, valType prometheus.ValueType, labelValues ...string) { 
  75.  descr := prometheus.NewDesc(prometheus.BuildFQName(c.Zone, "", metric), metric, nil, nil) 
  76.  if m, err := prometheus.NewConstMetric(descr, valType, val, labelValues...); err == nil { 
  77.   ch <- m 
  78.  } 
  79.  
  80. func main() { 
  81.  workerCA := NewClusterManager("xiaodian") 
  82.  reg := prometheus.NewPedanticRegistry() 
  83.  reg.MustRegister(workerCA) 
  84.     //当promhttp.Handler()被执行时,所有metric被序列化输出。题外话,其实输出的格式既可以是plain text,也可以是protocol Buffers。 
  85.  http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{})) 
  86.  http.ListenAndServe(":8888", nil) 

此时就可以去http://localhost:8888/metrics 看到传递过去的数据了。

高质量Exporter的编写原则与方法

主要方法

参考链接:https://prometheus.io/docs/instrumenting/writing_exporters/。1.在访问Exporter的主页(即http://yourExporter/这样的根路径)时,它会返回一个简单的页面,这就是Exporter的落地页(Landing Page)。落地页中可以放文档和帮助信息,包括监控指标项的说明。落地页上还包括最近执行的检查列表、列表的状态以及调试信息,这对故障排查非常有帮助。

2.一台服务器或者容器上可能会有许多Exporter和Prometheus组件,它们都有自己的端口号。因此,在写Exporter和发布Exporter之前,需要检查新添加的端口是否已经被使用[1],建议使用默认端口分配范围之外的端口。

3.我们应该根据业务类型设计好指标的#HELP#TYPE的格式。这些指标往往是可配置的,包括默认开启的指标和默认关闭的指标。这是因为大部分指标并不会真正被用到,设计过多的指标不仅会消耗不必要的资源,还会影响整体的性能。

其他方法

对于如何写高质量Exporter,除了合理分配端口号、设计落地页、梳理指标这3个方面外,还有一些其他的原则。

  • 记录Exporter本身的运行状态指标。
  • 可配置化进行功能的启用和关闭。
  • 推荐使用YAML作为配置格式。
  • 遵循度量标准命名的最佳实践[2],特别是_count、_sum、_total、_bucket和info等问题。
  • 为度量提供正确的单位。
  • 标签的唯一性、可读性及必要的冗余信息设计。
  • 通过Docker等方式一键配置Exporter。
  • 尽量使用Collectors方式收集指标,如Go语言中的MustNewConstMetric。
  • 提供scrapes刮擦失败的错误设计,这有助于性能调试。
  • 尽量不要重复提供已有的指标,如Node Exporter已经提供的CPU、磁盘等信息。
  • 向Prometheus公开原始的度量数据,不建议自行计算,Exporter的核心是采集原始指标。

Redis Exporter源码解析

在本章中,读者可以发现开源领域有着不计其数的Exporter,阿里巴巴开源的Exporter就有RocketMQ Exporter、Sentinel Exporter、Sentry Exporter、Alibaba Cloud Exporter等多种。编写Exporter和编写Spring Boot Starter一样,可以多参考其他优秀的开源软件的代码。本节就来简单分析一下运维工作中用到的Redis Exporter源码。在使用Redis Exporter时,可以通过redis_exporter--help命令查看完整的参数列表。默认情况下,它在端口9192上运行,并在路径/metrics上暴露指标。可以通过--web.listen-addres和--web.telemetry-path命令来设置端口和路径,代码如下所示。

 
 
 
  1. redis_exporter -web.listen-address=":8888" -web.telemetry-path="/node_metrics" 

上述代码将修改redis Exporter绑定到端口8888并在路径/node_metrics上暴露指标。这个逻辑是在源码redis_exporter.go中实现的.Redis Exporter[3]主要通过Redis原生的命令获取Redis所有的信息,它支持2.x、3.x、4.x、5.x和6.x版本。在源码中,可以看到多处使用了doRedisCmd方法发送命令以获取性能指标,代码如下所示。主要是通过原生的INFO命令获取所有性能信息。该命令的返回结果详情参考[4]。

 
 
 
  1. infoAll, err := redis.String(doRedisCmd(c, "INFO", "ALL")) 

生成的infoAll信息通过func (e *Exporter) extractInfoMetrics(ch chan<- prometheus.Metric, info string, dbCount int)继续处理。它的主要目的是遍历查询到的结果,根据指标生成一个hash值。源代码如下所示:

 
 
 
  1. func (e *Exporter) extractInfoMetrics(ch chan<- prometheus.Metric, info string, dbCount int) { 
  2.  keyValues := map[string]string{} 
  3.  handledDBs := map[string]bool{} 
  4.  
  5.  fieldClass := "" 
  6.     //以换行符进行分割 
  7.  lines := strings.Split(info, "\n") 
  8.  masterHost := "" 
  9.  masterPort := "" 
  10.     //遍历查询到的结果,根据指标生成一个hash值 
  11.  for _, line := range lines { 
  12.   line = strings.TrimSpace(line) 
  13.   log.Debugf("info: %s", line) 
  14.         //去除带#的注释文件 
  15.   if len(line) > 0 && strings.HasPrefix(line, "# ") { 
  16.    fieldClass = line[2:] 
  17.    log.Debugf("set fieldClass: %s", fieldClass) 
  18.    continue 
  19.   } 
  20.         //去除不带:的或者字符小于2的 
  21.   if (len(line) < 2) || (!strings.Contains(line, ":")) { 
  22.    continue 
  23.   } 
  24.         //以冒号进行分割 
  25.   split := strings.SplitN(line, ":", 2) 
  26.   fieldKey := split[0] 
  27.   fieldValue := split[1] 
  28.         //将指标名称与值存到hash中 
  29.   keyValues[fieldKey] = fieldValue 
  30.  
  31.   if fieldKey == "master_host" { 
  32.    masterHost = fieldValue 
  33.   } 
  34.  
  35.   if fieldKey == "master_port" { 
  36.    masterPort = fieldValue 
  37.   } 
  38.         //按照集群和副本和哨兵模式进行处理 
  39.   switch fieldClass { 
  40.  
  41.   case "Replication": 
  42.    if ok := e.handleMetricsReplication(ch, masterHost, masterPort, fieldKey, fieldValue); ok { 
  43.     continue 
  44.    } 
  45.  
  46.   case "Server": 
  47.    e.handleMetricsServer(ch, fieldKey, fieldValue) 
  48.  
  49.   case "Commandstats": 
  50.    e.handleMetricsCommandStats(ch, fieldKey, fieldValue) 
  51.    continue 
  52.  
  53.   case "Keyspace": 
  54.    if keysTotal, keysEx, avgTTL, ok := parseDBKeyspaceString(fieldKey, fieldValue); ok { 
  55.     dbName := fieldKey 
  56.  
  57.     e.registerConstMetricGauge(ch, "db_keys", keysTotal, dbName) 
  58.     e.registerConstMetricGauge(ch, "db_keys_expiring", keysEx, dbName) 
  59.  
  60.     if avgTTL > -1 { 
  61.      e.registerConstMetricGauge(ch, "db_avg_ttl_seconds", avgTTL, dbName) 
  62.     } 
  63.     handledDBs[dbName] = true 
  64.     continue 
  65.    } 
  66.  
  67.   case "Sentinel": 
  68.    e.handleMetricsSentinel(ch, fieldKey, fieldValue) 
  69.   } 
  70.  
  71.   if !e.includeMetric(fieldKey) { 
  72.    continue 
  73.   } 
  74.         //将收集到信息进行按照一定规则进行处理 
  75.   e.parseAndRegisterConstMetric(ch, fieldKey, fieldValue) 
  76.  } 
  77.  
  78.  for dbIndex := 0; dbIndex < dbCount; dbIndex++ { 
  79.   dbName := "db" + strconv.Itoa(dbIndex) 
  80.   if _, exists := handledDBs[dbName]; !exists { 
  81.    e.registerConstMetricGauge(ch, "db_keys", 0, dbName) 
  82.    e.registerConstMetricGauge(ch, "db_keys_expiring", 0, dbName) 
  83.   } 
  84.  } 
  85.        
  86.  e.registerConstMetricGauge(ch, "instance_info", 1, 
  87.   keyValues["role"], 
  88.   keyValues["redis_version"], 
  89.   keyValues["redis_build_id"], 
  90.   keyValues["redis_mode"], 
  91.   keyValues["os"], 
  92.   keyValues["maxmemory_policy"], 
  93.   keyValues["tcp_port"], keyValues["run_id"], keyValues["process_id"], 
  94.  ) 
  95.  
  96.  if keyValues["role"] == "slave" { 
  97.   e.registerConstMetricGauge(ch, "slave_info", 1, 
  98.    keyValues["master_host"], 
  99.    keyValues["master_port"], 
  100.    keyValues["slave_read_only"]) 
  101.  } 

然后通过e.parseAndRegisterConstMetric(ch, fieldKey, fieldValue)方法,将收集到hash中的信息,按照一定的规则生成prometheus.Metric。核心代码如下:

 
 
 
  1. func (e *Exporter) registerConstMetric(ch chan<- prometheus.Metric, metric string, val float64, valType prometheus.ValueType, labelValues ...string) { 
  2.  descr := e.metricDescriptions[metric] 
  3.  if descr == nil { 
  4.   descr = newMetricDescr(e.options.Namespace, metric, metric+" metric", labelValues) 
  5.  } 
  6.  
  7.  if m, err := prometheus.NewConstMetric(descr, valType, val, labelValues...); err == nil { 
  8.   ch <- m 
  9.  } 

最后*Exporter.Collect的方法调用registerConstMetric方法,就完成了redis的info指标的收集。其他指标的收集原来也是相同的,有兴趣的读者可以自行阅读。

总结

本文介绍了Exporter的概念。Exporter的来源主要有两个:一个是社区提供的,一个是用户自定义的。在实际生产中,官方提供的Exporter主要涵盖数据库、硬件、问题跟踪及持续集成、消息系统、存储、HTTP、API、日志、其他监控系统等,这些已有的Exporter可以满足绝大多数开发人员及运维人员的需求。对于系统、软件没有Exporter的情况,本章也从数据规范、数据采集方式、代码案例撰写等方面带领读者体验了Exporter的设计与实践,一步步指导读者打造定制化Exporter。为了帮助读者形成良好的代码风格并能够真正编写高质量Exporter,本章还给出了编写高质量Exporter的建议,并结合 Redis Exporter的原理进行了实战解析。通过对本章的学习,读者可以掌握使用和定制Exporter的能力。

[1] Exporter端口列表:https://github.com/prometheus/prometheus/wiki/Default-port-allocations。

[2] 标准命名最佳实践:https://prometheus.io/docs/practices/naming。

[3] Redis Exporter地址:https://github.com/oliver006/redis_Exporter。

[4] Redis INFO命令地址:https://redis.io/commands/info。

本文标题:通过Exporter收集一切指标
分享地址:http://www.shufengxianlan.com/qtweb/news15/355065.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联