当前位置: 首页 > 工具软件 > go-restful > 使用案例 >

go-restful接入prometheus笔记

戎亦
2023-12-01

一、前言

      云服务时代,接入prometheus是再寻常不过的事情了,只不过之前的接入或多或少都是基于的公司基建,轻松且简单。本次算是从头接了一遍prometheus,还是有些有意思的点的,记录一下。

二、接入prometheus

1、确认集群pod是否支持prometheus以及上传指标的地址

(1)查询集群下的prometheus 上报地址列表

http://集群ip:9090/targets

注:Prometheus/targets 端点用于显示 Prometheus 抓取器的目标列表,即正在被监视的数据源。该端点显示了所有已配置的目标及其当前状态,包括最后一次抓取时间、抓取结果、抓取错误等信息。

(2)根据pod的ip进行搜索

      比如服务pod节点ip是:10.xx.x.63,在targets列表中搜索该ip,可以看到prometheus采集的地址是:

https://10.xx.x.63:10250/metrics

      prometheus的采集端口10250是默认端口。服务层面不需要关心,只需要在自己的http服务基础上,暴露一个/metrics接口即可。接口处理的handlerprometheus处理。

      如果集群中还没有接入prometheus的收集器,那么只能去找运维同学了。

2、下载扩展包

go get github.com/prometheus/client_golang/prometheus
go get github.com/prometheus/client_golang/prometheus/promauto
go get github.com/prometheus/client_golang/prometheus/promhttp

3、上报prometheus的步骤

(1)定义指标和注册指标

具体需要什么指标可以先查查各指标的含义,博主这里是使用Counter计数器指标和Histogram指标统计请求耗时等。

以下为部分代码:

func NewMetrics() *Metrics {
	metrics := &Metrics{
		counters:   make(map[string]*prometheus.CounterVec),
		gauges:     make(map[string]*prometheus.GaugeVec),
		histograms: make(map[string]*prometheus.HistogramVec),
	}
func (m *Metrics) Register() {

	// 初始化 prometheus, 将所有计数器转换为 CounterVec 类型,并注册到 prometheus

	for name, counter := range m.counters {
		Logger.Warnf("Register Counter metrics for:%s!", name)
		prometheus.MustRegister(counter)
	}
	}

(2)启动http服务和prometheus的handler

http.Handle("/metrics", promhttp.Handler())

      promhttp.Handler() 用于创建一个 http 处理程序,该处理程序返回所有已注册的Prometheus 指标。所以在启动http服务之前,必须要注册所有的指标。

(3)报指标启动服务,查看指标注册情况

// go-restful 获取定义的路由子路径
subPath := req.SelectedRoutePath()
common.MetricsInstance.ReportCounter(common.CoreRequestTotalCounter, 1, map[string]string{"api": subPath})
1)代码中查看指标注册情况
	// 查看已注册的指标
	// 使用 DefaultGatherer 获取已经注册的指标数据
	gathered, err := prometheus.DefaultGatherer.Gather()
	if err != nil {
		panic(err)
	}

	for _, f := range gathered {
		// 输出指标数据
		Logger.Infof("查看注册的metrics", f.GetName(), f.GetHelp())
	}
2)通过接口查看
curl "http://127.0.0.1:8000/metrics"
// 返回很多默认指标,以及自定义的指标。

三、启动http服务的问题

关于go中的http服务参考:go http 服务器编程

1、启动两个http服务

restful走8000端口,handler是go-restful的handler.
prometheus走9000端口,handler是promhttp.Handler()

// restful
srv := &http.Server{
		Handler:      s.Container,
		Addr:         8000,
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}
	// restful
	go func() {
		if err := srv.ListenAndServe(); err != http.ErrServerClosed {
			Logger.Fatalf("HTTP server ListenAndServe: %v", err)
		}
	}()

	// register promhttp
	metrics := http.NewServeMux()
	metrics.Handle("/metrics", promhttp.Handler())
	metricsServer := &http.Server{
		Addr:    ":9000",
		Handler: metrics,
	}
	go metricsServer.ListenAndServe()
	Logger.Infof("HTTP metrics started at %s", "9090")

      开启两个http服务一般是为了不影响业务服务,除了业务的8080端口,再开一个9000端口用来上报prometheus指标和健康检查,或者接入其他的第三方组件。

2、启动单个http服务

//ServeMux 可以注册多了 URL 和 handler 的对应关系,并自动把请求转发到对应的 handler 进行处理。

//以/ 结尾的 URL 可以匹配它的任何子路径,比如 /images 会匹配 /images/cute-cat.jpg

mux := http.NewServeMux()
	mux.Handle("/", s.Container)
	mux.Handle("/metrics", promhttp.Handler())
	srv := &http.Server{
		Handler:      mux,
		Addr:         8000,
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}
	go func() {
		if err := srv.ListenAndServe(); err != http.ErrServerClosed {
			Logger.Fatalf("HTTP server ListenAndServe: %v", err)
		}
	}()

      这样原先的接口访问不受影响。但是访问/metrics的时候,就会走到prometheushandler,从而读取到当前注册的所有指标。

四、关于prometheus的其他问题

