为什么须要指标监控告警
一个简单的利用,往往由很多个模块组成,而且往往会存在各种各样奇奇怪怪的应用场景,谁也不能保障本人保护的服务永远不会出问题,等用户投诉才发现问题再去解决问题就为时已晚,损失已无法挽回。
所以,通过数据指标来掂量一个服务的稳定性和解决效率,是否失常运作,监控指标曲线的状态,指标出现异常时及时被动告警,这一套工具就非常重要。
常见的一些指标,包含但不限于:
- QPS
- 申请解决耗时
- 过程占用内存
- 过程占用 CPU
- golang 服务的 goroutine
- nodejs 的 event loop lag
- 前端利用的 Performance 耗时
- …
举个例子,如果一个服务:
- 应用内存随着工夫逐步上涨
- CPU 占用越来越高
- 申请耗时越来越高,申请成功率降落
- 磁盘空间频频被挤爆
<!– 这到底是兽性的扭曲还是道德的沦丧,–>
一旦服务存在某些缺点导致这些问题,通过服务日志,很难直观疾速地察觉到这些指标的变动稳定。
通过监控和告警伎俩能够无效地笼罩了「发现」和「定位」问题,从而更有效率地排查和解决问题。
指标监控零碎:Prometheus
Prometheus 是一个开源的服务监控零碎和工夫序列数据库。
工作流能够简化为:
- client 采集以后 机器 / 服务 / 过程 的状态等相干指标数据
- Prometheus server 按肯定的工夫周期被动拉取 client 的指标数据,并存储到时序数据库中
- 发现指标异样后,通过 alert manager 将告警告诉给相干负责人
具体的架构设计如下:
为什么不必 mysql 存储?
Prometheus 用的是本人设计的时序数据库(TSDB),那么为什么不必咱们更加相熟,更加罕用的 mysql, 或者其余关系型数据库呢?
假如须要监控 WebServerA 每个 API 的申请量为例,须要监控的维度包含:服务名(job)、实例 IP(instance)、API 名(handler)、办法(method)、返回码(code)、申请量(value)。
如果以 SQL 为例,演示常见的查问操作:
# 查问 method=put 且 code=200 的申请量
SELECT * from http_requests_total WHERE code=”200”AND method=”put”AND created_at BETWEEN 1495435700 AND 1495435710;
# 查问 handler=prometheus 且 method=post 的申请量
SELECT * from http_requests_total WHERE handler=”prometheus”AND method=”post”AND created_at BETWEEN 1495435700 AND 1495435710;
# 查问 instance=10.59.8.110 且 handler 以 query 结尾 的申请量
SELECT * from http_requests_total WHERE handler=”query”AND instance=”10.59.8.110”AND created_at BETWEEN 1495435700 AND 1495435710;
通过以上示例能够看出,在罕用查问和统计方面,日常监控多用于依据监控的维度进行查问与工夫进行组合查问。如果监控 100 个服务,均匀每个服务部署 10 个实例,每个服务有 20 个 API,4 个办法,30 秒收集一次数据,保留 60 天。那么总数据条数为:100(服务)* 10(实例)* 20(API)* 4(办法)* 86400(1 天秒数)* 60(天) / 30(秒)= 138.24 亿条数据
,写入、存储、查问如此量级的数据是不可能在 Mysql 类的关系数据库上实现的。 因而 Prometheus 应用 TSDB 作为 存储引擎。
时序数据库(Time Series Database/TSDB)
时序数据库次要用于指解决带工夫标签(依照工夫的程序变动,即工夫序列化)的数据,带工夫标签的数据也称为时序数据。
对于 prometheus 来说,每个时序点构造如下:
- metric: 指标名,以后数据的标识,有些零碎中也称为 name。
- label: 标签属性
- timestamp: 数据点的工夫,示意数据产生的工夫。
- value: 值,数据的数值
每个指标,有多个时序图;多个时序数据点连接起来,形成一个时序图
如果用传统的关系型数据库来示意时序数据,就是以下构造:
create_time | __metric_name__ |
path | value |
---|---|---|---|
2020-10-01 00:00:00 | http_request_total | /home | 100 |
2020-10-01 00:00:00 | http_request_total | /error | 0 |
2020-10-01 00:00:15 | http_request_total | /home | 120 |
2020-10-01 00:01:00 | http_request_total | /home | 160 |
2020-10-01 00:01:00 | http_request_total | /error | 1 |
指标 request_total{path=”/home”} 在 2020-10-01 00:01:00 时的 qps = (160 – 100)/60 = 1 , 同理,
指标 request_total{path=”/error”} 在 2020-10-01 00:01:00 时的 qps = 1/60
相比于 MySQL,时序数据库外围在于时序,其查问工夫相干的数据耗费的资源绝对较低,效率绝对较高,而恰好指标监控数据带有显著的时序个性,所以采纳时序数据库作为存储层
数据类型
- counter: 计数器,只能线性减少,一直变大,场景:qps
- gauge:绝对值,非线性,值可大可小,场景:机器温度变动,磁盘容量,CPU 使用率,
- histogram:,聚合数据查问耗时散布【服务端计算,含糊,不准确】
- summary:不能聚合查问的耗时散布【客户端计算,准确】
nodejs 指标采集与数据拉取
- 定义一个 Counter 的数据类型,记录指标
const reqCounter = new Counter({
name: `credit_insight_spl_id_all_pv`,
help: 'request count',
labelNames: ['deviceBrand','systemType', 'appVersion', 'channel']
})
reqCounter.inc({
deviceBrand: 'Apple',
systemType: 'iOS',
appVersion: '26014',
channel: 'mepage'
},1)
- 定义拜访门路为
/metrics
的 controller
@Get('metrics')
getMetrics(@Res() res) {res.set('Content-Type', register.contentType)
res.send(register.metrics())
}
- Prometheus 被动申请 node client 的
/metrics
接口,取得
以后数据快照
promQL
promQL 是 prometheus 的查询语言,语法非常简略
根本查问
查问指标最新的值:
{__name__="http_request_total", handler="/home"}
# 语法糖:http_request_total{handler="/home"}
# 等价于 mysql:
select * from http_request_total
where
handler="/home" AND
create_time=《now()》
区间时间段查问
查问过来一分钟内的数据
# promQL
http_request_total[1m]
# 等价于
SELECT * from http_requests_total
WHERE create_time BETWEEN《now() - 1min》AND《now()》;
工夫偏移查问
PS:promQL 不反对指定工夫点进行查问,只能通过 offset 来查问历史某个点的数据
查问一个小时前的数据。
# promQL
http_request_total offset 1h
# 等价于
SELECT * from http_requests_total
WHERE create_time=《now() - 1 hour》;
promQL 查问函数
依据以上的查问语法,咱们能够简略组合出一些指标数据:
例如,查问最近一天内的 /home 页申请数
http_request_total{handler="/home"} - http_request_total{handler="/home"} offset 1d
那么实际上面这个写法很显著比拟不简洁,咱们可应用内置 increase 函数来替换:
# 和上述写法等价
increase(http_request_total{handler="/home"}[1d])
除了 increase 外,还有很多其余好用的函数,例如,
rate 函数计算 QPS
// 过来的 2 分钟内均匀每秒申请数
rate(http_request_total{code="400"}[2m])
// 等价于
increase(http_request_total{code="400"}[2m]) / 120
指标聚合查问
除了上述根底查问外,咱们可能还须要聚合查问
如果咱们有以下数据指标:
credit_insight_spl_id_all_pv{url="/home",channel="none"}
credit_insight_spl_id_all_pv{url="/home",channel="mepage"}
credit_insight_spl_id_all_pv{url="/error",channel="none"}
credit_insight_spl_id_all_pv{url="/error",channel="mepage"}
将所有指标数据以某个维度进行聚合查问时,例如:查问 url=”/home” 最近一天的访问量,channel 是 none 还是 mepage 的 /home 访问量都包含在内。
咱们天经地义地会写出:
increase(credit_insight_spl_id_all_pv{url="/home"}[1d])
但实际上咱们会得出这样的两条指标后果:
credit_insight_spl_id_all_pv{url="/home",channel="none"} 233
credit_insight_spl_id_all_pv{url="/home",channel="mepage"} 666
并非咱们预期中的:
credit_insight_spl_id_all_pv{url="/home"} 899
而要是咱们想要失去这样的聚合查问后果,就须要用到 sum by
# 聚合 url="/home" 的数据
sum(increase(credit_insight_spl_id_all_pv{url="/home"}[1d])) by (url)
# 得出后果:credit_insight_spl_id_all_pv{url="/home"} 899 # 所有 channel 中 /home 页访问量累加值
# 聚合所有的 url 则能够这样写:sum(increase(credit_insight_spl_id_all_pv{}[1d])) by (url)
# 得出后果:credit_insight_spl_id_all_pv{url="/home"} 899
credit_insight_spl_id_all_pv{url="/error"} 7
# 等价于 mysql
SELECT url, COUNT(*) AS total FROM credit_insight_spl_id_all_pv
WHERE create_time between <now() - 1d> and <now()>
GROUP BY url;
指标时序曲线
以上的所有例子的查问数值,其实都是 最近工夫点 的数值,
而咱们更关注的是一个 时间段 的数值变动。
要实现这个原理也很简略,只须要在历史的每个工夫点都执行一次指标查问,
# 如果明天 7 号
# 6 号到 7 号的一天访问量
sum(increase(credit_insight_spl_id_all_pv{}[1d] )) by (url)
# 5 号到 6 号的一天访问量 offset 1d
sum(increase(credit_insight_spl_id_all_pv{}[1d] offset 1d)) by (url)
# 4 号到 5 号的一天访问量
sum(increase(credit_insight_spl_id_all_pv{}[1d] offset 2d)) by (url)
而 Prometheus 曾经内置了时间段查问性能,并对此优化解决。
可通过 /api/v1/query_range
接口进行查问,获的 grpah:
Prometheus 查问瓶颈
数据存储:
指标数据有“Writes are vertical,reads are horizontal”的(垂直写,程度读)模式:
“Writes are vertical,reads are horizontal”的意思是 tsdb 通常按固定的工夫距离收集指标并写入,会“垂直”地写入最近所有工夫序列的数据,而读取操作往往面向肯定工夫范畴的一个或多个工夫序列,“横向”地逾越工夫进行查问
- 每个指标(metric)依据指标数量不同,有 labelA labelB labelC * … 个时序图
- 每个时序图(time series)的一个点时序是 [timestamp, value], 例如 [1605607257, 233]。[工夫戳 - 值] 能够确定图上的一个点,一个工夫区间内的所有点连成一个时序曲线图。
- 因为 Prometheus 每隔 15s 采集一次数据,所以 时序点的工夫间距是 15s,即 1 分钟有 60/15= 4 个时序点,1 小时就有 4 * 60 = 240 个时序点。
而 Prometheus 的默认查问 sample 下限是 5000w
所以,如果指标的时序图数量过大,容许查问的工夫区间绝对就会较小了。
一个图表查问时序数量的影响因素有 3 个,别离是:
- 查问条件的时序数量(n)
- 查问的工夫区间(time)
- 图表曲线每个时序点之间的距离(step)
以 credit_insight_spl_id_all_pv
指标为例,该指标总共大概有 n = 163698 种时序,
如果 step = 15s,如果搜寻该指标过来 time = 60m 的全副时序图,那么,须要搜寻的例子要163698 * 60 * (60/15) = 39287520
,将近 4kw,是能够搜进去的。
但如果搜的是过来 90m 的数据,163698 * 90 * 4 = 58931280
,超过了 5000w,你就发现数据申请异样:Error executing query: query processing would load too many samples into memory in query execution
所以,目测可得一个图的查问时序点数量公式是:total = n * time / step,time 和 step 的工夫单位必须统一,total 必须不超过 5000w。
反推一下得出,time < 5000w / n * step。要扩充搜寻工夫范畴,增大 step,或者升高 n 即可做到。
- step 不变,升高 n【指定 label 值可缩小搜寻条件的后果数】:
credit_insight_spl_id_all_pv{systemType="Android", systemVersion="10"}
,n = 18955
- 增大 step 到 30s,n 不变:
当然,个别状况下,咱们的 n 值只有几百,而 step 根本是大于 60s 的,所以个别状况下都能查问 2 个多月以上的数据图。
可视化平台:Grafana
grafana 是一个开源的,高度可配置的数据图表剖析,监控,告警的平台,也是一款前端可视化的产品。
自定义图表
grafana 内置提供多种图表模板,具体是以下类型:
Prometheus 作为数据源的状况下,个别用的 graph 类型画时序图比拟多。
对于一些根底的数据大盘监控,这些图表类型曾经足够满足咱们的需要。
但对于简单的需要,这些类型无奈满足咱们的须要时,咱们装置 pannel 插件,来更新可用的图表类型,也能够依据官网文档 build a panel plugin 开发本人的前端图表 panel。
图表配置
在时序图表配置场景下,咱们须要外围关注配置的有:
- promQL: 查问语句
- Legend: 格式化图例文本
- step/interval: 采集点距离,每隔一段时间,采集一次数据。
一条曲线的数据点数量 = 图表时长 / 采样距离。例如查看最近 24 小时的数据,采样 距离 5min,数据点数量 =24*60/5=288。
采集间隔时间越短,采样率越大,图表数据量越大,曲线越平滑。采集距离默认主动计算生成,也能够自定义配置。 - metric time range: 每个点的数据统计工夫区间时长。
以 QPS 为例,图表上每个工夫点的数据的意义是:在这工夫点上,过来 n 秒间的访问量。
从上图能够看到,
- 如果采样距离 > 统计区间时长: 数据采样率 < 100%。未能采集到的数据抛弃,不会再图表上展现。采样率过小可能会谬误异样的数据指标。。
- 如果采样距离 == 统计区间时长,采样率 100%。
- 如果采样距离 < 统计区间时长,数据被反复统计,意义不大。
自定义变量
为了实现一些罕用的筛选过滤场景,grafana 提供了变量性能
- 变量配置:变量配置有多种形式(Type),能够自定义选项,也能够依据 prometheus 指标的 label 动静拉取。
- 变量应用:变量通过
$xxx
模式去援用。
告警
除了 Prometheus 自身能够配置告警表达式之外:
grafana 也能够配置告警:
数据源
Prometheus 通常用于后端利用的指标数据实时上报,次要用于异样告警,问题排查,所以数据 存在时效性,咱们不会关注几个月前的一个曾经被排查并 fixed 的指标异样稳定告警。
然而,要是咱们将 Prometheus 用于业务指标监控,那么咱们可能会关注更长远的数据。
例如咱们可能想要看过来一个季度的环比同比增长,用 Prometheus 作为数据源就不适合,因为 Prometheus 是时序数据库,更多关注实时数据,数据量大,以后数据保留的时效设定只有 3 个月。
那么这个时候可能咱们要保护一个长期的统计数据,可能就须要存储在 mysql 或者其余存储形式。
grafana 不是 Prometheus 的专属产品,还反对多种数据源,包含但不限于:
-
常见数据库
- MySql
- SQL Server
- PostgreSQL
- Oracle
-
日志、文档数据库
- Loki
- Elasticsearch
-
时序数据库
- Prometheus
- graphite
- openTSDB
- InfluxDB
-
链路追踪
- Jaeger
- Zipkin
- ….
如果没有本人须要的数据源配置,还能够装置 REST API Datasource Plugin, 通过 http 接口查问作为数据源
总结
理解 grafana 的高度可配置性设计后,有值得思考的几点:
- 关注其设计思维,如果要本人实现一个相似的可视化的 web app,本人会怎么设计?
- 本人要做一个高度可配置化的性能,又应该怎么设计?
- 深刻到业务,例如咱们罕用的 admin 治理 零碎,一些罕用的业务性能是否能够高度可配置化?业务强关联的如何做到配置与业务的有机联合?
等等这些,其实都是值得咱们去思考的。
此外,Prometheus 和 grafana 都有些进阶的玩法,大家有趣味也能够去摸索下。
参考文章
- Prometheus 的数据存储实现【实践篇】
- prometheus tsdb 的存储与索引
- query processing would load too many samples into memory in query execution