Prometheus 监控:让你的应用可观测

全面介绍如何在 Go 应用中集成 Prometheus 监控,涵盖四种指标类型、HTTP 服务埋点、自定义业务指标、告警规则与 Grafana 可视化

Prometheus 监控:让你的应用可观测

在生产环境中,了解应用的运行状态至关重要。Prometheus 是一个开源的监控系统,配合 Grafana 可以实现强大的指标收集和可视化。

本文将介绍如何在 Go 应用中集成 Prometheus 监控。

什么是 Prometheus?

Prometheus 是一个时间序列数据库和监控系统,具有以下特点:

  • 多维数据模型:通过指标名称和键值对组织数据
  • PromQL:强大的查询语言
  • 拉取模式:主动从目标抓取指标
  • 服务发现:自动发现监控目标
  • 告警:基于规则的告警机制

安装依赖

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

基础指标暴露

package main

import (
    "log"
    "net/http"
    
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
    // 暴露 Prometheus 指标端点
    http.Handle("/metrics", promhttp.Handler())
    
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, World!"))
    })
    
    log.Println("Server starting on :8080")
    http.ListenAndServe(":8080", nil)
}

访问 http://localhost:8080/metrics 可以看到默认的 Go 运行时指标。

自定义指标

Counter(计数器)

只能递增的计数器:

package main

import (
    "net/http"
    
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

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

func init() {
    // 注册指标
    prometheus.MustRegister(requestTotal)
}

func requestMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 包装 ResponseWriter 以捕获状态码
        wrapped := &responseWriter{ResponseWriter: w, statusCode: 200}
        
        next.ServeHTTP(wrapped, r)
        
        // 增加计数
        requestTotal.WithLabelValues(
            r.Method,
            r.URL.Path,
            http.StatusText(wrapped.statusCode),
        ).Inc()
    })
}

type responseWriter struct {
    http.ResponseWriter
    statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.statusCode = code
    rw.ResponseWriter.WriteHeader(code)
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello"))
    })
    
    handler := requestMiddleware(mux)
    
    http.Handle("/metrics", promhttp.Handler())
    http.Handle("/", handler)
    
    http.ListenAndServe(":8080", nil)
}

Gauge(仪表盘)

可增可减的指标:

var (
    // 当前在线用户数
    onlineUsers = prometheus.NewGauge(
        prometheus.GaugeOpts{
            Name: "online_users",
            Help: "Current number of online users",
        },
    )
    
    // 内存使用量
    memoryUsage = prometheus.NewGaugeVec(
        prometheus.GaugeOpts{
            Name: "memory_usage_bytes",
            Help: "Memory usage in bytes",
        },
        []string{"type"},
    )
)

func init() {
    prometheus.MustRegister(onlineUsers, memoryUsage)
}

// 用户登录
func onUserLogin() {
    onlineUsers.Inc()
}

// 用户登出
func onUserLogout() {
    onlineUsers.Dec()
}

// 更新内存使用
func updateMemoryUsage() {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    
    memoryUsage.WithLabelValues("alloc").Set(float64(m.Alloc))
    memoryUsage.WithLabelValues("sys").Set(float64(m.Sys))
    memoryUsage.WithLabelValues("heap").Set(float64(m.HeapAlloc))
}

Histogram(直方图)

统计分布:

var (
    // 请求延迟
    requestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request duration in seconds",
            Buckets: prometheus.DefBuckets,
        },
        []string{"method", "endpoint"},
    )
)

func init() {
    prometheus.MustRegister(requestDuration)
}

func durationMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        
        next.ServeHTTP(w, r)
        
        duration := time.Since(start).Seconds()
        requestDuration.WithLabelValues(r.Method, r.URL.Path).Observe(duration)
    })
}

Summary(摘要)

类似直方图,但计算分位数:

var (
    // 响应大小
    responseSize = prometheus.NewSummaryVec(
        prometheus.SummaryOpts{
            Name:       "http_response_size_bytes",
            Help:       "HTTP response size in bytes",
            Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
        },
        []string{"endpoint"},
    )
)

完整的监控示例

package main

import (
    "log"
    "net/http"
    "runtime"
    "strconv"
    "time"
    
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
    // 计数器
    httpRequestsTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests",
        },
        []string{"method", "endpoint", "status"},
    )
    
    // 仪表盘
    activeConnections = prometheus.NewGauge(
        prometheus.GaugeOpts{
            Name: "active_connections",
            Help: "Number of active connections",
        },
    )
    
    // 直方图
    httpRequestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request duration in seconds",
            Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5},
        },
        []string{"method", "endpoint"},
    )
    
    // 摘要
    httpResponseSize = prometheus.NewSummaryVec(
        prometheus.SummaryOpts{
            Name:       "http_response_size_bytes",
            Help:       "HTTP response size in bytes",
            Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
            MaxAge:     5 * time.Minute,
        },
        []string{"endpoint"},
    )
)

