一个复杂的应用,每每由不少个模块组成,并且每每会存在各类各样奇奇怪怪的使用场景,谁也不能保证本身维护的服务永远不会出问题,等用户投诉才发现问题再去处理问题就为时已晚,损失已没法挽回。html
因此,经过数据指标来衡量一个服务的稳定性和处理效率,是否正常运做,监控指标曲线的状态,指标出现异常时及时主动告警,这一套工具就十分重要。前端
常见的一些指标,包括但不限于:node
举个例子,假如一个服务:mysql
<!--这究竟是人性的扭曲仍是道德的沦丧,-->
一旦服务存在某些缺陷致使这些问题,经过服务日志,很难直观快速地察觉到这些指标的变化波动。golang
经过监控和告警手段能够有效地覆盖了「发现」和「定位」问题,从而更有效率地排查和解决问题。web
Prometheus 是一个开源的服务监控系统和时间序列数据库。sql
工做流能够简化为:数据库
具体的架构设计以下:后端
Prometheus 用的是本身设计的时序数据库(TSDB),那么为何不用咱们更加熟悉,更加经常使用的 mysql, 或者其余关系型数据库呢?api
假设须要监控 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 做为 存储引擎。
时序数据库主要用于指处理带时间标签(按照时间的顺序变化,即时间序列化)的数据,带时间标签的数据也称为时序数据。
对于 prometheus 来讲,每一个时序点结构以下:
每一个指标,有多个时序图;多个时序数据点链接起来,构成一个时序图
假如用传统的关系型数据库来表示时序数据,就是如下结构:
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,时序数据库核心在于时序,其查询时间相关的数据消耗的资源相对较低,效率相对较高,而刚好指标监控数据带有明显的时序特性,因此采用时序数据库做为存储层
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()) }
/metrics
接口,得到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》;
根据以上的查询语法,咱们能够简单组合出一些指标数据:
例如,查询最近一天内的 /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:
数据存储:
![]()
指标数据有 “Writes are vertical,reads are horizontal” 的(垂直写,水平读)模式:
“Writes are vertical,reads are horizontal” 的意思是 tsdb 一般按固定的时间间隔收集指标并写入,会 “垂直” 地写入最近全部时间序列的数据,而读取操做每每面向必定时间范围的一个或多个时间序列,“横向” 地跨越时间进行查询
而 Prometheus 的默认查询 sample 上限是 5000w
因此,若是指标的时序图数量过大,容许查询的时间区间相对就会较小了。
一个图表查询时序数量的影响因素有 3 个,分别是:
以 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 便可作到。
credit_insight_spl_id_all_pv{systemType="Android", systemVersion="10"}
,n = 18955固然,通常状况下,咱们的 n 值只有几百,而 step 基本是大于 60s 的,因此通常状况下都能查询 2 个多月以上的数据图。
grafana 是一个开源的,高度可配置的数据图表分析,监控,告警的平台,也是一款前端可视化的产品。
grafana 内置提供多种图表模板,具体是如下类型:
Prometheus 做为数据源的状况下,通常用的 graph 类型画时序图比较多。
对于一些基础的数据大盘监控,这些图表类型已经足够知足咱们的需求。
但对于复杂的需求,这些类型没法知足咱们的须要时,咱们安装 pannel 插件,来更新可用的图表类型,也能够根据官方文档 build a panel plugin 开发本身的前端图表 panel。
在时序图表配置场景下,咱们须要核心关注配置的有:
从上图能够看到,
为了实现一些经常使用的筛选过滤场景,grafana 提供了变量功能
$xxx
形式去引用。除了 Prometheus 自己能够配置告警表达式以外:
grafana 也能够配置告警:
Prometheus 一般用于后端应用的指标数据实时上报,主要用于异常告警,问题排查,因此数据存在时效性,咱们不会关注几个月前的一个已经被排查并 fixed 的指标异常波动告警。
可是,要是咱们将 Prometheus 用于业务指标监控,那么咱们可能会关注更久远的数据。
例如咱们可能想要看过去一个季度的环比同比增加,用 Prometheus 做为数据源就不合适,由于 Prometheus 是时序数据库,更多关注实时数据,数据量大,当前数据保存的时效设定只有 3 个月。
那么这个时候可能咱们要维护一个长期的统计数据,可能就须要存储在 mysql 或者其余存储方式。
grafana 不是 Prometheus 的专属产品,还支持多种数据源,包括但不限于:
常见数据库
日志、文档数据库
时序数据库
链路追踪
若是没有本身须要的数据源配置,还能够安装 REST API Datasource Plugin, 经过 http 接口查询做为数据源
了解 grafana 的高度可配置性设计后,有值得思考的几点:
等等这些,其实都是值得咱们去思考的。
此外,Prometheus 和 grafana 都有些进阶的玩法,你们有兴趣也能够去探索下。