> 文章列表 > go-restful接入prometheus笔记

go-restful接入prometheus笔记

go-restful接入prometheus笔记

文章目录

    • 一、前言
    • 二、接入prometheus
      • 1、确认集群pod是否支持prometheus以及上传指标的地址
        • (1)查询集群下的prometheus 上报地址列表
        • (2)根据pod的ip进行搜索
      • 2、下载扩展包
      • 3、上报prometheus的步骤
        • (1)定义指标和注册指标
        • (2)启动http服务和prometheus的handler
        • (3)报指标启动服务,查看指标注册情况
          • 1)代码中查看指标注册情况
          • 2)通过接口查看
    • 三、启动http服务的问题
      • 1、启动两个http服务
      • 2、启动单个http服务
    • 四、关于prometheus的其他问题
      • 1、注册的指标,和http怎么联系起来的呢
      • 2、Register和MustRegister的区别
    • 五、完整上报prometheus指标的demo
      • 1、初始化和定义指标
      • 2、初始化和上报指标

一、前言

      云服务时代,接入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 类型,并注册到 prometheusfor 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走9090端口,handler是promhttp.Handler()// restful
srv := &http.Server{Handler:      s.Container,Addr:         8000,WriteTimeout: 15 * time.Second,ReadTimeout:  15 * time.Second,}// restfulgo func() {if err := srv.ListenAndServe(); err != http.ErrServerClosed {Logger.Fatalf("HTTP server ListenAndServe: %v", err)}}()// register promhttpmetrics := http.NewServeMux()metrics.Handle("/metrics", promhttp.Handler())metricsServer := &http.Server{Addr:    ":9090",Handler: metrics,}go metricsServer.ListenAndServe()Logger.Infof("HTTP metrics started at %s", "9090")

      开启两个http服务一般是因为端口不一致所以才开启的,一般来说还是跟go-restful端口保持一致比较好。

2、启动单个http服务

//ServeMux 可以注册多了 URL 和 handler 的对应关系,并自动把请求转发到对应的 handler 进行处理。//以/ 结尾的 URL 可以匹配它的任何子路径,比如 /images 会匹配 /images/cute-cat.jpgmux := 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 commonimport ("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.CounterVecgauges     map[string]*prometheus.GaugeVechistograms map[string]*prometheus.HistogramVecmu 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 类型,并注册到 prometheusfor name, counter := range m.counters {Logger.Warnf("Register Counter metrics for:%s!", name)prometheus.MustRegister(counter)}//将所有 Gauge 转换为 GaugeVec 类型,并注册到 prometheusfor name, gauge := range m.gauges {Logger.Warnf("Register gauges metrics for:%s!", name)prometheus.MustRegister(gauge)}// 将所有 Histogram 转换为 HistogramVec 类型,并注册到 prometheusfor 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