func init() {
    // 注册自定义指标
    prometheus.MustRegister(
        httpRequestsTotal,
        activeConnections,
        httpRequestDuration,
        httpResponseSize,
    )
    
    // 定期更新系统指标
    go func() {
        ticker := time.NewTicker(10 * time.Second)
        defer ticker.Stop()
        
        for range ticker.C {
            var m runtime.MemStats
            runtime.ReadMemStats(&m)
            
            // 可以通过自定义 Gauge 记录这些值
            log.Printf("Goroutines: %d, Alloc: %d MB",
                runtime.NumGoroutine(),
                m.Alloc/1024/1024)
        }
    }()
}

type responseWriter struct {
    http.ResponseWriter
    statusCode int
    size       int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.statusCode = code
    rw.ResponseWriter.WriteHeader(code)
}

func (rw *responseWriter) Write(b []byte) (int, error) {
    size, err := rw.ResponseWriter.Write(b)
    rw.size += size
    return size, err
}

func monitoringMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        
        // 增加活跃连接数
        activeConnections.Inc()
        defer activeConnections.Dec()
        
        // 包装 ResponseWriter
        wrapped := &responseWriter{
            ResponseWriter: w,
            statusCode:     200,
        }
        
        // 执行请求
        next.ServeHTTP(wrapped, r)
        
        // 记录指标
        duration := time.Since(start).Seconds()
        
        httpRequestsTotal.WithLabelValues(
            r.Method,
            r.URL.Path,
            strconv.Itoa(wrapped.statusCode),
        ).Inc()
        
        httpRequestDuration.WithLabelValues(
            r.Method,
            r.URL.Path,
        ).Observe(duration)
        
        httpResponseSize.WithLabelValues(
            r.URL.Path,
        ).Observe(float64(wrapped.size))
    })
}

func main() {
    mux := http.NewServeMux()
    
    // 业务路由
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, World!"))
    })
    
    mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte(`{"users": []}`))
    })
    
    mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(2 * time.Second)
        w.Write([]byte("Slow response"))
    })
    
    // 应用中间件
    handler := monitoringMiddleware(mux)
    
    // Prometheus 指标端点
    http.Handle("/metrics", promhttp.Handler())
    http.Handle("/", handler)
    
    log.Println("Server starting on :8080")
    log.Println("Metrics available at :8080/metrics")
    http.ListenAndServe(":8080", nil)
}

Prometheus 配置

创建 prometheus.yml:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'go-app'
    static_configs:
      - targets: ['localhost:8080']
    metrics_path: '/metrics'
    scrape_interval: 5s

启动 Prometheus:

docker run -d \
  --name prometheus \
  -p 9090:9090 \
  -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \
  prom/prometheus

Grafana 可视化

启动 Grafana

docker run -d \
  --name grafana \
  -p 3000:3000 \
  grafana/grafana

常用查询

# 请求速率(每秒)
rate(http_requests_total[5m])

# 错误率
rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m])

# 平均延迟
rate(http_request_duration_seconds_sum[5m]) / rate(http_request_duration_seconds_count[5m])

# P99 延迟
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

# 活跃连接数
active_connections

# 内存使用
go_memstats_alloc_bytes / 1024 / 1024

告警规则

创建 alert.rules:

groups:
  - name: app-alerts
    rules:
      - alert: HighErrorRate
        expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High error rate detected"
          description: "Error rate is above 5% for more than 5 minutes"
      
      - alert: HighLatency
        expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High latency detected"
          description: "P99 latency is above 1 second"

总结

Prometheus 监控让你的应用具备可观测性:

  1. Counter:统计请求总数、错误总数
  2. Gauge:记录当前状态(连接数、内存使用)
  3. Histogram:统计分布(延迟、响应大小)
  4. Summary:计算分位数

最佳实践:

  • 使用 RED 方法:Rate(速率)、Errors(错误)、Duration(延迟)
  • 标签不要太多,避免高基数问题
  • 设置合理的告警阈值
  • 定期审查和优化指标

记住:没有监控的应用就像在黑暗中航行,Prometheus 是你的灯塔。

进阶概念

四种指标类型详解

Prometheus 提供了四种核心指标类型,每种类型适用于不同的监控场景。