1、注册的指标,和http怎么联系起来的呢

      在 Prometheus 中,Registry 对象是一个全局的管理器,用于注册和存储所有的指标对象。Prometheus 的 HTTP 服务器将使用此 Registry 对象来提供 /metrics 端点,以便其他应用程序和监控系统可以获取指标数据。

      Register() 函数将指标对象注册到 Registry 对象中,当调用 promhttp.Handler() 函数时,将会创建一个新的 HTTP 处理程序对象,并使用默认的RegistryGatherer 实例来管理和读取所有的指标数据。

      该处理程序对象将在收到 HTTP 请求时调用 Gatherer 对象的 Gather() 方法,从而读取当前所有的指标,并将它们作为响应内容返回给客户端。

2、Register和MustRegister的区别

prometheus.Register(counter): 将指标对象注册到 registry 中。该方法会返回一个 error,表示注册时可能出现的错误,如果发生错误则会将其忽略,并且不会在控制台输出任何信息。
prometheus.MustRegister(counter): 将指标对象注册到 registry 中。该方法不会返回任何值,如果出现错误则会直接 panic,并将错误信息输出到控制台。通常用于初始化阶段,如果无法正确注册指标对象,则终止程序运行。

五、完整上报prometheus指标的demo

1、初始化和定义指标

package common

import (
	"sync"

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

var metricsOnce sync.Once
var MetricsInstance *Metrics

// 定义指标
const (
	HttpRequestTotalCounter        = "http_requests_total"
	CoreRequestTotalCounter        = "core_requests_total"
	CoreRequestSuccessTotalCounter = "core_requests_success_total"
	CoreRequestCostHistogram       = "core_request_cost"
)

type Metrics struct {
	counters   map[string]*prometheus.CounterVec
	gauges     map[string]*prometheus.GaugeVec
	histograms map[string]*prometheus.HistogramVec

	mu sync.RWMutex
}

// prometheus的指标必须在http服务启动之前定义好
func NewMetrics() *Metrics {
	metrics := &Metrics{
		counters:   make(map[string]*prometheus.CounterVec),
		gauges:     make(map[string]*prometheus.GaugeVec),
		histograms: make(map[string]*prometheus.HistogramVec),
	}

	// 创建并注册 Counter 指标
	// 请求总数
	metrics.counters[HttpRequestTotalCounter] = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: HttpRequestTotalCounter,
		Help: "The total number of HTTP requests",
	}, []string{"api"})

	// 核心功能请求数
	metrics.counters[CoreRequestTotalCounter] = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: CoreRequestTotalCounter,
		Help: "The total number of core function requests",
	}, []string{"api"})

	// 核心功能请求成功数
	metrics.counters[CoreRequestSuccessTotalCounter] = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: CoreRequestSuccessTotalCounter,
		Help: "The total number of core function success requests",
	}, []string{"api"})

	// histogram指标,核心功能请求耗时。 可以通过api标签,区分接口名称
	metrics.histograms[CoreRequestCostHistogram] = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Name:    CoreRequestCostHistogram,
		Help:    "The total number of core apis request cost",
		Buckets: []float64{100, 200, 300, 400, 500, 600, 700, 800, 900, 1000},
	}, []string{"api"})

	metricsOnce.Do(func() {
		MetricsInstance = metrics
	})

	return MetricsInstance
}

func (m *Metrics) Register() {

	// 初始化 prometheus, 将所有计数器转换为 CounterVec 类型,并注册到 prometheus

	for name, counter := range m.counters {
		Logger.Warnf("Register Counter metrics for:%s!", name)
		prometheus.MustRegister(counter)
	}

	//将所有 Gauge 转换为 GaugeVec 类型,并注册到 prometheus
	for name, gauge := range m.gauges {
		Logger.Warnf("Register gauges metrics for:%s!", name)
		prometheus.MustRegister(gauge)
	}

	// 将所有 Histogram 转换为 HistogramVec 类型,并注册到 prometheus
	for name, histogram := range m.histograms {
		Logger.Warnf("Register histograms metrics for:%s!", name)
		prometheus.MustRegister(histogram)
	}

	// 查看已注册的指标
	// 使用 DefaultGatherer 获取已经注册的指标数据
	gathered, err := prometheus.DefaultGatherer.Gather()
	if err != nil {
		panic(err)
	}

	for _, f := range gathered {
		// 输出指标数据
		Logger.Infof("查看注册的metrics", f.GetName(), f.GetHelp())
	}
}

// ReportCounter with labels,example:
func (m *Metrics) ReportCounter(name string, value float64, labels map[string]string) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, ok := m.counters[name]; !ok {
		Logger.Warnf("Counter metrics for:%s not exist!", name)
		return
	}
	Logger.Infof("Counter Add for name:%s", name)
	m.counters[name].With(labels).Add(value)
}

func (m *Metrics) ReportGauge(name string, value float64, labels map[string]string) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, ok := m.gauges[name]; !ok {
		Logger.Infof("Gauge metrics for:%s not exist!", name)
		return
	}
	m.gauges[name].With(labels).Set(value)
}

func (m *Metrics) ReportHistogram(name string, value float64, labels map[string]string) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, ok := m.histograms[name]; !ok {
		Logger.Infof("Histogram metrics for:%s not exist!", name)
	}
	m.histograms[name].With(labels).Observe(value)
}

2、初始化和上报指标

// 初始化
metrics := common.NewMetrics()
metrics.Register()


//上报指标
subPath := req.SelectedRoutePath()
	common.MetricsInstance.ReportCounter(common.CoreRequestTotalCounter, 1, map[string]string{"api": subPath})

end

 类似资料: