94·工程实践进阶

监控与告警

prometheusgrafanametricalert

第94课 - 监控与告警

学习目标

完成本课后,你将能够:

  1. 理解为什么并发应用需要监控指标
  2. 使用Prometheus和Grafana搭建监控系统
  3. 在Go应用中暴露并发相关的指标
  4. 配置基本的告警规则

核心概念

为什么需要监控并发应用?

想象你经营一家餐厅(你的Go服务),监控就像安装在厨房各处的摄像头和传感器。通过监控你可以知道:

  • 厨师(goroutine)是否足够忙碌或空闲
  • 食材传递通道(channel)是否堵塞
  • 顾客请求(HTTP请求)的响应时间
  • 系统资源使用情况

核心监控组件

  1. Prometheus:收集和存储时间序列数据的系统
  2. Grafana:将数据可视化的仪表盘工具
  3. 指标(Metric):代表系统某个可度量属性的数据点
  4. 告警规则:当指标超过阈值时触发的通知机制

常用指标类型

  • Counter:只增不减的计数器(如:请求总数)
  • Gauge:可增可减的测量值(如:当前goroutine数)
  • Histogram:统计分布情况(如:请求耗时分布)

代码示例

完整的可监控Go服务

package main

import (
    "fmt"
    "math/rand"
    "net/http"
    "runtime"
    "sync"
    "time"

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

var (
    // 定义并发相关指标
    activeGoroutines = promauto.NewGauge(prometheus.GaugeOpts{
        Name: "go_concurrent_active_goroutines",
        Help: "当前活跃的goroutine数量",
    })
    
    channelOperations = promauto.NewCounterVec(prometheus.CounterOpts{
        Name: "go_concurrent_channel_operations_total",
        Help: "channel操作总数",
    }, []string{"operation", "channel"})
    
    requestDuration = promauto.NewHistogram(prometheus.HistogramOpts{
        Name:    "http_request_duration_seconds",
        Help:    "HTTP请求持续时间",
        Buckets: prometheus.LinearBuckets(0.1, 0.1, 5), // 0.1s, 0.2s, ... 0.5s
    })
)

// 模拟并发工作器
func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        // 记录channel操作
        channelOperations.WithLabelValues("receive", "jobs").Inc()
        
        // 模拟工作耗时
        duration := time.Duration(rand.Float64() * float64(time.Second))
        time.Sleep(duration)
        
        // 更新活跃goroutine数
        activeGoroutines.Inc()
        
        // 处理任务
        result := j * 2
        results <- result
        channelOperations.WithLabelValues("send", "results").Inc()
        
        // 工作完成,减少goroutine计数
        activeGoroutines.Dec()
    }
}

// HTTP请求处理器
func handleRequest(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    
    // 模拟并发处理
    var wg sync.WaitGroup
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    
    // 启动3个worker
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
    
    // 发送任务
    for j := 1; j <= 5; j++ {
        wg.Add(1)
        go func(job int) {
            defer wg.Done()
            jobs <- job
            channelOperations.WithLabelValues("send", "jobs").Inc()
        }(j)
    }
    
    // 等待所有任务完成
    go func() {
        wg.Wait()
        close(jobs)
    }()
    
    // 收集结果
    var total int
    for i := 1; i <= 5; i++ {
        total += <-results
    }
    
    // 记录请求耗时
    duration := time.Since(start).Seconds()
    requestDuration.Observe(duration)
    
    fmt.Fprintf(w, "处理结果: %d, 耗时: %.2f秒, 当前Goroutine数: %d\n", 
        total, duration, runtime.NumGoroutine())
}

func main() {
    // 启动时记录初始goroutine数
    activeGoroutines.Set(float64(runtime.NumGoroutine()))
    
    // 创建HTTP服务器
    http.HandleFunc("/", handleRequest)
    http.Handle("/metrics", promhttp.Handler())
    
    fmt.Println("监控服务器启动在 :8080")
    fmt.Println("访问 /metrics 查看指标")
    fmt.Println("访问 / 测试并发处理")
    
    // 启动定期更新goroutine数的协程
    go func() {
        for {
            activeGoroutines.Set(float64(runtime.NumGoroutine()))
            time.Sleep(5 * time.Second)
        }
    }()
    
    http.ListenAndServe(":8080", nil)
}