Counter(计数器):Counter 是一个只能递增的累积指标。它适合统计事件发生的总次数,如请求总数、错误总数、处理任务数量等。Counter 的值只增不减(除非进程重启),可以通过 rate() 函数计算每秒的变化率。使用 Counter 时需要注意,如果进程重启,计数会归零,因此需要在查询时处理这种不连续性。

Gauge(仪表盘):Gauge 代表一个可以任意上下波动的瞬时值。它适合监控当前状态,如温度、内存使用量、CPU 负载、队列长度、在线用户数等。Gauge 可以任意增加或减少,直接反映被监控对象的当前状态,不需要使用 rate() 函数。

Histogram(直方图):Histogram 将观察值划分为多个预设的 bucket(桶),统计每个桶中的样本数量,同时记录所有观察值的总和和总数。它特别适合监控请求延迟和响应大小等分布型数据。Histogram 的优势在于可以在服务端聚合,不需要经过网络传输原始数据。Go 的 client_golang 库默认提供了 DefBuckets,适用于大多数延迟监控场景。

Summary(摘要):Summary 与 Histogram 类似,也用于监控分布,但它是在客户端计算分位数(percentile),而不是在服务端。Summary 使用滑动时间窗口和流式算法计算预设的分位点(如 0.5、0.9、0.99),因此在服务端可以直接查询精确的分位数。但 Summary 的缺点是无法在多个实例间聚合分位数。

Exposition 格式

Prometheus 使用文本暴露格式(text-based exposition format)传输指标数据。了解此格式有助于自定义 exporter 和排查问题。

# HELP http_requests_total Total number of HTTP requests
# TYPE http_requests_total counter
http_requests_total{method="GET",endpoint="/api/users",status="200"} 1027
http_requests_total{method="POST",endpoint="/api/users",status="201"} 34

# HELP http_request_duration_seconds HTTP request duration in seconds
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{method="GET",endpoint="/",le="0.005"} 240
http_request_duration_seconds_bucket{method="GET",endpoint="/",le="0.01"} 334
http_request_duration_seconds_bucket{method="GET",endpoint="/",le="+Inf"} 363
http_request_duration_seconds_sum{method="GET",endpoint="/"} 56.34
http_request_duration_seconds_count{method="GET",endpoint="/"} 363

格式规则:

  • # HELP 行提供指标描述
  • # TYPE 行声明指标类型(counter、gauge、histogram、summary、untyped)
  • 指标名只能包含 ASCII 字母、数字、下划线和冒号
  • 标签使用键值对格式,值必须是 UTF-8 字符串
  • 指标值必须是浮点数或整数

PromQL 基础

PromQL 是 Prometheus 的查询语言,灵活且功能丰富。

# 瞬时查询:返回最新值
http_requests_total

# 范围查询:返回时间序列的一段时间
http_requests_total[5m]

# 聚合操作:按标签分组求和
sum by (method) (http_requests_total)

# 数学运算:计算 QPS
rate(http_requests_total[5m])

# 比较运算符:筛选高延迟
http_request_duration_seconds > 0.5

# 向量匹配:计算错误率
rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m])

# 分位数计算:P99 延迟(仅 Histogram)
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

# 偏移查询:与上周同日对比
rate(http_requests_total[5m]) - rate(http_requests_total[5m] offset 1w)

实战案例

HTTP 服务指标埋点完整实现

以下是一个生产级 HTTP 服务的监控埋点实现,涵盖请求量、延迟、错误率和响应大小:

package main

import (
    "context"
    "log"
    "net/http"
    "strconv"
    "time"

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

var (
    httpRequestsTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests",
        },
        []string{"method", "path", "status"},
    )

    httpRequestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request duration in seconds",
            Buckets: []float64{0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10},
        },
        []string{"method", "path"},
    )

    httpRequestSize = prometheus.NewSummaryVec(
        prometheus.SummaryOpts{
            Name:       "http_request_size_bytes",
            Help:       "HTTP request size in bytes",
            Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
        },
        []string{"method", "path"},
    )

    httpResponseSize = prometheus.NewSummaryVec(
        prometheus.SummaryOpts{
            Name:       "http_response_size_bytes",
            Help:       "HTTP response size in bytes",
            Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
        },
        []string{"method", "path"},
    )

    httpActiveRequests = prometheus.NewGaugeVec(
        prometheus.GaugeOpts{
            Name: "http_active_requests",
            Help: "Number of active HTTP requests",
        },
        []string{"method", "path"},
    )
)

