第94课 - 监控与告警
学习目标
完成本课后,你将能够:
- 理解为什么并发应用需要监控指标
- 使用Prometheus和Grafana搭建监控系统
- 在Go应用中暴露并发相关的指标
- 配置基本的告警规则
核心概念
为什么需要监控并发应用?
想象你经营一家餐厅(你的Go服务),监控就像安装在厨房各处的摄像头和传感器。通过监控你可以知道:
- 厨师(goroutine)是否足够忙碌或空闲
- 食材传递通道(channel)是否堵塞
- 顾客请求(HTTP请求)的响应时间
- 系统资源使用情况
核心监控组件
- Prometheus:收集和存储时间序列数据的系统
- Grafana:将数据可视化的仪表盘工具
- 指标(Metric):代表系统某个可度量属性的数据点
- 告警规则:当指标超过阈值时触发的通知机制
常用指标类型
- 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服务,暴露以下指标:
app_requests_total- 请求计数器app_active_connections- 当前连接数- 在
/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的服务,添加以下并发监控功能:
- 监控goroutine数量变化
- 记录并发任务队列长度
- 统计任务处理成功率
练习3:告警规则配置
为你的服务配置Prometheus告警规则:
- 当活跃goroutine数超过100时触发警告
- 当请求错误率超过5%时触发严重告警
- 当平均响应时间超过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(...)
}
// 正确:使用定时清理或聚合
小结
关键要点回顾
- 监控必要性:并发应用需要监控goroutine、channel、锁竞争等关键指标
- 三大支柱:Prometheus(采集存储)、Grafana(可视化)、告警系统(通知)
- 核心指标:Gauge(瞬时值)、Counter(累计值)、Histogram(分布统计)
- 最佳实践:
- 遵循命名规范(
namespace_subsystem_name_unit) - 避免高基数标签
- 定期审查指标需求
- 告警规则要具体可操作
- 遵循命名规范(
下一步学习
- 学习使用
pprof进行深度性能分析 - 了解分布式追踪(如Jaeger)
- 探索SLO(服务级别目标)监控
生产环境检查清单
- 监控所有关键业务指标
- 设置合理的告警阈值
- 定期审查和清理无用指标
- 为监控系统设置高可用
- 制定告警响应流程
通过本课学习,你已经掌握了Go并发应用监控的基本框架。记住:好的监控系统就像一个优秀的"应用体检医生",能在问题变得严重之前发现并预警。
练习编辑器
go
Loading...