可观测性之饱和度
Posted on Thu 15 May 2025 in tech
Abstract | 可观测性之饱和度 |
---|---|
Authors | Walter Fan |
Category | learning note |
Status | v1.0 |
Updated | 2025-05-15 |
License | CC-BY-NC-ND 4.0 |
- 🚥 可观测性之饱和度
- 一、什么是可观测性(Observability)?
- 二、四大黄金指标(Four Golden Signals)
- 三、深入理解饱和度(Saturation)
- 四、饱和度的常见表现形式与实例
- 五、饱和度指标的监控与告警建议
- 六、实例
- 七、源码
- 八、小结
🚥 可观测性之饱和度
一、什么是可观测性(Observability)?
在现代软件系统中,可观测性指的是:通过收集系统的外部输出(如日志、指标、追踪)来推断系统内部状态的能力。它不仅是监控的一部分,更是一种设计理念,目标是帮助开发与运维团队快速发现、定位和解决问题。
可观测性强的系统,可以回答这样的问题:
- 系统为什么变慢了?
- 某个请求为什么失败?
- 服务是否濒临崩溃?
二、四大黄金指标(Four Golden Signals)
Google SRE 团队在《Site Reliability Engineering》一书中提出了四个关键的可观测性指标,即“四大黄金信号”(Four Golden Signals):
指标 | 含义 | 举例 |
---|---|---|
Latency(延迟) | 请求完成所需时间 | 登录接口平均响应时间 500ms |
Traffic(流量/使用量) | 系统处理的请求量或数据量 | 每秒接收 1000 个 API 请求 |
Errors(错误) | 请求失败或不符合预期的比例 | 5% 的支付请求返回 500 错误 |
Saturation(饱和度) | 系统资源是否接近瓶颈 | 数据库连接池使用率 99% |
前三个指标关注的是系统外部的表现,而饱和度则更像是一种内部健康信号,是预警的关键。
三、深入理解饱和度(Saturation)
什么是饱和度?
饱和度指的是一个系统在资源使用上接近极限的程度,是衡量系统“快撑不住”的程度。
当 CPU、内存、线程、连接数等资源接近用完时,系统虽然可能还在正常运行,但已经进入危险区——稍微再增加一点负载,就会出现排队、超时、错误等问题。
饱和度 ≠ 错误,但是错误的前奏
举个比喻:
饱和度就像一个高速公路已经塞满了车,但还没真正堵死。如果继续增加车辆,就会交通瘫痪。
四、饱和度的常见表现形式与实例
下面我们通过几个典型资源维度,说明饱和度如何表现,以及如何监控。
1. CPU 饱和
- 症状:CPU 使用率长期超过 85%,服务响应变慢
- 实例:Go HTTP 服务处理图像上传,上传量大增,CPU 一直跑在 95%,导致响应时间从 200ms 增加到 1s+
- 指标:
node_cpu_seconds_total
、process_cpu_seconds_total
2. 内存饱和
- 症状:内存使用接近上限,频繁触发 GC,出现长时间暂停
- 实例:Java 服务处理大批量 JSON 请求,内存使用达 98%,频繁 Full GC,响应时间不稳定
- 指标:
jvm_memory_used_bytes
、go_memstats_heap_alloc_bytes
、GC 停顿时间
3. 连接池饱和
- 症状:数据库/Redis 连接池满,新的请求被阻塞或拒绝
- 实例:服务高峰期数据库连接池(最大 100)全部被占用,新的请求提示
connection timeout
- 指标:
db_pool_active_connections
、db_pool_waiting_connections
4. 线程/协程池饱和
- 症状:Web 服务线程池满,新的请求排队甚至被拒绝
- 实例:Spring Boot 应用线程池最大 200,活跃线程数长期维持在 200,出现
RejectedExecutionException
- 指标:
active_threads / max_threads
、go_goroutines
、jvm_threads_live
5. 消息/请求队列堆积
- 症状:任务处理速度慢于接收速度,队列堆积
- 实例:RabbitMQ 中订单处理队列从 100 增长到 50,000,消费者压力巨大
- 指标:
queue_length
、pending_tasks
五、饱和度指标的监控与告警建议
要让饱和度发挥作用,建议为以下场景设置阈值告警:
CPU 使用率 > 90% 且持续 5 分钟
数据库连接池使用率 > 95%
线程池活跃线程数 = 最大线程数
请求队列长度 > 1000
GC 时间 > 1 秒 且频率异常
这些告警可以帮你在系统彻底崩溃前就采取措施,如:
- 扩容实例
- 实施限流
- 降级非核心功能
- 优化资源使用
六、实例
以一个典型的 Go 服务为例, 假设具备以下特性:
- 使用
net/http
或Gin/Echo
提供 HTTP API - 有业务逻辑处理、数据库访问、任务队列等操作
- 已使用
Prometheus
+Grafana
进行监控 - 服务部署在 Linux 上(如 K8s 或裸机)
1. 饱和度监控的关键维度
资源维度 | 风险表现 | 饱和度指标示例 |
---|---|---|
CPU 使用率 | 高负载下处理变慢 | process_cpu_seconds_total 、node_cpu_seconds_total |
内存使用率 | GC 频繁、服务变卡、OOM 崩溃 | go_memstats_alloc_bytes 、go_memstats_heap_inuse_bytes |
Goroutine 数 | 协程泄漏或堆积,服务阻塞 | go_goroutines |
线程数 | 操作系统资源瓶颈 | go_threads (如启用)或 ps 工具 |
数据库连接池 | 连接耗尽、排队 | 自定义指标,如 db_pool_in_use / db_pool_max |
HTTP 请求排队 | 请求未能及时处理、响应变慢 | 中间件记录队列时间、自定义指标 |
任务队列长度 | 队列堆积、处理线程/协程压力大 | queue_length 、pending_tasks (需自定义) |
2.Prometheus 具体监控指标配置
2.1 CPU 使用率
100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[1m])) * 100)
- 告警建议:CPU 使用率 > 90%,持续 3 分钟
- 可视化:Grafana 曲线图,颜色随负载变化
2.2 内存使用 / GC 时间
go_memstats_alloc_bytes / go_memstats_sys_bytes
rate(go_gc_duration_seconds_sum[5m])
-
告警建议:
-
内存使用率 > 90%
- GC 时间 > 500ms 且频率 > 每分钟 5 次
2.3 Goroutine 数量
go_goroutines
-
告警建议:
-
goroutine 数量 > 平均值 2 倍,持续 3 分钟
- goroutine 数呈线性增长趋势(泄漏可能)
2.4 数据库连接池使用率(需你在代码中暴露)
假设你用的是 GORM + PostgreSQL,建议暴露如下自定义指标:
db_pool_in_use / db_pool_max
示例注册 Prometheus 指标:
prometheus.NewGaugeFunc(prometheus.GaugeOpts{
Name: "db_pool_in_use_ratio",
Help: "Database connection pool usage ratio",
}, func() float64 {
stats := db.DB().Stats()
return float64(stats.InUse) / float64(stats.MaxOpenConnections)
})
- 告警建议:使用率 > 95%,持续 2 分钟
2.5 HTTP 请求排队时间(需自定义中间件)
在中间件中记录请求开始到真正处理的延迟:
func queueTimeMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
queuedAt := time.Now()
next.ServeHTTP(w, r)
queueDuration := time.Since(queuedAt)
queueTimeHistogram.Observe(queueDuration.Seconds())
})
}
注册 Prometheus Histogram:
queueTimeHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "http_request_queue_duration_seconds",
Help: "Time spent in request queue before processing",
Buckets: prometheus.DefBuckets,
})
- 告警建议:P95 请求队列时间 > 100ms
2.6 任务队列长度(如使用 channel、Redis、Kafka)
如果你有一个内部任务队列,可自定义指标:
taskQueueLength = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "internal_task_queue_length",
Help: "Current length of internal async task queue",
})
在生产/消费中更新它:
taskQueueLength.Set(float64(len(taskChan)))
- 告警建议:队列长度 > 平均值 3 倍 或 超过安全阈值(如 1000)
3. Grafana 面板建议
建议创建一个 Saturation Dashboard,包含:
面板 | 图表类型 | 描述 |
---|---|---|
CPU 使用率 | 折线图 | 节点整体 CPU |
内存使用 & GC 时间 | 折线图 + 柱状图 | 查看 GC 高峰 |
goroutines | 折线图 | goroutine 增长趋势 |
DB 连接池使用率 | 热力图 | 查看使用高峰 |
请求队列时间 | 分位直方图 | 重点看 P95 |
队列长度 | 折线图 | 消费速度 vs 队列增长 |
4. 预防机制建议
结合饱和度指标,可以实施以下策略:
策略 | 应对场景 |
---|---|
限流(如令牌桶) | 请求过多,goroutine 飙升 |
自动扩容(HPA) | CPU/Goroutine 饱和时动态增实例 |
资源隔离 | 后台任务、数据库查询用不同线程池 |
降级 | 请求过载时跳过缓存、关闭非核心功能 |
指标驱动告警 | 结合饱和度触发 PagerDuty / 企业微信通知 |
七、源码
https://github.com/walterfan/kata-go/tree/master/kata/prompt_service
功能模块
模块 | 功能说明 |
---|---|
main.go | 程序入口,使用 cobra 支持命令行参数(如监听端口),集成 zap 日志系统。 |
pkg/database/sqlite.go | 初始化 SQLite 数据库连接,并提供数据迁移和初始化样本数据的功能。 |
pkg/models/prompt.go | 定义 Prompt 结构体,映射数据库表结构,包含字段如 Name, Description, Tags, UserPrompt, SystemPrompt 等。 |
pkg/handlers/prompt_handler.go | 提供 RESTful API 接口: |
- GET /metrics : 获取 Prometheus 监控指标 |
|
- POST /api/v1/prompts : 创建 Prompt |
|
- GET /api/v1/prompts/:id : 获取单个 Prompt |
|
- PUT /api/v1/prompts/:id : 更新 Prompt |
|
- DELETE /api/v1/prompts/:id : 删除 Prompt |
|
- GET /api/v1/prompts : 支持关键字搜索与分页 |
|
pkg/metrics/metrics.go | 集成 Prometheus 指标监控,记录 HTTP 请求次数、耗时等信息。 |
技术栈
技术 | 用途 |
---|---|
Gin | Web 框架,用于构建 HTTP 服务。 |
GORM + SQLite | ORM 和数据库,用于持久化存储 prompts 数据。 |
Prometheus + Metrics Middleware | 监控接口调用次数、延迟等运行指标。 |
Zap | 高性能日志库,用于记录服务日志。 |
Cobra | CLI 命令行支持,用于解析启动参数(如监听端口)。 |
启动服务 go run main.go -p 8888
调用 curl http://localhost:8888/metrics
即可观察服务运行的度量指标。
八、小结
在实际工作中,我们常常关注“慢没慢”、“错没错”,但忽略了“快顶不住了”这个临界状态。饱和度正是你发现“即将撑不住”时的哨兵。
饱和度 ≠ 故障,但它预示着故障即将发生。
一个具备良好可观测性的系统,应该同时涵盖四大黄金指标。尤其是高并发、微服务、分布式架构中,饱和度监控是不可或缺的。 它是提前预警系统压力的重要信号,监控饱和度能让你:
- 在服务抖动前就开始扩容或限流
- 更有效地定位是 CPU、内存、线程还是连接池成为瓶颈
- 做好弹性架构与可用性保障
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。