func init() {
    prometheus.MustRegister(
        httpRequestsTotal,
        httpRequestDuration,
        httpRequestSize,
        httpResponseSize,
        httpActiveRequests,
    )
}

type metricsResponseWriter struct {
    http.ResponseWriter
    statusCode int
    size       int
}

func (w *metricsResponseWriter) WriteHeader(code int) {
    w.statusCode = code
    w.ResponseWriter.WriteHeader(code)
}

func (w *metricsResponseWriter) Write(b []byte) (int, error) {
    size, err := w.ResponseWriter.Write(b)
    w.size += size
    return size, err
}

func metricsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        path := r.URL.Path

        httpActiveRequests.WithLabelValues(r.Method, path).Inc()
        defer httpActiveRequests.WithLabelValues(r.Method, path).Dec()

        wrapped := &metricsResponseWriter{ResponseWriter: w, statusCode: 200}
        next.ServeHTTP(wrapped, r)

        duration := time.Since(start).Seconds()
        status := strconv.Itoa(wrapped.statusCode)

        httpRequestsTotal.WithLabelValues(r.Method, path, status).Inc()
        httpRequestDuration.WithLabelValues(r.Method, path).Observe(duration)
        httpResponseSize.WithLabelValues(r.Method, path).Observe(float64(wrapped.size))
    })
}

自定义业务指标

除了 HTTP 服务指标,自定义业务指标对运营决策至关重要:

package main

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

var (
    // 订单指标
    ordersCreated = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "orders_created_total",
            Help: "Total number of orders created",
        },
        []string{"status"},
    )

    orderAmountHistogram = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "order_amount_distribution",
            Help:    "Order amount distribution",
            Buckets: prometheus.ExponentialBuckets(10, 2, 10), // 10, 20, 40, 80...
        },
        []string{"category"},
    )

    // 用户指标
    userRegistrations = prometheus.NewCounter(
        prometheus.CounterOpts{
            Name: "user_registrations_total",
            Help: "Total number of user registrations",
        },
    )

    activeUserGauge = prometheus.NewGaugeVec(
        prometheus.GaugeOpts{
            Name: "active_users",
            Help: "Current number of active users by channel",
        },
        []string{"channel"},
    )

    // 支付指标
    paymentProcessed = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "payments_processed_total",
            Help: "Total number of payments processed",
        },
        []string{"status", "method"},
    )

    paymentAmount = prometheus.NewHistogram(
        prometheus.HistogramOpts{
            Name:    "payment_amount_histogram",
            Help:    "Payment amount distribution",
            Buckets: []float64{1, 10, 50, 100, 500, 1000, 5000},
        },
    )
)

func init() {
    prometheus.MustRegister(
        ordersCreated,
        orderAmountHistogram,
        userRegistrations,
        activeUserGauge,
        paymentProcessed,
        paymentAmount,
    )
}

// OrderService 业务逻辑中的指标埋点
type OrderService struct{}

func (s *OrderService) CreateOrder(ctx context.Context, userID int, items []Item) error {
    // ... 创建订单逻辑 ...

    totalAmount := calculateTotal(items)
    category := items[0].Category

    ordersCreated.WithLabelValues("success").Inc()
    orderAmountHistogram.WithLabelValues(category).Observe(totalAmount)

    return nil
}

func (s *OrderService) ProcessPayment(ctx context.Context, orderID int, method string, amount float64) error {
    // ... 支付处理逻辑 ...

    paymentProcessed.WithLabelValues("success", method).Inc()
    paymentAmount.Observe(amount)

    return nil
}

Alertmanager 告警规则

完整的告警配置包含多个维度:

# alert.rules

groups:
  - name: availability-alerts
    rules:
      - alert: ServiceDown
        expr: up{job="go-app"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: Service {{ $labels.instance }} is down
          description: Service has been down for more than 1 minute

  - name: performance-alerts
    rules:
      - alert: HighRequestLatency
        expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 0.5
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: High request latency on {{ $labels.instance }}
          description: 99th percentile latency is above 500ms for 2 minutes

      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m])) by (instance)
          /
          sum(rate(http_requests_total[5m])) by (instance) > 0.01
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: High error rate on {{ $labels.instance }}
          description: Error rate is above 1%

  - name: resource-alerts
    rules:
      - alert: HighMemoryUsage
        expr: go_memstats_alloc_bytes / go_memstats_sys_bytes > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: High memory usage on {{ $labels.instance }}
          description: Memory usage is above 80%

      - alert: TooManyGoroutines
        expr: go_goroutines > 5000
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: Too many goroutines on {{ $labels.instance }}
          description: Goroutine count is above 5000

      - alert: GCRunTimeTooHigh
        expr: rate(go_gc_duration_seconds_sum[5m]) > 0.1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: GC runtime too high on {{ $labels.instance }}
          description: GC is running more than 10% of the time

  - name: business-alerts
    rules:
      - alert: OrderSuccessRateLow
        expr: |
          sum(rate(orders_created_total{status="success"}[10m]))
          /
          sum(rate(orders_created_total[10m])) < 0.95
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: Order success rate is low
          description: Order success rate has dropped below 95%
