这篇博文的面向群体是 还不太了解 Prometheus 和 想要开始使用 Prometheus 的人群.html
本文想作的事是 想尽力讲清楚 Prometheus 是如何看待监控这件事情 以及 Prometheus 是如何实现这些需求的.golang
本文中不会出现的内容: 跟 Prometheus 实现细节有太多相关的东西 等数据库
当想看监控的时候, 咱们到底想要什么?
咱们想要看的东西也就是咱们对监控的需求.数组
需求
在实际的生产过程当中, 产生的和须要收集的监控数据分为不少种, 例如如下这些, 除此以外, 还有不少不少. 但从实现方式上来讲, 大多都大同小异.微信
瞬时状态的 CPU 和 MEM 使用率读数并发
硬盘使用量的增加率函数
对 集群节点 状态 进行筛选 , 记录节点位于什么时刻不可用, 这就要求有 Tag 支持优化
瞬时状态的 网卡流量, 例如 100 Mbps,url
服务请求量, 服务的 QPS, 服务的 错误率和错误次数spa
所有请求的平均时耗
一段时间内, 全部请求的 时耗中, 50% 的请求时耗小于多少毫秒, 95% 的请求时耗小于多少毫秒? 以此评估总体的接口状况
一段时间内, 全部请求的 时耗中, 多少请求时耗大于 1000ms, 多少请求时耗位于 200-500 区间内, 用于了解 请求时耗的具体分布, 以评估接口状况
……
那么咱们就须要一个 监控系统
来完成 上述需求, 这个监控系统 仅仅能收集上面的这些数据还不够, 若是不能展现 和 查询, 这些数据的保存将毫无心义. 另外, 既然是监控, 那么必然要有告警的功能.
那么总结一下, 咱们须要 可以 将 监控数据 收集
和 查询
的监控系统 来完成咱们的需求, 除此以外, 咱们还须要 告警 和 展现 的功能
而 Prometheus 就是完成了 咱们上述需求的一个 监控系统 的 实现.
从 Prometheus 的视角看这些需求
数据的保存
Prometheus 使用一个 TSDB 来保存 这些监控数据,
TSDB 的全称是 Time series Database (时序数据库), 是为了解决 时序性数据的保存问题, 而诞生的 数据库类型.
一开始的话, 其实这些保存 时序型数据 的需求均可以使用 关系型 数据库 来解决. 但 若是直接基于 关系型数据库 来直接作需求的话, 各类写入 和 读出的 适配都得得按照 关系型数据库的规则来作, 比较麻烦, 虽然确实有一些 TSDB 是基于 关系型数据库实现的.
另外 因为时序型数据 的一些特色, 例如 大部分都是写入操做, 极少修改, 大部分读都是顺序读, 对于一个指标的分析. 那么基于这些特色, 又有一些 技巧, 来作不少的优化, 因此便有了 单独的 TSDB 实现. 例如 InfluxDB, FaceBook 的 Gorilla 等, Prometheus 的 TSDB 即是 参考了 FB 的 Gorilla 以后, 自行实现的.
TSDB 中的 保存的数据, 一般以 数据点(Point)
做为基本单位, 多个 Point 构成 Series (序列)
, 全部关于同一个主题的数据点 构成 Metrics(指标)
, 而每一个数据点会带有一个 TimeStemp , 也就是这个点所关联的时间, 而后每一个数据点会带一些 Tag, 也能够叫 Label , 下文中一概称为 Tag
而后咱们能够经过对 TSDB 经过 指定 一些相关的指标和 Tag 来进行查询, TSDB 的 数据的层级结构以下所示.
TSDB 和 关系型数据库系统 相似, 一般分为三层, 读者能够按这种对应关系来理解, 不过细节的意义上仍是有所不一样的.
Database
对应 RDBMS 的 Schema , 概念相似 , 而在 Prometheus 中则没有 这一层, 全部的 Metrics 都在一个 Database 中Metrics
对应 RDBMS 的 Table , 在 InfluxDB 中这一层叫作measurement
,概念相似 .Point
对应 RDBMS 的 一条数据 Row , 这个是 TSDB 中的最小单位, 每一个 Point 带有一些 Tag , 能够根据不一样的条件筛选出来
收集? 如何收集?
监控的收集无非就是上报, 而上报须要 服务端 和 客户端配合. 这里先介绍服务端.
对 Prometheus 来说, 他的上报采用的是 pull 模型, 也就是拉取模型, 服务端根据客户端的位置, 按时去固定接口拉取. PULL 模型 相较于 PUSH 模型在 客户端较多的状况下较为明显, 很好了缓解了 服务端的并发压力, 但也带来了一些问题, 例如 每一个客户端的 位置都要注册给服务端, 会很麻烦, 这个问题一般使用 服务发现和注册
来解决.
整个过程上报流程能够描述为 客户端按照协议, 准备好数据, 而后服务端的抓取器 定时访问, 来抓取.
相较于 服务端, 客户端就要复杂的多, 咱们先来聊聊 Prometheus 上报的协议
协议
Prometheus 服务端 要求 客户端 准备一个 访问 endpoint, 在 Prometheus 访问 endpoint 的时候, 客户端程序须要按照 相似于下面这种格式 准备好 上报数据,
# 这里每一条 由 三个部分组成,
# metricName : metrics 的名字 // [a-zA-Z_:][a-zA-Z0-9_:]*
# labels : 也能够不填 // [a-zA-Z0-9_]*
# value : float64 类型的值
#
<--- metricName ---> <- labels -> <--value-->
go_gc_duration_seconds{quantile="0"} 1.1883e-05
go_gc_duration_seconds{quantile="0.25"} 2.2286e-05
go_gc_duration_seconds{quantile="0.5"} 4.734e-05
go_gc_duration_seconds{quantile="0.75"} 7.4898e-05
go_gc_duration_seconds{quantile="1"} 0.000809044
go_gc_duration_seconds_sum 0.683513876
go_gc_duration_seconds_count 10304
go_goroutines 36
go_info{version="go1.13.12"} 1
go_memstats_alloc_bytes 1.2614712e+07
go_memstats_alloc_bytes_total 8.483245152e+10
go_memstats_buck_hash_sys_bytes 1.648549e+06
go_memstats_frees_total 1.199358391e+09
而后 Prometheus 服务端 在抓取到这些数据, 会将一条记录作成一个 point , 存入 TSDB 中.
这就是所有协议的内容, 很简单. 另外上面的 endpoint 一般状况下 是一个 相似于 http://ip:port/metrics
这样,而后以 metrics 结尾的 url, 不过也能够根据自身需求来拟定 url 格式.
抽象
协议在 描述 服务端 如何与 客户端 交互, 但若全部监控项 都由 客户端这么生成其实会有些繁琐 和 混乱, 因此 Prometheus 的客户端除了对 生成结构进行封装外, 还提出了 四种 Metrics 规则类型 供 用户使用. 这些 Metrics 规则类型仅仅和 Client 端有关, 与 Server 无关.
不少教程 一上来就给你讲 四种 Metrics 规则类型, 让人觉得 这四个东西是 Prometheus 里很是重要的 东西, 甚至笔者在 写这篇 文章的前几版 以前, 也是这样认为. 但事实上, 这仅仅表明 Prometheus 看待 监控这件事的想法和态度 , 而后抽象出来一方面方便用户使用, 另外一方面, 在社区发展第三方内容(例如 第三方 Client SDK 和 Exportor )的时候, 能够规范你们的实现, 以方便讨论.
即使 用户不使用这四种 Metrics 规则类型, 也能够彻底自定义本身的指标数据, 甚至定义本身的 指标类型 , 只要知足 上述协议便可. 这四种 Metrics 类型, 更像 Prometheus 团队 在作监控这件事上 ,提出的一个行之有效的方法论(maybe 是 Google 团队 XD).
除此以外, 因为 这些规则被放在 Client 放在客户端运行, 全部的 规则都会在算好以后, 被抓取到服务端, 这样也进一步 降低了服务端的压力, 和上面使用 Pull 的方式 的想法相似, 将压力下推和分散到 Client 端.
只加不减的类型 Counter
顾名思义 Counter, 累加者, 这个是最经常使用的类型, 经常用于 记录 HTTP Request Total
这种数据, 这个 类型 一般会一直上升, 基于这个特性, 咱们又可使用一些函数来获取到另外一些咱们比较常关心的 指标 , 例如 单日访问量 和 QPS
因为只增不减 的特性, 因此咱们使用 当前时刻的值 减去 当前时刻一日以前的值, 便可得到单日的访问量, 例以下图, 用
2020-01-03 11:00
时刻的数据, 减去2020-01-02 11:00
时刻的数据, 便可算出单日请求量, 事实上, 任意时段的 请求量, 咱们均可以使用相似的方法算出.有了 时间段内请求量, 咱们要算出 QPS 那就很简单了, 只须要 使用
时间段内请求量 / 秒数
便可, 例如 想展现一天时段内的 QPS, 咱们以 5m 为一格, 那么 5m 内的 QPS 就是5m 内的请求总数 / 60*5
, 而后把每一个 5m 的 QPS 算出来, 展现在 时间轴上, 便可看到一天 全部时段的 QPS 数据
瞬时指标 Gauge
这个指标就比较直白, 用于表示一些瞬时指标, 例如 上面需求提到的 瞬时 CPU 读数, 瞬时 Disk Used Space, 等 , 这个指标不会累加, 只是按当前值为主.
直方图 Histogram 和 分位值 Summary
这两个指标类型在使用上, 一般会一块儿使用. 但也根据需求, 有时候会单独使用. 在聊这两种指标以前, 我想先聊聊 平均值
, 用来引出 这两种指标.
平均值的缺陷
假设 以下图的一个瞬间, 系统 处理 100 条并发请求的 时耗以下左边所示, 这 100 条请求里面 时耗分为 五个段, 0 ~ 100 ms
,101~200 ms
,201 ~ 300 ms
,301 ~ 400 ms
,401 ~ 500 ms
, 接着 很快就能算出 这一秒请求的时耗 平均值 是 300 ms, 在 3s 或者更长的时间段里, 咱们都能看到时间段内的全部请求的平均值 在 300ms
.
假设请求时延 超过 400 ms 被认为 不可接受
, 那么很明显, 若是咱们只关注 平均值(avg) , 咱们就极可能认为这一时段, 系统正处于正常状态, 但实际上, 系统此时已经发生异常, 有 20% 的 请求处于不可接受的状态. 平均值 过分的 屏蔽了 请求时耗分布
的 具体细节.
分位值 Summary
而为了解决 平均值的这个缺陷, 这个时候就须要引入 分位值(Top Percentile)
的概念.
顾名思义, 分位值 就是 分位 分位上的值, 例如 中位数, 其实也就是 50% 分位数. 一般计算分位值的 分位数 是在将 样本集 的数据排序后, 取出指定位置的数据 做为对应位置的分位值.例如 一个已经排好序的数组 里面有 100 个数, 那么 这个数组的 25% 分位值 就是第 25 个数. 例以下面的 图, 就分别展现了 25% 分位值, 50% 分位值, 75% 分位值 , 95% 分位值 和 99% 分位值
那么利用分位数,咱们就能够明确知道 这个时段内的请求分布状况, 相比刚才计算 平均数的方式, 分位值 的优点就展现的比较明显.
分位值除了能够比较好的展现时段内请求的细节外, 分位值还能够做为服务异常的基准指标, 以搜索接口为例, 假设 规定搜索接口 95% 的请求都须要保证在 1s 内完成, 若是超过就告警. 用分位值来作就很好作, 直接 95% 的分位值 不容许大于 1s 便可.
在 Prometheus 中, 客户端根据 协议 提交给 服务端 的 分位数 指标数据大体会像下面这个样子
# quantile 能够由用户自行指定
go_gc_duration_seconds{quantile="0"} 1.1883e-05
go_gc_duration_seconds{quantile="0.25"} 2.2286e-05
go_gc_duration_seconds{quantile="0.5"} 4.734e-05
go_gc_duration_seconds{quantile="0.75"} 7.4898e-05
go_gc_duration_seconds{quantile="0.99"} 0.000809044
go_gc_duration_seconds_sum 0.683513876
go_gc_duration_seconds_count 10304
直方图 Histogram
但仅仅只有 Summary , 仍是不太够, 虽然知道了分位值 , 但有时候咱们想知道, 到底有多少请求 大于 400ms , 多少请求在 100ms 内, 这个仅仅经过 Summary 是没有办法告诉咱们的, 咱们须要借助 Histogram 来表示.
Histogram 也就是直方图, 没错, 就是 小学课本上那种. 在客户端 使用直方图 进行计数, 咱们就能够很清晰的看到 请求时耗
在 咱们划定的 区间中的分布.
在 Prometheus 中, 客户端根据 协议 生成好后, 提交给 服务端 的 指标数据大体会像下面这样.
# le 能够由用户自行指定
prometheus_tsdb_compaction_chunk_range_bucket{le="100"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="1000"} 100
prometheus_tsdb_compaction_chunk_range_bucket{le="1600"} 100
prometheus_tsdb_compaction_chunk_range_bucket{le="409600"} 100
prometheus_tsdb_compaction_chunk_range_bucket{le="1.6384e+06"} 260
prometheus_tsdb_compaction_chunk_range_bucket{le="2.62144e+07"} 780
prometheus_tsdb_compaction_chunk_range_bucket{le="+Inf"} 780
prometheus_tsdb_compaction_chunk_range_sum 1.1540798e+09
prometheus_tsdb_compaction_chunk_range_count 780
关于直方图的实现, Prometheus 客户端 生成的 并非像上面图这样错落有致, 而是用这个公式这样算出来的 当前的区间的值 = 当前区间内的实际值 + 上一个区间的值
, 初看可能比较绕. 不过这样作的好处有不少,
咱们若是要计算 任意区间之间的 实际值 的话, 就只须要使用后面区间减去 前面区间的值便可, 在计算多个区间的值的时候尤为明显.
当区间在减小的时候, 数据依旧不失真
数据存储时比较好优化
……
结
至此, 咱们基本讲完了 Prometheus 的大多数 使用细节. 接着咱们来看看 Prometheus 的结构.
Prometheus
上面这个是 Prometheus 官网的 Prometheus 结构图, 咱们能够看到 Prometheus 的结构分为三块,
最左边的 是 监控数据的 source 区, 里面有 Prometheus 使用它的 抓取器 来抓取 咱们的 应用程序的 监控数据, 而后保存到 TSDB 上,
接着 最右边的 部分分为两块, 一块是 AlertManager , Prometheus 会按期检查一些告警规则, 若是这些规则 被知足, 将会 推送 给 AlertManager 表示这些数据须要告警. 另外一块是 展现的部分 , Grafana 或者 Prometheus 的 WebUI 经过 PromQL 查询 Prometheus 的 TSDB 来获取 结果并展现.
告警
告警的部分, 在 Prometheus 推送消息给 AlertManager 以后, AlertManager 会自行判断, 这条告警是否须要推送出去, AlertManger 中有一些 沉默 和 告警阈值的规则, 当 一条告警触发 多少次, 或者 多久以内触发一次, 就会告警到 设置好的 Channel , 这样能够避免 由 告警风暴 带来的麻痹 和 狼来了的故事.
PromQL
PromQL 是 Prometheus 设计出来的一种 DSL , 使用起来的感受向使用 函数. 关于 PromQL 的内容, 能够参考 另外一篇博文 PromQL 指南
基于指标的监控系统 和 基于日志的监控系统的区别
在 接触 Prometheus + Grafana 指标监控系统 以前, 笔者也接触过 ElasticSearch + Logstash/Fluentd 的 日志监控系统,
笔者认为两者各有各的优点, Prometheus 方案的重点在于轻和迅速, 没有太多的基础设施, 甚至能够不依赖 服务注册中心, 只要把 Prometheus 拉起来, 而后 服务接入一下 Prometheus Client , 就能够开始使用监控. 但 Prometheus 因为基于 TSDB 的缘故, 因此 Prometheus 没有办法支持太太高维度的指标 或者 枚举值太多的 Tag, 而这点对于 基于日志监控系统 来说则还好.
而基于日志的监控系统的问题在于过重了, 光是搭建和维护 一个 ES 集群加上 Logstash 以及 Beats 收集器 和 Kibana, 就已经有些费力 . 另外 过多的 东西须要在 Logstash 这一层配置, 每次 业务方新的需求写到日志中, 须要添加一些 Logstash 的 配置, 来解析日志以方便 Kibana 的视图查询.固然咱们也能够用 通用解析的方案来实现, 那么接着有时候就要添加一些 Index 规则 , 笔者以为太高的自由度带来了更多问题. 固然这只是笔者的想法, 若是有别的见解, 欢迎交流.
因此总结来说, 笔者认为, 基于指标的监控系统 和 基于日志的监控系统 更像是一种互补的方案, 虽然一般状况下, 监控需求方面, 若是须要关注 太高维度的指标或者太高枚举值的 状况, 一般都是 这个需求自己就不合理. 但仍然有些状况, 咱们必须实现这种需求, 那么就能够考虑 基于日志的监控系统
Ref
https://developer.aliyun.com/article/174535
https://www.cnblogs.com/jimbo17/p/8337535.html
https://fabxc.org/tsdb/
https://www.jianshu.com/p/31afb8492eff
本文分享自微信公众号 - GoCN(golangchina)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。