配置文件示例

# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'go-concurrent-app'
    static_configs:
      - targets: ['localhost:8080']

Grafana仪表盘查询示例

# 查看当前活跃goroutine数
go_concurrent_active_goroutines

# 查看channel操作速率(每秒)
rate(go_concurrent_channel_operations_total[5m])

# 查看HTTP请求的95分位耗时
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

实践练习

练习1:基础指标暴露

创建一个简单的Go HTTP服务,暴露以下指标:

  1. app_requests_total - 请求计数器
  2. app_active_connections - 当前连接数
  3. /metrics 端点暴露这些指标

预期输出

$ curl http://localhost:8080/metrics
# HELP app_requests_total Total number of requests
# TYPE app_requests_total counter
app_requests_total 5
# HELP app_active_connections Number of active connections
# TYPE app_active_connections gauge
app_active_connections 2

练习2:并发指标监控

扩展练习1的服务,添加以下并发监控功能:

  1. 监控goroutine数量变化
  2. 记录并发任务队列长度
  3. 统计任务处理成功率

练习3:告警规则配置

为你的服务配置Prometheus告警规则:

  1. 当活跃goroutine数超过100时触发警告
  2. 当请求错误率超过5%时触发严重告警
  3. 当平均响应时间超过2秒时触发性能告警

预期告警规则

groups:
  - name: concurrent_app_alerts
    rules:
      - alert: HighGoroutineCount
        expr: go_concurrent_active_goroutines > 100
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "High goroutine count detected"

常见错误

1. 内存泄漏监控遗漏

// 错误:只监控goroutine数量,不监控内存
runtime.NumGoroutine()

// 正确:同时监控内存使用
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
memStats.Alloc // 分配的内存
memStats.NumGC // GC次数

2. 指标命名不规范

// 错误:使用不清晰的指标名
prometheus.NewGauge(prometheus.GaugeOpts{
    Name: "count",
})

// 正确:遵循命名规范
prometheus.NewGauge(prometheus.GaugeOpts{
    Name: "myapp_goroutine_count",
    Help: "Current number of goroutines",
})

3. 高基数标签问题

// 错误:使用高基数标签导致指标爆炸
channelOperations.WithLabelValues(
    fmt.Sprintf("goroutine_%d", goroutineID), // 每个goroutine一个指标
    channelName,
).Inc()

// 正确:使用有限的标签值
channelOperations.WithLabelValues(
    "send", // 操作类型有限
    "jobs", // channel名称有限
).Inc()

4. 忘记清理过期指标

// 错误:动态创建指标但从不删除
metrics := make(map[string]prometheus.Counter)
for _, user := range users {
    key := "user_" + user.ID + "_requests"
    metrics[key] = prometheus.NewCounter(...)
}

// 正确:使用定时清理或聚合

小结

关键要点回顾

  1. 监控必要性:并发应用需要监控goroutine、channel、锁竞争等关键指标
  2. 三大支柱:Prometheus(采集存储)、Grafana(可视化)、告警系统(通知)
  3. 核心指标:Gauge(瞬时值)、Counter(累计值)、Histogram(分布统计)
  4. 最佳实践
    • 遵循命名规范(namespace_subsystem_name_unit
    • 避免高基数标签
    • 定期审查指标需求
    • 告警规则要具体可操作

下一步学习

  • 学习使用pprof进行深度性能分析
  • 了解分布式追踪(如Jaeger)
  • 探索SLO(服务级别目标)监控

生产环境检查清单

  • 监控所有关键业务指标
  • 设置合理的告警阈值
  • 定期审查和清理无用指标
  • 为监控系统设置高可用
  • 制定告警响应流程

通过本课学习,你已经掌握了Go并发应用监控的基本框架。记住:好的监控系统就像一个优秀的"应用体检医生",能在问题变得严重之前发现并预警。

练习编辑器

go
Loading...

继续学习

完成本课后,建议继续学习下一课「安全最佳实践」 以巩固所学知识。