# alertmanager.yml
route:
  group_by: ['alertname']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 1h
  receiver: 'web.hook'
receivers:
  - name: 'web.hook'
    webhook_configs:
      - url: 'http://localhost:5001/webhook'
        send_resolved: true

性能对比:不同指标收集方式开销

收集方式CPU 开销内存开销精度适用场景
Counter极低极低精确所有计数场景
Gauge极低极低精确状态瞬时量
Histogram低中桶级聚合延迟分布(服务端聚合)
Summary低较高分位点精确分位数(客户端计算)
自定义注册中取决于数量精确业务指标
// 性能基准:带监控 vs 不带监控的中间件
func BenchmarkMonitoringOverhead(b *testing.B) {
    b.Run("WithMetrics", func(b *testing.B) {
        handler := metricsMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            w.Write([]byte("OK"))
        }))
        req := httptest.NewRequest("GET", "/", nil)
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            w := httptest.NewRecorder()
            handler.ServeHTTP(w, req)
        }
    })

    b.Run("WithoutMetrics", func(b *testing.B) {
        handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            w.Write([]byte("OK"))
        })
        req := httptest.NewRequest("GET", "/", nil)
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            w := httptest.NewRecorder()
            handler.ServeHTTP(w, req)
        }
    })
}

FAQ

1. Prometheus 的拉取模式有什么优势?

拉取模式让 Prometheus 控制抓取节奏,易于水平扩展和故障检测。被监控服务只需暴露指标端点,不需要知道监控系统的存在,解耦更彻底。

2. Counter 值为负或下降怎么办?

Counter 理论上只增不减。如果需要递减,应使用 Gauge。如果 Counter 值下降(如进程重启),在使用 rate() 或 increase() 函数时会自动处理重置。

3. 标签数量过多有什么问题?

标签值的高基数(high cardinality)会导致时间序列数量爆炸,占用大量内存和存储。每个新的标签值组合都会创建一个新的时间序列。应限制标签值的数量。

4. Histogram 和 Summary 如何选择?

如果需要聚合多个实例的分位数(如集群总览),选择 Histogram。如果需要精确的客户端分位数且不需要聚合,选择 Summary。大多数 HTTP 监控场景适合 Histogram。

5. Prometheus 支持推送指标吗?

默认不直接支持推送,但可以通过 Pushgateway 实现。Pushgateway 适用于短生命周期任务(如批处理作业),不推荐用于常驻服务。

6. 如何避免内存泄漏?

确保不再使用的指标正确注销,避免创建动态标签值(如用户 ID 作为标签)。定期审查指标基数,及时清理废弃指标。

7. PromQL 查询慢怎么办?

优化 range vector 的时间范围,避免大范围查询。使用 recording rules 预计算常用查询。增加 Prometheus 实例内存或使用 Thanos/Cortex 进行水平扩展。

8. 如何监控 Prometheus 自身?

Prometheus 自动暴露自己的运行时指标,如 prometheus_tsdb_head_series(当前时间序列数)、prometheus_tsdb_compactions_total(压缩次数)等。

最佳实践

  1. 指标命名遵循规范:namespace_subsystem_metric_name_unit,使用小写和下划线
  2. 标签值有界可控:不要用用户 ID、订单 ID 等无界数据作为标签值,使用有枚举范围的值
  3. RED 方法指导指标设计:Rate(速率)、Errors(错误)、Duration(延迟)
  4. 业务指标与基础设施指标同等重视:业务指标的异常往往先于系统指标暴露问题
  5. 告警规则避免过于敏感:适当设置 for 持续时间,避免抖动告警
  6. 分环境暴露指标:测试与生产环境使用不同的 job 名称或 instance 标签
  7. 定期审查指标基数:监控 prometheus_tsdb_head_series 防止意外增长
  8. Recording rules 预聚合:对常用复杂查询使用 recording rules 减少实时计算开销

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「golang」更多文章

  1. 《Go 语言编程实战》目录
  2. 《Go 语言编程实战》18.3 上线、观测与迭代
  3. 《Go 语言编程实战》18.2 故障演练