乐趣区

关于前端:NodeJS-从-0-开始-Prometheus-Grafana-业务性能指标监控

为什么须要指标监控告警

一个简单的利用,往往由很多个模块组成,而且往往会存在各种各样奇奇怪怪的应用场景,谁也不能保障本人保护的服务永远不会出问题,等用户投诉才发现问题再去解决问题就为时已晚,损失已无法挽回。

所以,通过数据指标来掂量一个服务的稳定性和解决效率,是否失常运作,监控指标曲线的状态,指标出现异常时及时被动告警,这一套工具就非常重要。

常见的一些指标,包含但不限于:

  • QPS
  • 申请解决耗时
  • 过程占用内存
  • 过程占用 CPU
  • golang 服务的 goroutine
  • nodejs 的 event loop lag
  • 前端利用的 Performance 耗时

举个例子,如果一个服务:

  • 应用内存随着工夫逐步上涨
  • CPU 占用越来越高
  • 申请耗时越来越高,申请成功率降落
  • 磁盘空间频频被挤爆

<!– 这到底是兽性的扭曲还是道德的沦丧,–>
一旦服务存在某些缺点导致这些问题,通过服务日志,很难直观疾速地察觉到这些指标的变动稳定。

通过监控和告警伎俩能够无效地笼罩了「发现」和「定位」问题,从而更有效率地排查和解决问题。

指标监控零碎:Prometheus

Prometheus 是一个开源的服务监控零碎和工夫序列数据库。

工作流能够简化为:

  1. client 采集以后 机器 / 服务 / 过程 的状态等相干指标数据
  2. Prometheus server 按肯定的工夫周期被动拉取 client 的指标数据,并存储到时序数据库中
  3. 发现指标异样后,通过 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 个,别离是:

  1. 查问条件的时序数量(n)
  2. 查问的工夫区间(time)
  3. 图表曲线每个时序点之间的距离(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。

图表配置

在时序图表配置场景下,咱们须要外围关注配置的有:

  1. promQL: 查问语句
  2. Legend: 格式化图例文本
  3. step/interval: 采集点距离,每隔一段时间,采集一次数据。
    一条曲线的数据点数量 = 图表时长 / 采样距离。例如查看最近 24 小时的数据,采样 距离 5min,数据点数量 =24*60/5=288。
    采集间隔时间越短,采样率越大,图表数据量越大,曲线越平滑。采集距离默认主动计算生成,也能够自定义配置。
  4. 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 都有些进阶的玩法,大家有趣味也能够去摸索下。

参考文章

  1. Prometheus 的数据存储实现【实践篇】
  2. prometheus tsdb 的存储与索引
  3. query processing would load too many samples into memory in query execution
退出移动版