云服务时代,接入prometheus
是再寻常不过的事情了,只不过之前的接入或多或少都是基于的公司基建,轻松且简单。本次算是从头接了一遍prometheus
,还是有些有意思的点的,记录一下。
http://集群ip:9090/targets
注:Prometheus
的 /targets
端点用于显示 Prometheus
抓取器的目标列表,即正在被监视的数据源。该端点显示了所有已配置的目标及其当前状态,包括最后一次抓取时间、抓取结果、抓取错误等信息。
比如服务pod
节点ip
是:10.xx.x.63
,在targets
列表中搜索该ip
,可以看到prometheus
采集的地址是:
https://10.xx.x.63:10250/metrics
prometheus
的采集端口10250
是默认端口。服务层面不需要关心,只需要在自己的http
服务基础上,暴露一个/metrics
接口即可。接口处理的handler
由prometheus
处理。
如果集群中还没有接入prometheus
的收集器,那么只能去找运维同学了。
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
具体需要什么指标可以先查查各指标的含义,博主这里是使用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)
}
}
http.Handle("/metrics", promhttp.Handler())
promhttp.Handler()
用于创建一个 http
处理程序,该处理程序返回所有已注册的Prometheus
指标。所以在启动http
服务之前,必须要注册所有的指标。
// go-restful 获取定义的路由子路径
subPath := req.SelectedRoutePath()
common.MetricsInstance.ReportCounter(common.CoreRequestTotalCounter, 1, map[string]string{"api": subPath})
// 查看已注册的指标
// 使用 DefaultGatherer 获取已经注册的指标数据
gathered, err := prometheus.DefaultGatherer.Gather()
if err != nil {
panic(err)
}
for _, f := range gathered {
// 输出指标数据
Logger.Infof("查看注册的metrics", f.GetName(), f.GetHelp())
}
curl "http://127.0.0.1:8000/metrics"
// 返回很多默认指标,以及自定义的指标。
关于go中的http服务参考:go 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
指标和健康检查,或者接入其他的第三方组件。
//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
的时候,就会走到prometheus
的handler
,从而读取到当前注册的所有指标。
在 Prometheus
中,Registry
对象是一个全局的管理器,用于注册和存储所有的指标对象。Prometheus
的 HTTP 服务器将使用此 Registry
对象来提供 /metrics
端点,以便其他应用程序和监控系统可以获取指标数据。
Register()
函数将指标对象注册到 Registry
对象中,当调用 promhttp.Handler()
函数时,将会创建一个新的 HTTP
处理程序对象,并使用默认的Registry
和 Gatherer
实例来管理和读取所有的指标数据。
该处理程序对象将在收到 HTTP
请求时调用 Gatherer
对象的 Gather()
方法,从而读取当前所有的指标,并将它们作为响应内容返回给客户端。
prometheus.Register(counter): 将指标对象注册到 registry 中。该方法会返回一个 error,表示注册时可能出现的错误,如果发生错误则会将其忽略,并且不会在控制台输出任何信息。
prometheus.MustRegister(counter): 将指标对象注册到 registry 中。该方法不会返回任何值,如果出现错误则会直接 panic,并将错误信息输出到控制台。通常用于初始化阶段,如果无法正确注册指标对象,则终止程序运行。
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)
}
// 初始化
metrics := common.NewMetrics()
metrics.Register()
//上报指标
subPath := req.SelectedRoutePath()
common.MetricsInstance.ReportCounter(common.CoreRequestTotalCounter, 1, map[string]string{"api": subPath})
end