为什么需要指标监控和告警?一个复杂的应用往往是由很多模块组成的,往往会有各种千奇百怪的使用场景。谁也不能保证它维护的服务永远不会出错。用户投诉发现问题再处理就来不及了,损失是无法弥补的。因此,利用数据指标来衡量一个服务的稳定性和处理效率,是否正常运行,监控指标曲线的状态,在指标异常时及时主动报警就显得非常重要了。一些常见的指标包括但不限于:QPS请求处理耗时进程占用内存进程占用CPUgolang服务goroutinenodejs事件循环滞后前端应用性能耗时...例如如果一个服务:使用内存超时逐渐上升的CPU占用率越来越高,请求耗时越来越高,请求成功率越来越低。磁盘空间经常过度拥挤。或者前端单页应用:前端重定向到/error页面,/excption页面越来越多。某个页面的打开次数越来越少。某个系统/版??本的设备激活率越来越低……这是人性的扭曲还是道德的沦丧?一旦应用中的某些缺陷导致了这些问题,通过服务日志很难直观快速地检测到这些指标的变化和波动。至于前端,很可能根本无法感知用户的行为,只能通过埋点进行一定程度的监控。通过监控告警手段,有效覆盖“发现”和“定位”问题,更高效地排查和解决问题。指标监控系统:PrometheusPrometheus是一个开源的服务监控系统和时序数据库。工作流程可以简化为:客户端采集当前机器/服务/进程状态等相关指标数据。Prometheus服务器根据一定时间主动拉取客户端的指标数据,并存储在时序数据库中。发现指标异常后,告警管理器会向相关负责人发送告警通知的具体架构设计如下:为什么不用mysql存储?Prometheus使用的是自己设计的时序数据库(TSDB),那为什么不使用大家比较熟悉和常用的mysql,或者其他关系型数据库呢?假设需要监控WebServerA的各个API的请求量为例,需要监控的维度包括:服务名(job)、实例IP(instance)、API名(handler)、方法(method)、返回码(代码)和请求量(值)。以SQL为例,演示常见的查询操作:#查询method=put,code=200的请求量SELECT*fromhttp_requests_totalWHEREcode=”200”ANDmethod=”put”ANDcreated_atBETWEEN1495435700AND1495435710;#查询处理程序=prometheusandmethod=postrequestsSELECT*fromhttp_requests_totalWHEREhandler=”prometheus”ANDmethod=”post”ANDcreated_atBETWEEN1495435700AND1495435710;#Queryinstance=10.59.8.110andthehandlerstartswithquerySELECT*fromhttp_requests_totalWHEREhandler=”query”ANDinstance=”10.59.8.110”ANDcreated_atBETWEEN1495435700AND1495435710;从上面的例子可以看出,在常见的查询和统计方面,日常监控大多是根据监控的维度,结合查询和时间进行查询。如果监控100个服务,每个服务平均部署10个实例,每个服务有20个API,4个方法,每30秒采集一次数据,保存60天。那么数据条总数为:100(服务)*10(实例)*20(API)*4(方法)*86400(秒/天)*60(天)/30(秒)=138.24亿条数据,这种量级的数据在Mysql等关系型数据库上是不可能写入、存储和查询的。所以Prometheus使用TSDB作为存储引擎。TimeSeriesDatabase(TSDB)TimeSeriesDatabase主要用于处理带有时间标签的数据(时间顺序的变化,即时间序列化)。带有时间标签的数据也称为时间序列数据。对于prometheus来说,每个时序点的结构如下:metric:metric名称,当前数据的标识,在某些系统中也叫name。label:标签属性timestamp:数据点的时间,表示数据发生的时间。value:值,数据的价值每个指标都有多个时间序列图;多个时间序列数据点连接起来形成一个时间序列图。如果用传统的关系数据库来表示时序数据,它有如下结构:create_time__metric_name__pathvalue2020-10-0100:00:00http_request_total/home1002020-10-0100:00:00http_request_total/error02020-10-0100:00:15http_request_total/home1202020-10-0100:01:00http_request_total/home1602020-10-0100:01:00http_request_total/2020-10-0100:01:00qps=(160-100)/60=1,同样,指标request_total{path="/error"}在2020-10-0100:01:00时qps=1/60。与MySQL相比,时序数据库的核心在于时间序列。查询与时间相关的数据,资源消耗相对较低,效率较高。数据具有明显的时序特征,因此采用时序数据库作为存储层的数据类型。Change,diskcapacity,CPUusage,histogram:,聚合数据查询耗时分布【服务器端计算,模糊,不准确】总结:无法聚合查询的耗时分布【客户端计算,准确】nodejs索引集合和数据拉取并定义一个Counter数据类型,记录索引constreqCounter=newCounter({name:`credit_insight_spl_id_all_pv`,help:'requestcount',labelNames:['deviceBrand','systemType','appVersion','channel']})reqCounter.inc({deviceBrand:'Apple',ssystemType:'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客户端的/metrics接口获取当前数据快照promQLpromQL是prometheus的查询语言,语法很simple和basicquery查询指标都是最新的Value:{__name__="http_request_total",handler="/home"}#语法糖:http_request_total{handler="/home"}#相当于mysql:select*fromhttp_request_totalwherehandler="/home"ANDcreate_time=《now()》间隔时间段查询查询过去一分钟的数据#promQLhttp_request_total[1m]#相当于SELECT*fromhttp_requests_totalWHEREcreate_timeBETWEEN《now() - 1min》AND《now()》;时间偏移量查询PS:promQL不支持指定时间点查询,只能通过偏移量查询历史某个时间点的数据,查询一小时前的数据#promQLhttp_request_totaloffset1h#相当于SELECT*fromhttp_requests_totalWHEREcreate_time=《now() - 1 hour》;promQL查询函数根据上面的查询语法,我们可以简单的组合一些索引数据:比如查询最近的/home页面请求数dayhttp_request_total{handler="/home"}-http_request_total{handler="/home"}offset1d其实上面的写法显然不够简洁,我们可以使用内置的增加函数来代替:#相当于上面的写法increase(http_request_total{handler="/home"}[1d])除了increase之外,还有很多其他有用的功能,比如rate函数计算QPS//每秒平均请求数过去2分钟rate(http_request_total{code="400"}[2m])//相当于增加(http_request_total{code="400"}[2m])/120个索引聚合查询除了上面的基础查询,我们还可以还需要聚合查询如果我们有以下数据指标:,channel="none"}credit_insight_spl_id_all_pv{url="/error",channel="mepage"}当所有的索引数据在某个维度上聚合查询时,例如:查询url="/home"最后一天的访问量,channel是none或mepage的/homevisits都包含在里面。当然我们会写:increase(credit_insight_spl_id_all_pv{url="/home"}[1d])但实际上我们会得到两个这样的指标:credit_insight_spl_id_all_pv{url="/home",channel="none"}233credit_insight_spl_id_all_pv{url="/home",channel="mepage"}666不是我们期望的:credit_insight_spl_id_all_pv{url="/home"}899但是如果我们想要得到这样的聚合查询结果,我们需要使用sumby#聚合url="/home"的数据sum(increase(credit_insight_spl_id_all_pv{url="/home"}[1d]))by(url)#得到结果:credit_insight_spl_id_all_pv{url="/home"}899#all的累计值访问/homepageinthechannel#聚合所有url可以这样写:sum(increase(credit_insight_spl_id_all_pv{}[1d]))by(url)#得到结果:credit_insight_spl_id_all_pv{url="/home"}899credit_insight_spl_id_all_pv{url="/error"}7#相当于mysqlSELECTurl,COUNT(*)AStotalFROMcredit_insight_spl_id_all_pvWHEREcreate_timebetween
