为Gin设计中间件(四):接入Prometheus提高可观测性
一、背景
在软件系统中,我们希望通过度量和监控系统中各组件的行为,从而了解系统的状态和性能,这种能力叫做“可观测性”。它可以帮助开发人员快速定位和解决系统中的问题,提高系统的稳定性和可靠性。可观测性分为三部分:
- 度量(Metrics):量化系统的性能和行为,例如 CPU 负载、内存使用量等,帮助我们理解和监控系统的整体性能。【本文介绍和实现 度量 的中间件
Prometheus
】 - 追踪(Tracing):追踪系统的请求和响应,帮助开发人员诊断和调试问题,了解系统中的路径和延迟。【实现 追踪 的中间件是
OpenTelemetry
】(下次再更新这部分) - 日志(Logging):记录系统的状态和行为,用于了解系统的历史记录和实时状态变化。
大部分公司内部会使用 Grafana
来做仪表盘,查看数据、配置告警和监控。(下次再更新这部分)
而本文只介绍如何接入 Prometheus
来展示 度量 指标。
二、配置
(1)使用 docker compose
安装Prometheus
prometheus:
image: prom/prometheus:v2.47.2
volumes:
# 将本地的 prometheus 文件映射到容器内的配置文件
- ./prometheus.yaml:/etc/prometheus/prometheus.yml
ports:
# 访问数据的端口
- 9090:9090
(2)Prometheus
的配置文件
global:
external_labels:
monitor: 'prometheus-monitor'
scrape_configs:
- job_name: "webook"
scrape_interval: 5s
scrape_timeout: 3s
static_configs:
- targets: ["host.docker.internal:8081"]
(3)在 main 函数中调�� initPrometheus()
来初始化 Prometheus
func initPrometheus() {
go func() {
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8081", nil)
}()
}
(4)下图是 Prometheus 自带的界面,打开 localhost:9090 就可以访问
三、API 实现
Prometheus
有 4 种指标类型,实践中按需选择:
- Counter:计数器,只能增加,用于统计次数,比如说某件事发生了多少次。
- Gauge:度量,它可以增加也可以减少,比如说当前正在处理的请求数。
- Histogram:柱状图,对观察对象进行采样,然后分到一个个桶里面。
- Summary:采样点按照百分位进行统计,比如说 99 线、999 线等
本文将用 Summary 指标来实现“利用 Gin middleware 统计 HTTP 请求响应时间”,用Gauge 指标来实现“利用 Gin middleware 统计 HTTP 请求活跃数量”
3.1 统计 HTTP 请求响应时间
package prometheus
import (
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"strconv"
"time"
)
type Builder struct {
Name string
Namespace string
Subsystem string
InstanceID string
Help string
}
func NewBuilder(name string, namespace string, subsystem string, instanceID string, help string) *Builder {
return &Builder{Name: name, Namespace: namespace, Subsystem: subsystem, InstanceID: instanceID, Help: help}
}
// BuildResponseTime 统计http请求响应时间
func (b *Builder) BuildResponseTime() gin.HandlerFunc {
// 分 请求方法、命中的路由和响应码
labels := []string{"method", "pattern", "status"}
vector := prometheus.NewSummaryVec(prometheus.SummaryOpts{
// note 这三个都不能有除了下划线以外的字符!!!
Namespace: b.Namespace,
Subsystem: b.Subsystem,
Name: b.Name + "_resp_time",
Help: b.Help,
ConstLabels: map[string]string{
// 部署到了多个实例
"instance_id": b.InstanceID,
},
Objectives: map[float64]float64{
0.5: 0.01,
0.75: 0.01,
0.90: 0.01,
0.99: 0.001,
0.999: 0.0001,
},
}, labels)
prometheus.MustRegister(vector)
return func(ctx *gin.Context) {
// 取当前时间
start := time.Now()
// note 当执行完ctx.Next()后,控制权回到此中间件BuildResponseTime处,执行defer
defer func() {
// 用于上报 prometheus
duration := time.Since(start).Milliseconds()
method := ctx.Request.Method
pattern := ctx.FullPath()
status := ctx.Writer.Status()
vector.WithLabelValues(method, pattern, strconv.Itoa(status)).Observe(float64(duration))
}()
// 执行下一个middleware
ctx.Next()
}
}
利用 wrk
进行压测:1个线程,持续时间为10min,连接数为50个,压测接口为 \users\signup
wrk -t1 -d10m -c50 -s signup.lua http://localhost:8080/users/signup
效果图:
3.2 统计 HTTP 请求活跃数量
// BuildActiveRequest 统计活跃请求数
func (b *Builder) BuildActiveRequest() gin.HandlerFunc {
gauge := prometheus.NewGauge(prometheus.GaugeOpts{
// note 这三个都不能有除了下划线以外的字符!!!
Namespace: b.Namespace,
Subsystem: b.Subsystem,
Name: b.Name + "_active_request",
Help: b.Help,
ConstLabels: map[string]string{
// 部署到了多个实例
"instance_id": b.InstanceID,
},
})
prometheus.MustRegister(gauge)
return func(ctx *gin.Context) {
gauge.Inc()
defer gauge.Dec()
ctx.Next()
}
}
效果图:
在 middleware 中调用 Builder()
:
// note prometheus
prometheus.NewBuilder(
"ecommerce_BG", "webook", "gin_http", "", "统计 gin 的http接口数据",
).BuildResponseTime(),
prometheus.NewBuilder(
"ecommerce_BG", "webook", "gin_http", "", "统计 gin 的http接口数据",
).BuildActiveRequest(),
转载自:https://juejin.cn/post/7386302393611845666