Go + Gin 项目中应用 Prometheus 监控

508 阅读5分钟

Prometheus 基础概念

核心概念

指标类型

  • Counter(计数器):只能增加的累积指标,用于统计请求数、错误数等
  • Gauge(仪表盘):可以任意增减的指标,用于统计内存使用量、CPU使用率等
  • Histogram(直方图):观察结果采样,统计数据分布,如请求延迟、响应大小等
  • Summary(摘要):类似于Histogram,但提供分位数统计

时间序列

  • 由指标名称和标签组合唯一标识的数据流
  • 每个时间序列包含时间戳和数值

监控架构

应用程序 → Prometheus Server → Grafana/AlertManager
    ↓           ↓                    ↓
  暴露指标   收集&存储指标        可视化&告警

实现监控

下面我们先实现对HTTP请求总数(HttpRequestsTotal)这一指标的完整监控流程。

编写监控逻辑

项目初始化

mkdir prometheus-demo
cd prometheus-demo
go mod init prometheus-demo
go get github.com/gin-gonic/gin
go get github.com/prometheus/client_golang

定义HttpRequestsTotal指标

创建 main.go 文件,首先定义这个核心指标:

package main

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
)

// 定义HTTP请求总数指标
var httpRequestsTotal = promauto.NewCounterVec(
    prometheus.CounterOpts{
        Name: "http_requests_total",
        Help: "Total number of HTTP requests",
    },
    []string{"method", "endpoint", "status"},
)

指标详解:

  • Name: "http_requests_total":指标名称,遵循Prometheus命名规范。Prometheus命名规范建议使用小写字母、下划线分隔单词,且以资源名_操作类型_单位(可选)为结构。
  • Help:指标描述,说明其用途
  • []string{"method", "endpoint", "status"}:标签维度,用于对指标进行分类。
    • method:HTTP方法(GET、POST等)
    • endpoint:请求端点路径
    • status:HTTP状态码

创建监控中间件

main.go文件中添加中间件:

import (
    "strconv"
    "github.com/gin-gonic/gin"
    // ... 其他导入
)

// HTTP请求监控中间件
func prometheusMiddleware() gin.HandlerFunc {
    return func(ctx *gin.Context) {

        // 不处理指标端点路径
        if ctx.Request.URL.Path == "/metrics" {
          // 直接跳过
          ctx.Next()
          return
        }

        // 处理请求
        c.Next()

        // 请求处理完成后记录指标
        status := strconv.Itoa(ctx.Writer.Status())
        
        httpRequestsTotal.WithLabelValues(
            ctx.Request.Method,    // HTTP方法
            ctx.FullPath(),        // 路由路径
            status,              // 状态码
        ).Inc()  // 指标值加1
    }
}

应用监控逻辑

创建简单的API端点

继续在main.go中添加业务逻辑:

import (
    "net/http"
    "github.com/prometheus/client_golang/prometheus/promhttp"
    // ... 其他导入
)

func main() {
    // 创建Gin路由器
    r := gin.Default()

    // 应用Prometheus中间件到所有路由
    r.Use(prometheusMiddleware())

    // 暴露指标端点
    r.GET("/metrics", gin.WrapH(promhttp.Handler()))

    // 业务API端点
    r.GET("/api/health", func(ctx *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"status": "ok"})
    })

    r.GET("/api/error", func(ctx *gin.Context) {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "模拟错误"})
    })

    // 启动服务器
    r.Run(":8080")
}

验证监控效果

2.5.1 启动应用并初步验证

启动应用:

go run main.go

应用启动后,服务器会监听在8080端口。

2.5.2 生成HttpRequestsTotal监控数据

打开新的终端窗口,执行以下测试命令:

# 发送GET请求
curl http://localhost:8080/api/health

# 发送请求到错误端点(产生500状态码)
curl http://localhost:8080/api/error

# 批量发送请求
for i in {1..5}; do
  curl -s http://localhost:8080/api/health
done

查看HttpRequestsTotal指标数据

查看指标端点:

curl http://localhost:8080/metrics | grep http_requests_total

应该看到类似以下输出:

# HELP http_requests_total Total number of HTTP requests
# TYPE http_requests_total counter
http_requests_total{method="GET",endpoint="/api/health",status="200"} 6
http_requests_total{method="GET",endpoint="/api/error",status="500"} 1

这样我们就实现了对HTTP请求总数的监控,可以通过 /metrics 端点查看指标数据。

扩展到其他类型指标

我们将按照以下顺序逐步扩展:

  1. Gauge指标:活跃连接数监控
  2. Histogram指标:请求响应时间监控
  3. Summary指标:请求大小分位数统计

添加Gauge指标监控活跃连接数

Gauge指标特点

  • 可增可减:与Counter不同,Gauge可以任意变化
  • 瞬时值:表示某个时刻的状态快照
  • 适用场景:内存使用量、连接数、队列长度等

定义Gauge指标

main.go文件中添加:

var activeConnections = promauto.NewGauge(
    prometheus.GaugeOpts{
        Name: "active_connections",
        Help: "Number of active connections",
    },
)

在中间件中应用连接数监控逻辑

func prometheusMiddleware() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        // 连接开始时增加计数
        activeConnections.Inc()
        
        // 确保连接结束时减少计数
        defer activeConnections.Dec()

        // 处理请求
        c.Next()

        // ...
    }
}

测试

创建持续的API端点用于测试活跃连接数监控

r.GET("/api/slow", func(c *gin.Context) {
    time.Sleep(30 * time.Second) // 延迟30秒
    c.JSON(http.StatusOK, gin.H{"message": "慢速响应完成"})
})

在终端执行以下命令:

# 启动应用
go run main.go

# 发送并发请求测试连接数变化
for i in {1..120}; do curl -s http://localhost:8080/api/slow > /dev/null & done

# 查看活跃连接数,指标值应该为120
curl -s http://localhost:8080/metrics | grep active_connections

# 等待接口请求完成

# 再次查看活跃连接数,指标值应该为0
curl -s http://localhost:8080/metrics | grep active_connections

添加Histogram指标监控响应时间

Histogram指标特点

  • 分布统计:将数据分到不同的桶(bucket)中
  • 自动计算:提供计数、总和、分位数
  • 适用场景:响应时间、请求大小、处理延迟等

定义Histogram指标

var httpRequestDuration = promauto.NewHistogramVec(
    prometheus.HistogramOpts{
        Name: "http_request_duration_seconds",
        Help: "Duration of HTTP requests in seconds",
        Buckets: prometheus.DefBuckets,  // 默认桶:0.005, 0.01, 0.025, 0.05...
    },
    []string{"method", "endpoint"},
)

在中间件中应用响应时间监控逻辑

func prometheusMiddleware() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        start := time.Now()  // 记录开始时间
        
        // ...

        c.Next()

        // 计算请求处理时间
        duration := time.Since(start).Seconds()
        
        // ...

        // 记录请求时间分布
        httpRequestDuration.WithLabelValues(
            ctx.Request.Method,
            ctx.FullPath(),
        ).Observe(duration)
    }
}

测试

创建随机响应时间的API端点用于测试响应时间监控

r.GET("/api/random-speed", func(c *gin.Context) {
		var millisecond = rand.Intn(2000)
		var duration = time.Duration(millisecond) * time.Millisecond
		time.Sleep(duration)
		c.JSON(http.StatusOK, gin.H{"message": "随机速率响应完成"})
})
# 发送一些请求
for i in {1..10}; do
  curl -s http://localhost:8080/api/random-speed > /dev/null
done

# 查看响应时间统计
curl -s http://localhost:8080/metrics | grep http_request_duration

应该看到类似输出:

http_request_duration_seconds_bucket{endpoint="/api/random-speed",method="GET",le="0.005"} 0
http_request_duration_seconds_bucket{endpoint="/api/random-speed",method="GET",le="0.01"} 0
http_request_duration_seconds_bucket{endpoint="/api/random-speed",method="GET",le="0.025"} 0
http_request_duration_seconds_bucket{endpoint="/api/random-speed",method="GET",le="0.05"} 0
http_request_duration_seconds_bucket{endpoint="/api/random-speed",method="GET",le="0.1"} 0
http_request_duration_seconds_bucket{endpoint="/api/random-speed",method="GET",le="0.25"} 0
http_request_duration_seconds_bucket{endpoint="/api/random-speed",method="GET",le="0.5"} 2
http_request_duration_seconds_bucket{endpoint="/api/random-speed",method="GET",le="1"} 3
http_request_duration_seconds_bucket{endpoint="/api/random-speed",method="GET",le="2.5"} 10
http_request_duration_seconds_bucket{endpoint="/api/random-speed",method="GET",le="5"} 10
http_request_duration_seconds_bucket{endpoint="/api/random-speed",method="GET",le="10"} 10
http_request_duration_seconds_bucket{endpoint="/api/random-speed",method="GET",le="+Inf"} 10

上面的输出表示,有2次请求的响应时间小于等于0.5秒,有3次请求的响应时间小于等于1秒,有10次请求的响应时间小于等于2.5秒,以此类推。

告警(待补完)

启动Prometheus服务

创建Prometheus配置

编写告警规则